1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-16 22:50:10 +03:00
This commit is contained in:
Sergio Betanzos 2021-06-30 11:09:48 +02:00
parent 1cc50546ca
commit 09bd5cbf7c
No known key found for this signature in database
GPG Key ID: E3E704F097737136
46 changed files with 1038 additions and 179 deletions

View File

@ -28,7 +28,7 @@ import { _APPS } from 'client/constants'
const APP_NAME = _APPS.provision.name
const ProvisionApp = () => {
const { jwt, firstRender } = useAuth()
const { isLogged, jwt, firstRender } = useAuth()
const { getAuthUser, logout } = useAuthApi()
const provisionTemplate = useProvisionTemplate()
@ -39,6 +39,7 @@ const ProvisionApp = () => {
(async () => {
try {
if (jwt) {
changeTitle(APP_NAME)
getAuthUser()
!provisionTemplate?.length && await getProvisionsTemplates()
}
@ -48,15 +49,11 @@ const ProvisionApp = () => {
})()
}, [jwt])
React.useEffect(() => {
changeTitle(APP_NAME)
}, [])
if (jwt && firstRender) {
return <LoadingScreen />
}
return <Router routes={routes} />
return <Router isLogged={isLogged} routes={routes} />
}
ProvisionApp.displayName = '_ProvisionApp'

View File

@ -29,8 +29,7 @@ export const PATH = {
CREATE: '/provisions/create',
EDIT: '/provisions/edit/:id'
},
SETTINGS: '/settings',
TEST_API: '/test-api'
SETTINGS: '/settings'
}
export const ENDPOINTS = [

View File

@ -16,45 +16,37 @@
import * as React from 'react'
import Router from 'client/router'
import routes from 'client/apps/flow/routes'
import routes from 'client/apps/sunstone/routes'
import { useGeneralApi } from 'client/features/General'
import { useAuth, useAuthApi } from 'client/features/Auth'
import LoadingScreen from 'client/components/LoadingScreen'
import { fakeDelay } from 'client/utils'
import { _APPS, TIME_HIDE_LOGO } from 'client/constants'
import { _APPS } from 'client/constants'
const APP_NAME = _APPS.sunstone.name
const SunstoneApp = () => {
const [firstRender, setFirstRender] = React.useState(() => true)
const { jwt } = useAuth()
const { getAuthUser } = useAuthApi()
const { isLogged, jwt, firstRender } = useAuth()
const { getAuthUser, logout } = useAuthApi()
const { changeTitle } = useGeneralApi()
React.useEffect(() => {
if (firstRender) {
jwt && (async () => {
await getAuthUser()
})()
(async () => {
try {
jwt && changeTitle(APP_NAME)
jwt && getAuthUser()
} catch {
logout()
}
})()
}, [jwt])
fakeDelay(TIME_HIDE_LOGO).then(() => setFirstRender(false))
}
}, [firstRender, jwt])
React.useEffect(() => {
changeTitle(APP_NAME)
}, [])
if (firstRender) {
if (jwt && firstRender) {
return <LoadingScreen />
}
return (
<Router routes={routes} />
)
return <Router isLogged={isLogged} routes={routes} />
}
SunstoneApp.displayName = '_SunstoneApp'

View File

@ -23,24 +23,24 @@ import MuiProvider from 'client/providers/muiProvider'
import NotistackProvider from 'client/providers/notistackProvider'
import { TranslateProvider } from 'client/components/HOC'
import App from 'client/apps/flow/_app'
import theme from 'client/apps/flow/theme'
import App 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
const Sunstone = ({ store, location, context }) => (
const Provision = ({ store, location, context }) => (
<ReduxProvider store={store}>
<TranslateProvider>
<MuiProvider theme={theme}>
<NotistackProvider>
{location && context ? (
// server build
// server build
<StaticRouter location={location} context={context}>
<App />
</StaticRouter>
) : (
// browser build
// browser build
<BrowserRouter basename={`${APP_URL}/${APP_NAME}`}>
<App />
</BrowserRouter>
@ -51,18 +51,18 @@ const Sunstone = ({ store, location, context }) => (
</ReduxProvider>
)
Sunstone.propTypes = {
Provision.propTypes = {
location: PropTypes.string,
context: PropTypes.shape({}),
store: PropTypes.shape({})
}
Sunstone.defaultProps = {
Provision.defaultProps = {
location: '',
context: {},
store: {}
}
Sunstone.displayName = 'SunstoneApp'
Provision.displayName = 'SunstoneApp'
export default Sunstone
export default Provision

View File

@ -1,21 +1,10 @@
import {
ReportColumns as DashboardIcon,
List as TemplatesIcons,
Cell4x4 as InstancesIcons
} from 'iconoir-react'
import loadable from '@loadable/component'
const Dashboard = loadable(
() => import('client/containers/Dashboard'),
{ ssr: false }
)
const Settings = loadable(
() => import('client/containers/Settings'),
{ ssr: false }
)
const ApplicationsTemplates = loadable(
() => import('client/containers/ApplicationsTemplates'),
{ ssr: false }
@ -32,7 +21,6 @@ const ApplicationsTemplatesFormCreate = loadable(
)
export const PATH = {
DASHBOARD: '/dashboard',
APPLICATIONS_TEMPLATES: {
LIST: '/applications-templates',
CREATE: '/applications-templates/create',
@ -40,26 +28,12 @@ export const PATH = {
},
APPLICATIONS: {
LIST: '/applications'
},
SETTINGS: '/settings'
}
}
export const ENDPOINTS = [
{
label: 'Dashboard',
path: PATH.DASHBOARD,
sidebar: true,
icon: DashboardIcon,
Component: Dashboard
},
{
label: 'Settings',
path: PATH.SETTINGS,
header: true,
Component: Settings
},
{
label: 'Templates',
label: 'Service Templates',
path: PATH.APPLICATIONS_TEMPLATES.LIST,
sidebar: true,
icon: TemplatesIcons,
@ -76,7 +50,7 @@ export const ENDPOINTS = [
Component: ApplicationsTemplatesFormCreate
},
{
label: 'Instances',
label: 'Service Instances',
path: PATH.APPLICATIONS.LIST,
sidebar: true,
icon: InstancesIcons,

View File

@ -0,0 +1,225 @@
import {
ReportColumns as DashboardIcon,
Settings as SettingsIcon,
Cell4x4 as InstancesIcons,
User as UserIcon,
SimpleCart as MarketplaceIcon,
CloudDownload as MarketplaceAppIcon,
EmptyPage as TemplateIcon,
Server as ClusterIcon,
HardDrive as HostIcon,
Folder as DatastoreIcon,
Group as GroupIcon,
Archive as ImageIcon,
NetworkAlt as NetworkIcon
} from 'iconoir-react'
import loadable from '@loadable/component'
const Dashboard = loadable(() => import('client/containers/Dashboard/Sunstone'), { ssr: false })
const Settings = loadable(() => import('client/containers/Settings'), { ssr: false })
const VirtualMachines = loadable(() => import('client/containers/VirtualMachines'), { ssr: false })
// const VirtualRouters = loadable(() => import('client/containers/VirtualRouters'), { ssr: false })
const VmTemplates = loadable(() => import('client/containers/VmTemplates'), { ssr: false })
// const VrTemplates = loadable(() => import('client/containers/VrTemplates'), { ssr: false })
// const VmGroups = loadable(() => import('client/containers/VmGroups'), { ssr: false })
const Datastores = loadable(() => import('client/containers/Datastores'), { ssr: false })
const Images = loadable(() => import('client/containers/Images'), { ssr: false })
// const Files = loadable(() => import('client/containers/Files'), { ssr: false })
const Marketplaces = loadable(() => import('client/containers/Marketplaces'), { ssr: false })
const MarketplaceApps = loadable(() => import('client/containers/MarketplaceApps'), { ssr: false })
// const VirtualNetworks = loadable(() => import('client/containers/VirtualNetworks'), { ssr: false })
// const NetworkTemplates = loadable(() => import('client/containers/NetworkTemplates'), { ssr: false })
// const NetworkTopologies = loadable(() => import('client/containers/NetworkTopologies'), { ssr: false })
// const SecurityGroups = loadable(() => import('client/containers/SecurityGroups'), { ssr: false })
const Clusters = loadable(() => import('client/containers/Clusters'), { ssr: false })
const Hosts = loadable(() => import('client/containers/Hosts'), { ssr: false })
// const Zones = loadable(() => import('client/containers/Zones'), { ssr: false })
const Users = loadable(() => import('client/containers/Users'), { ssr: false })
const Groups = loadable(() => import('client/containers/Groups'), { ssr: false })
// const VDCs = loadable(() => import('client/containers/VDCs'), { ssr: false })
// const ACLs = loadable(() => import('client/containers/ACLs'), { ssr: false })
export const PATH = {
DASHBOARD: '/dashboard',
INSTANCE: {
VMS: {
LIST: '/vms'
}
},
TEMPLATE: {
VMS: {
LIST: '/vm-templates'
}
},
STORAGE: {
DATASTORES: {
LIST: '/datastores'
},
IMAGES: {
LIST: '/images'
},
FILES: {
LIST: '/files'
},
MARKETPLACES: {
LIST: '/marketplaces'
},
MARKETPLACE_APPS: {
LIST: '/marketplaces-apps'
}
},
NETWORK: {
VNETS: {
LIST: '/virtual-networks'
},
VN_TEMPLATES: {
LIST: '/network-templates'
},
SEC_GROUPS: {
LIST: '/security-groups'
}
},
INFRASTRUCTURE: {
CLUSTERS: {
LIST: '/clusters'
},
HOSTS: {
LIST: '/hosts'
}
},
SYSTEM: {
USERS: {
LIST: '/users'
},
GROUPS: {
LIST: '/groups'
}
},
SETTINGS: '/settings'
}
export const ENDPOINTS = [
{
label: 'Dashboard',
path: PATH.DASHBOARD,
sidebar: true,
icon: DashboardIcon,
Component: Dashboard
},
{
label: 'Instances',
sidebar: true,
routes: [
{
label: 'VMs',
path: PATH.INSTANCE.VMS.LIST,
sidebar: true,
icon: InstancesIcons,
Component: VirtualMachines
}
]
},
{
label: 'Templates',
sidebar: true,
routes: [
{
label: 'VMs',
path: PATH.TEMPLATE.VMS.LIST,
sidebar: true,
icon: TemplateIcon,
Component: VmTemplates
}
]
},
{
label: 'Storage',
sidebar: true,
routes: [
{
label: 'Datastores',
path: PATH.STORAGE.DATASTORES.LIST,
sidebar: true,
icon: DatastoreIcon,
Component: Datastores
},
{
label: 'Images',
path: PATH.STORAGE.IMAGES.LIST,
sidebar: true,
icon: ImageIcon,
Component: Images
},
{
label: 'Marketplaces',
path: PATH.STORAGE.MARKETPLACES.LIST,
sidebar: true,
icon: MarketplaceIcon,
Component: Marketplaces
},
{
label: 'Apps',
path: PATH.STORAGE.MARKETPLACE_APPS.LIST,
sidebar: true,
icon: MarketplaceAppIcon,
Component: MarketplaceApps
}
]
},
{
label: 'Infrastructure',
sidebar: true,
routes: [
{
label: 'Clusters',
path: PATH.INFRASTRUCTURE.CLUSTERS.LIST,
sidebar: true,
icon: ClusterIcon,
Component: Clusters
},
{
label: 'Hosts',
path: PATH.INFRASTRUCTURE.HOSTS.LIST,
sidebar: true,
icon: HostIcon,
Component: Hosts
}
]
},
{
label: 'System',
sidebar: true,
routes: [
{
label: 'Users',
path: PATH.SYSTEM.USERS.LIST,
sidebar: true,
icon: UserIcon,
Component: Users
},
{
label: 'Groups',
path: PATH.SYSTEM.GROUPS.LIST,
sidebar: true,
icon: GroupIcon,
Component: Groups
}
]
},
{
label: 'Settings',
path: PATH.SETTINGS,
sidebar: true,
icon: SettingsIcon,
Component: Settings
}
]
export default { PATH, ENDPOINTS }

View File

@ -4,6 +4,7 @@ import clsx from 'clsx'
import {
List,
Icon as MIcon,
Collapse,
ListItem,
ListItemText,
@ -32,26 +33,24 @@ const SidebarCollapseItem = ({ label, routes, icon: Icon }) => {
<Icon />
</ListItemIcon>
)}
<ListItemText primary={label} />
{expanded ? (
<CollapseIcon
className={clsx({ [classes.expandIcon]: isUpLg && !isFixMenu })}
/>
) : (
<ExpandMoreIcon
className={clsx({ [classes.expandIcon]: isUpLg && !isFixMenu })}
/>
)}
<ListItemText
className={classes.itemText}
data-max-label={label}
data-min-label={label.slice(0, 3)}
/>
<MIcon className={clsx({ [classes.expandIcon]: isUpLg && !isFixMenu })}>
{expanded ? <CollapseIcon /> : <ExpandMoreIcon />}
</MIcon>
</ListItem>
{routes?.map((subItem, index) => (
<Collapse
key={`subitem-${index}`}
in={expanded}
timeout="auto"
timeout='auto'
unmountOnExit
className={clsx({ [classes.subItemWrapper]: isUpLg && !isFixMenu })}
>
<List component="div" disablePadding>
<List component='div' disablePadding>
<SidebarLink {...subItem} isSubItem />
</List>
</Collapse>

View File

@ -36,11 +36,11 @@ export default makeStyles(theme => ({
'& #logo__text': {
visibility: 'hidden'
},
'& $menu': {
overflowY: 'hidden'
},
'& $expandIcon, & $subItemWrapper': {
display: 'none'
},
'& $itemText::before': {
content: 'attr(data-min-label)'
}
}
}
@ -62,6 +62,9 @@ export default makeStyles(theme => ({
},
'& $expandIcon, & $subItemWrapper': {
display: 'block !important'
},
'& $itemText::before': {
content: 'attr(data-max-label) !important'
}
}
},
@ -114,6 +117,14 @@ export default makeStyles(theme => ({
list: {
color: theme.palette.text.primary
},
itemText: {
'&::before': {
...theme.typography.body1,
display: 'block',
minWidth: 100,
content: 'attr(data-max-label)'
}
},
expandIcon: {},
subItemWrapper: {},
subItem: {

View File

@ -0,0 +1,12 @@
const getTotalOfResources = resources => [resources?.ID ?? []].flat().length || 0
export default [
{ Header: 'ID', accessor: 'ID', sortType: 'number' },
{ Header: 'Name', accessor: 'NAME' },
{
Header: 'Total Users',
id: 'TOTAL_USERS',
accessor: row => getTotalOfResources(row?.USERS),
sortType: 'number'
}
]

View File

@ -0,0 +1,31 @@
import React, { useEffect } from 'react'
import { useFetch } from 'client/hooks'
import { useGroup, useGroupApi } from 'client/features/One'
import { EnhancedTable } from 'client/components/Tables'
import GroupColumns from 'client/components/Tables/Groups/columns'
import GroupRow from 'client/components/Tables/Groups/row'
const GroupsTable = () => {
const columns = React.useMemo(() => GroupColumns, [])
const groups = useGroup()
const { getGroups } = useGroupApi()
const { fetchRequest, loading, reloading } = useFetch(getGroups)
useEffect(() => { fetchRequest() }, [])
return (
<EnhancedTable
columns={columns}
data={groups}
isLoading={loading || reloading}
getRowId={row => String(row.ID)}
RowComponent={GroupRow}
/>
)
}
export default GroupsTable

View File

@ -0,0 +1,42 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { Group } from 'iconoir-react'
import { Typography } from '@material-ui/core'
import { rowStyles } from 'client/components/Tables/styles'
const Row = ({ original, value, ...props }) => {
const classes = rowStyles()
const { ID, NAME, TOTAL_USERS } = value
return (
<div {...props}>
<div className={classes.main}>
<div className={classes.title}>
<Typography className={classes.titleText} component='span'>
{NAME}
</Typography>
</div>
<div className={classes.caption}>
<span>
{`#${ID}`}
</span>
<span title={`Total Users: ${TOTAL_USERS}`}>
<Group size={16} />
<span>{` ${TOTAL_USERS}`}</span>
</span>
</div>
</div>
</div>
)
}
Row.propTypes = {
original: PropTypes.object,
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func
}
export default Row

View File

@ -0,0 +1,7 @@
export default [
{ Header: 'ID', accessor: 'ID', sortType: 'number' },
{ Header: 'Name', accessor: 'NAME' },
{ Header: 'Group', accessor: 'GNAME' },
{ Header: 'Enabled', accessor: 'ENABLED' },
{ Header: 'Auth driver', accessor: 'AUTH_DRIVER' }
]

View File

@ -0,0 +1,31 @@
import React, { useEffect } from 'react'
import { useFetch } from 'client/hooks'
import { useUser, useUserApi } from 'client/features/One'
import { EnhancedTable } from 'client/components/Tables'
import UserColumns from 'client/components/Tables/Users/columns'
import UserRow from 'client/components/Tables/Users/row'
const UsersTable = () => {
const columns = React.useMemo(() => UserColumns, [])
const users = useUser()
const { getUsers } = useUserApi()
const { fetchRequest, loading, reloading } = useFetch(getUsers)
useEffect(() => { fetchRequest() }, [])
return (
<EnhancedTable
columns={columns}
data={users}
isLoading={loading || reloading}
getRowId={row => String(row.ID)}
RowComponent={UserRow}
/>
)
}
export default UsersTable

View File

@ -0,0 +1,49 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { Group, Lock, LockKey } from 'iconoir-react'
import { Typography } from '@material-ui/core'
import { rowStyles } from 'client/components/Tables/styles'
const Row = ({ original, value, ...props }) => {
const classes = rowStyles()
const { ID, NAME, GNAME, ENABLED, AUTH_DRIVER } = value
return (
<div {...props}>
<div className={classes.main}>
<div className={classes.title}>
<Typography className={classes.titleText} component='span'>
{NAME}
</Typography>
<span className={classes.labels}>
{!+ENABLED && <Lock size={20} />}
</span>
</div>
<div className={classes.caption}>
<span>
{`#${ID}`}
</span>
<span title={`Group: ${GNAME}`}>
<Group size={16} />
<span>{` ${GNAME}`}</span>
</span>
<span title={`Auth Driver: ${AUTH_DRIVER}`}>
<LockKey size={16} />
<span>{` ${AUTH_DRIVER}`}</span>
</span>
</div>
</div>
</div>
)
}
Row.propTypes = {
original: PropTypes.object,
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func
}
export default Row

View File

@ -0,0 +1,8 @@
export default [
{ Header: 'ID', accessor: 'ID', sortType: 'number' },
{ Header: 'Name', accessor: 'NAME' },
{ Header: 'Owner', accessor: 'UNAME' },
{ Header: 'Group', accessor: 'GNAME' },
{ Header: 'Start Time', accessor: 'REGTIME' },
{ Header: 'Locked', accessor: 'LOCK' }
]

View File

@ -0,0 +1,70 @@
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import { LinearProgress } from '@material-ui/core'
import Tabs from 'client/components/Tabs'
import { useFetch } from 'client/hooks'
import { useVmTemplateApi } from 'client/features/One'
import * as Helper from 'client/models/Helper'
const VmTemplateDetail = ({ id }) => {
const { getVmTemplate } = useVmTemplateApi()
const { data, fetchRequest, loading, error } = useFetch(getVmTemplate)
useEffect(() => {
fetchRequest(id)
}, [id])
if ((!data && !error) || loading) {
return <LinearProgress color='secondary' style={{ width: '100%' }} />
}
if (error) {
return <div>{error}</div>
}
const { ID, NAME, UNAME, GNAME, REGTIME, LOCK, TEMPLATE } = data
const tabs = [
{
name: 'info',
renderContent: (
<div>
<span>
{`#${ID} - ${NAME}`}
</span>
<div>
<p>Owner: {UNAME}</p>
<p>Group: {GNAME}</p>
<p>Locked: {Helper.levelLockToString(LOCK?.LOCKED)}</p>
<p>Register time: {Helper.timeToString(REGTIME)}</p>
</div>
</div>
)
},
{
name: 'template',
renderContent: (
<div>
<pre>
<code>
{JSON.stringify(TEMPLATE, null, 2)}
</code>
</pre>
</div>
)
}
]
return (
<Tabs tabs={tabs} />
)
}
VmTemplateDetail.propTypes = {
id: PropTypes.string.isRequired
}
export default VmTemplateDetail

View File

@ -0,0 +1,33 @@
import React, { useEffect } from 'react'
import { useFetch } from 'client/hooks'
import { useVmTemplate, useVmTemplateApi } from 'client/features/One'
import { EnhancedTable } from 'client/components/Tables'
import VmTemplateColumns from 'client/components/Tables/VmTemplates/columns'
import VmTemplateRow from 'client/components/Tables/VmTemplates/row'
import VmTemplateDetail from 'client/components/Tables/VmTemplates/detail'
const VmTemplatesTable = () => {
const columns = React.useMemo(() => VmTemplateColumns, [])
const vmTemplates = useVmTemplate()
const { getVmTemplates } = useVmTemplateApi()
const { fetchRequest, loading, reloading } = useFetch(getVmTemplates)
useEffect(() => { fetchRequest() }, [])
return (
<EnhancedTable
columns={columns}
data={vmTemplates}
isLoading={loading || reloading}
getRowId={row => String(row.ID)}
RowComponent={VmTemplateRow}
renderDetail={row => <VmTemplateDetail id={row.ID} />}
/>
)
}
export default VmTemplatesTable

View File

@ -0,0 +1,54 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { User, Group, Lock } from 'iconoir-react'
import { Typography } from '@material-ui/core'
import { rowStyles } from 'client/components/Tables/styles'
import * as Helper from 'client/models/Helper'
const Row = ({ original, value, ...props }) => {
const classes = rowStyles()
const { ID, NAME, UNAME, GNAME, REGTIME, LOCK } = value
const time = Helper.timeFromMilliseconds(+REGTIME)
const timeAgo = `registered ${time.toRelative()}`
return (
<div {...props}>
<div className={classes.main}>
<div className={classes.title}>
<Typography className={classes.titleText} component='span'>
{NAME}
</Typography>
<span className={classes.labels}>
{LOCK && <Lock size={20} />}
</span>
</div>
<div className={classes.caption}>
<span title={time.toFormat('ff')}>
{`#${ID} ${timeAgo}`}
</span>
<span title={`Owner: ${UNAME}`}>
<User size={16} />
<span>{` ${UNAME}`}</span>
</span>
<span title={`Group: ${GNAME}`}>
<Group size={16} />
<span>{` ${GNAME}`}</span>
</span>
</div>
</div>
</div>
)
}
Row.propTypes = {
original: PropTypes.object,
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func
}
export default Row

View File

@ -17,7 +17,11 @@ const Multiple = ({ tags, limitTags = 1 }) => {
))
return (
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
<div style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'end'
}}>
{Tags}
{more > 0 && (
<Tooltip arrow

View File

@ -1,12 +1,15 @@
import ClustersTable from 'client/components/Tables/Clusters'
import DatastoresTable from 'client/components/Tables/Datastores'
import EnhancedTable from 'client/components/Tables/Enhanced'
import GroupsTable from 'client/components/Tables/Groups'
import HostsTable from 'client/components/Tables/Hosts'
import ImagesTable from 'client/components/Tables/Images'
import MarketplaceAppsTable from 'client/components/Tables/MarketplaceApps'
import MarketplacesTable from 'client/components/Tables/Marketplaces'
import UsersTable from 'client/components/Tables/Users'
import VirtualizedTable from 'client/components/Tables/Virtualized'
import VmsTable from 'client/components/Tables/Vms'
import VmTemplatesTable from 'client/components/Tables/VmTemplates'
export {
EnhancedTable,
@ -14,9 +17,12 @@ export {
ClustersTable,
DatastoresTable,
GroupsTable,
HostsTable,
ImagesTable,
MarketplaceAppsTable,
MarketplacesTable,
VmsTable
UsersTable,
VmsTable,
VmTemplatesTable
}

View File

@ -0,0 +1,66 @@
import * as React from 'react'
import {
User as UserIcon,
Group as GroupIcon,
Archive as ImageIcon,
NetworkAlt as NetworkIcon
} from 'iconoir-react'
import { makeStyles } from '@material-ui/core'
import { useUser, useGroup, useImage, useVNetwork } from 'client/features/One'
import Count from 'client/components/Count'
import { WavesCard } from 'client/components/Cards'
import { T } from 'client/constants'
const useStyles = makeStyles({
root: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
gridGap: '2em'
}
})
const TotalProvisionInfrastructures = () => {
const classes = useStyles()
const users = useUser()
const groups = useGroup()
const images = useImage()
const vnetworks = useVNetwork()
return React.useMemo(() => (
<div
data-cy='dashboard-widget-total-sunstone-resources'
className={classes.root}
>
<WavesCard
text={T.Users}
value={<Count number={`${users.length}`} />}
bgColor='#fa7892'
icon={UserIcon}
/>
<WavesCard
text={T.Groups}
value={<Count number={`${groups.length}`} />}
bgColor='#b25aff'
icon={GroupIcon}
/>
<WavesCard
text={T.Images}
value={<Count number={`${images.length}`} />}
bgColor='#1fbbc6'
icon={ImageIcon}
/>
<WavesCard
text={T.VirtualNetwork}
value={<Count number={`${vnetworks.length}`} />}
bgColor='#f09d42'
icon={NetworkIcon}
/>
</div>
), [users.length, groups.length, images.length, vnetworks.length])
}
TotalProvisionInfrastructures.displayName = 'TotalProvisionInfrastructures'
export default TotalProvisionInfrastructures

View File

@ -1,9 +1,11 @@
import TotalProviders from 'client/components/Widgets/TotalProviders'
import TotalProvisionsByState from 'client/components/Widgets/TotalProvisionsByState'
import TotalProvisionInfrastructures from 'client/components/Widgets/TotalProvisionInfrastructures'
import TotalSunstoneResources from 'client/components/Widgets/TotalSunstoneResources'
export {
TotalProviders,
TotalProvisionInfrastructures,
TotalProvisionsByState
TotalProvisionsByState,
TotalSunstoneResources
}

View File

@ -8,7 +8,7 @@ import { yupResolver } from '@hookform/resolvers'
import FormStepper from 'client/components/FormStepper'
import Steps from 'client/containers/ApplicationsTemplates/Form/Create/Steps'
import { PATH } from 'client/apps/flow/routes'
import { PATH } from 'client/apps/sunstone/routes-flow'
import { useFetch } from 'client/hooks'
import { useApplicationTemplateApi } from 'client/features/One'
import { parseApplicationToForm, parseFormToApplication } from 'client/utils'

View File

@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'
import { useHistory } from 'react-router-dom'
import { Container, Box } from '@material-ui/core'
import { PATH } from 'client/apps/flow/routes'
import { PATH } from 'client/apps/sunstone/routes-flow'
import { useFetch } from 'client/hooks'
import { useApplicationTemplate, useApplicationTemplateApi } from 'client/features/One'

View File

@ -0,0 +1,23 @@
import * as React from 'react'
import { styled, Container as MContainer, Box } from '@material-ui/core'
import * as Tables from 'client/components/Tables'
const Container = styled(MContainer)`
display: flex;
flexDirection: column;
height: 100%
`
function Clusters () {
return (
<Container disableGutters>
<Box py={2} overflow='auto'>
<Tables.ClustersTable />
</Box>
</Container>
)
}
export default Clusters

View File

@ -0,0 +1,45 @@
import * as React from 'react'
import clsx from 'clsx'
import { Container, Box, Grid } from '@material-ui/core'
import { useAuth } from 'client/features/Auth'
import { useUserApi, useGroupApi, useImageApi, useVNetworkApi } from 'client/features/One'
import * as Widgets from 'client/components/Widgets'
import dashboardStyles from 'client/containers/Dashboard/Provision/styles'
function Dashboard () {
const { getUsers } = useUserApi()
const { getGroups } = useGroupApi()
const { getImages } = useImageApi()
const { getVNetworks } = useVNetworkApi()
const { settings: { disableanimations } = {} } = useAuth()
const classes = dashboardStyles({ disableanimations })
React.useEffect(() => {
getUsers()
getGroups()
getImages()
getVNetworks()
}, [])
const withoutAnimations = String(disableanimations).toUpperCase() === 'YES'
return (
<Container
disableGutters
className={clsx({ [classes.withoutAnimations]: withoutAnimations })}
>
<Box py={3}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Widgets.TotalSunstoneResources />
</Grid>
</Grid>
</Box>
</Container>
)
}
export default Dashboard

View File

@ -0,0 +1,9 @@
import { makeStyles } from '@material-ui/core'
export default makeStyles({
withoutAnimations: {
'& *, & *::before, & *::after': {
animation: 'none !important'
}
}
})

View File

@ -1,40 +0,0 @@
/* 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 * as React from 'react'
import { Box, Typography } from '@material-ui/core'
import dashboardStyles from 'client/containers/Dashboard/styles'
import { Tr } from 'client/components/HOC/Translate'
import { T } from 'client/constants'
function Dashboard () {
const classes = dashboardStyles()
return (
<Box>
<Typography
variant="h2"
className={classes.title}
data-cy="dashboard-title"
>
{Tr(T.Dashboard)}
</Typography>
</Box>
)
}
export default Dashboard

View File

@ -1,8 +0,0 @@
import { makeStyles } from '@material-ui/core'
export default makeStyles(theme => ({
root: {},
title: {
color: theme.palette.common.black
}
}))

View File

@ -0,0 +1,23 @@
import * as React from 'react'
import { styled, Container as MContainer, Box } from '@material-ui/core'
import * as Tables from 'client/components/Tables'
const Container = styled(MContainer)`
display: flex;
flexDirection: column;
height: 100%
`
function Datastores () {
return (
<Container disableGutters>
<Box py={2} overflow='auto'>
<Tables.DatastoresTable />
</Box>
</Container>
)
}
export default Datastores

View File

@ -0,0 +1,23 @@
import * as React from 'react'
import { styled, Container as MContainer, Box } from '@material-ui/core'
import * as Tables from 'client/components/Tables'
const Container = styled(MContainer)`
display: flex;
flexDirection: column;
height: 100%
`
function Groups () {
return (
<Container disableGutters>
<Box py={2} overflow='auto'>
<Tables.GroupsTable />
</Box>
</Container>
)
}
export default Groups

View File

@ -0,0 +1,23 @@
import * as React from 'react'
import { styled, Container as MContainer, Box } from '@material-ui/core'
import * as Tables from 'client/components/Tables'
const Container = styled(MContainer)`
display: flex;
flexDirection: column;
height: 100%
`
function Hosts () {
return (
<Container disableGutters>
<Box py={2} overflow='auto'>
<Tables.HostsTable />
</Box>
</Container>
)
}
export default Hosts

View File

@ -0,0 +1,23 @@
import * as React from 'react'
import { styled, Container as MContainer, Box } from '@material-ui/core'
import * as Tables from 'client/components/Tables'
const Container = styled(MContainer)`
display: flex;
flexDirection: column;
height: 100%
`
function Images () {
return (
<Container disableGutters>
<Box py={2} overflow='auto'>
<Tables.ImagesTable />
</Box>
</Container>
)
}
export default Images

View File

@ -0,0 +1,23 @@
import * as React from 'react'
import { styled, Container as MContainer, Box } from '@material-ui/core'
import * as Tables from 'client/components/Tables'
const Container = styled(MContainer)`
display: flex;
flexDirection: column;
height: 100%
`
function MarketplaceApps () {
return (
<Container disableGutters>
<Box py={2} overflow='auto'>
<Tables.MarketplaceAppsTable />
</Box>
</Container>
)
}
export default MarketplaceApps

View File

@ -0,0 +1,23 @@
import * as React from 'react'
import { styled, Container as MContainer, Box } from '@material-ui/core'
import * as Tables from 'client/components/Tables'
const Container = styled(MContainer)`
display: flex;
flexDirection: column;
height: 100%
`
function Marketplaces () {
return (
<Container disableGutters>
<Box py={2} overflow='auto'>
<Tables.MarketplacesTable />
</Box>
</Container>
)
}
export default Marketplaces

View File

@ -58,9 +58,9 @@ const Newstone = () => {
<Route exact path={TABS.hosts} component={Tables.HostsTable} />
<Route exact path={TABS.images} component={Tables.ImagesTable} />
<Route exact path={TABS.marketplaces} component={Tables.MarketplacesTable} />
<Route exact path={TABS.vms} component={Tables.VmsTable} />
<Route exact path={PATH.INSTANCES_VMS} component={Tables.VmsTable} />
<Route component={() => <Redirect to={TABS.vms} />} />
<Route component={() => <Redirect to={PATH.INSTANCES_VMS} />} />
</Switch>
</Box>
</Container>

View File

@ -0,0 +1,23 @@
import * as React from 'react'
import { styled, Container as MContainer, Box } from '@material-ui/core'
import * as Tables from 'client/components/Tables'
const Container = styled(MContainer)`
display: flex;
flexDirection: column;
height: 100%
`
function Users () {
return (
<Container disableGutters>
<Box py={2} overflow='auto'>
<Tables.UsersTable />
</Box>
</Container>
)
}
export default Users

View File

@ -0,0 +1,23 @@
import * as React from 'react'
import { styled, Container as MContainer, Box } from '@material-ui/core'
import * as Tables from 'client/components/Tables'
const Container = styled(MContainer)`
display: flex;
flexDirection: column;
height: 100%
`
function VirtualMachines () {
return (
<Container disableGutters>
<Box py={2} overflow='auto'>
<Tables.VmsTable />
</Box>
</Container>
)
}
export default VirtualMachines

View File

@ -0,0 +1,23 @@
import * as React from 'react'
import { styled, Container as MContainer, Box } from '@material-ui/core'
import * as Tables from 'client/components/Tables'
const Container = styled(MContainer)`
display: flex;
flexDirection: column;
height: 100%
`
function VmTemplates () {
return (
<Container disableGutters>
<Box py={2} overflow='auto'>
<Tables.VmTemplatesTable />
</Box>
</Container>
)
}
export default VmTemplates

View File

@ -15,7 +15,7 @@
import * as React from 'react'
import FlowApp from 'client/apps/flow'
import SunstoneApp from 'client/apps/sunstone'
import ProvisionApp from 'client/apps/provision'
import { isDevelopment, isBackend } from 'client/utils'
@ -39,7 +39,7 @@ const DevelopmentApp = props => {
return (
<>
{appName === _APPS.provision.name && <ProvisionApp {...props} />}
{appName === _APPS.sunstone.name && <FlowApp {...props} />}
{appName === _APPS.sunstone.name && <SunstoneApp {...props} />}
</>
)
}

View File

@ -1,9 +1,10 @@
import { Actions, Commands } from 'server/utils/constants/commands/template'
import { httpCodes } from 'server/utils/constants'
import { requestParams, RestClient } from 'client/utils'
import { poolRequest } from 'client/features/One/utils'
export const vmTemplateService = ({
getVNetwork: ({ filter, id }) => {
getVmTemplate: ({ filter, id }) => {
const name = Actions.TEMPLATE_INFO
const { url, options } = requestParams(
{ filter, id },
@ -15,5 +16,10 @@ export const vmTemplateService = ({
return res?.data?.VMTEMPLATE ?? {}
})
},
getVmTemplates: data => {
const name = Actions.TEMPLATE_POOL_INFO
const command = { name, ...Commands[name] }
return poolRequest(data, command, 'VMTEMPLATE')
}
})

View File

@ -69,7 +69,7 @@ const useFetch = (request, socket) => {
}, [])
const doFetch = useCallback(async (payload, reload = false) => {
dispatch({ type: ACTIONS.REQUEST, reload })
!cancelRequest.current && dispatch({ type: ACTIONS.REQUEST, reload })
try {
const response = await request(payload)

View File

@ -1,25 +1,13 @@
import loadable from '@loadable/component'
import {
Code as DevIcon,
ViewGrid as NewstoneIcon
} from 'iconoir-react'
import { Code as DevIcon } from 'iconoir-react'
const Newstone = loadable(() => import('client/containers/Newstone'), { ssr: false })
const TestApi = loadable(() => import('client/containers/TestApi'), { ssr: false })
export const PATH = {
NEWSTONE: '/newstone/:resource',
TEST_API: '/test-api'
}
export const ENDPOINTS = [
{
label: 'Newstone',
path: PATH.NEWSTONE,
sidebar: true,
icon: NewstoneIcon,
Component: Newstone
},
{
label: 'Test API',
path: PATH.TEST_API,

View File

@ -18,6 +18,7 @@ import PropTypes from 'prop-types'
import { Redirect, Route, Switch } from 'react-router-dom'
import { TransitionGroup } from 'react-transition-group'
import { LinearProgress } from '@material-ui/core'
import devRoutes from 'client/router/dev'
import commonRoutes from 'client/router/common'
@ -28,51 +29,67 @@ import Sidebar from 'client/components/Sidebar'
import Notifier from 'client/components/Notifier'
import { isDevelopment } from 'client/utils'
const Router = ({ routes }) => {
const Router = ({ isLogged, routes }) => {
const ENDPOINTS = React.useMemo(() => [
...routes.ENDPOINTS,
...(isDevelopment() ? devRoutes.ENDPOINTS : [])
], [])
const renderRoute = React.useCallback(
({ Component, ...rest }, index) => (
<ProtectedRoute key={index} exact {...rest}>
<InternalLayout>
<Component fallback={<LinearProgress color='secondary' />} />
</InternalLayout>
</ProtectedRoute>
), [])
return (
<TransitionGroup>
<Switch>
{ENDPOINTS?.map(({ Component, ...rest }, index, endpoints) =>
<ProtectedRoute key={index} exact {...rest}>
<Sidebar endpoints={endpoints} />
<Notifier />
<InternalLayout>
<>
{isLogged && (
<>
<Sidebar endpoints={ENDPOINTS} />
<Notifier />
</>
)}
<TransitionGroup>
<Switch>
{ENDPOINTS?.map(({ routes: subRoutes, ...rest }, index) =>
Array.isArray(subRoutes)
? subRoutes?.map(renderRoute)
: renderRoute(rest, index)
)}
{commonRoutes.ENDPOINTS?.map(({ Component, ...rest }, index) =>
<NoAuthRoute key={index} exact {...rest}>
<Component />
</InternalLayout>
</ProtectedRoute>
)}
{commonRoutes.ENDPOINTS?.map(({ Component, ...rest }, index) =>
<NoAuthRoute key={index} exact {...rest}>
<Component />
</NoAuthRoute>
)}
<Route component={() => <Redirect to={commonRoutes.PATH.LOGIN} />} />
</Switch>
</TransitionGroup>
</NoAuthRoute>
)}
<Route component={() => <Redirect to={commonRoutes.PATH.LOGIN} />} />
</Switch>
</TransitionGroup>
</>
)
}
Router.propTypes = {
isLogged: PropTypes.bool,
routes: PropTypes.shape({
PATH: PropTypes.object,
ENDPOINTS: PropTypes.arrayOf(
PropTypes.shape({
Component: PropTypes.object.isRequired,
Component: PropTypes.object,
icon: PropTypes.object,
label: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
sidebar: PropTypes.bool
path: PropTypes.string,
sidebar: PropTypes.bool,
routes: PropTypes.array
})
)
})
}
Router.defaultProps = {
isLogged: false,
routes: {
PATH: {},
ENDPOINTS: []

View File

@ -17,7 +17,7 @@ import * as React from 'react'
import { hydrate, render } from 'react-dom'
import { createStore } from 'client/store'
import App from 'client/apps/flow'
import App from 'client/apps/sunstone'
const { store } = createStore({ initState: window.REDUX_DATA })