Skip to content

Commit c3937cc

Browse files
committed
feat(fireedge): redesign Sunstone admin dashboard with operational widgets
Replace the 4 static count cards with a production-grade 4-row layout: - Row 1: Enhanced WavesCards with live subtitles (running VMs, monitored hosts, storage used/total capacity) - Row 2: Infrastructure utilization (aggregate CPU/Memory bars with color thresholds) + Storage capacity (per-type datastore bars) - Row 3: Host CPU and Memory monitoring graphs (UPlot 1h time-series) - Row 4: VM state distribution donut chart (Recharts) + Quick Actions navigation panel All widgets reuse existing RTK Query hooks with cache deduplication (5 unique API calls shared across widgets). No new npm dependencies. Respects DISABLE_ANIMATIONS setting, dark/light mode, responsive breakpoints, and per-resource access control gating. Signed-off-by: pablodelarco <pablodelarco1@gmail.com>
1 parent 8eae722 commit c3937cc

File tree

10 files changed

+1056
-122
lines changed

10 files changed

+1056
-122
lines changed

src/fireedge/src/modules/components/Cards/WavesCard.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,19 @@ const Wave = styled('span')(({ theme, bgcolor, duration = 1 }) => {
8989
})
9090

9191
const WavesCard = memo(
92-
({ text, value, bgColor, icon: Icon, onClick }) => (
92+
({ text, value, subtitle, bgColor, icon: Icon, onClick }) => (
9393
<Card title={Tr(text)} bgcolor={bgColor} onClick={onClick || undefined}>
9494
<Typography variant="h6" zIndex={2} noWrap>
9595
<Translate word={text} />
9696
</Typography>
9797
<Typography variant="h4" zIndex={2}>
9898
{value}
9999
</Typography>
100+
{subtitle && (
101+
<Typography variant="body2" zIndex={2} sx={{ opacity: 0.85 }}>
102+
{subtitle}
103+
</Typography>
104+
)}
100105
<Wave bgcolor={bgColor} duration={7} />
101106
<Wave bgcolor={bgColor} duration={5} />
102107
{Icon && (
@@ -106,7 +111,7 @@ const WavesCard = memo(
106111
)}
107112
</Card>
108113
),
109-
(prev, next) => prev.value === next.value
114+
(prev, next) => prev.value === next.value && prev.subtitle === next.subtitle
110115
)
111116

112117
WavesCard.propTypes = {
@@ -116,6 +121,7 @@ WavesCard.propTypes = {
116121
PropTypes.number,
117122
PropTypes.element,
118123
]),
124+
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
119125
bgColor: PropTypes.string,
120126
icon: PropTypes.any,
121127
onClick: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
@@ -124,6 +130,7 @@ WavesCard.propTypes = {
124130
WavesCard.defaultProps = {
125131
text: undefined,
126132
value: undefined,
133+
subtitle: undefined,
127134
bgColor: '#ffffff00',
128135
icon: undefined,
129136
onClick: undefined,

src/fireedge/src/modules/constants/translates.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2691,4 +2691,19 @@ module.exports = {
26912691
RunBackground: 'Run in background',
26922692

26932693
LearnMore: 'Learn more',
2694+
2695+
/* Admin Dashboard */
2696+
InfrastructureUtilization: 'Infrastructure Utilization',
2697+
StorageCapacity: 'Storage Capacity',
2698+
VmStateDistribution: 'VM State Distribution',
2699+
QuickActions: 'Quick Actions',
2700+
CreateVM: 'Create VM',
2701+
ViewHosts: 'View Hosts',
2702+
ViewDatastores: 'View Datastores',
2703+
NRunning: '%s running',
2704+
NMonitored: '%s monitored',
2705+
ImageDatastores: 'Image Datastores',
2706+
SystemDatastores: 'System Datastores',
2707+
FileDatastores: 'File Datastores',
2708+
NoDataAvailableYet: 'No data available yet',
26942709
}

src/fireedge/src/modules/containers/Dashboard/Sunstone/General.js

Lines changed: 62 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -13,58 +13,47 @@
1313
* See the License for the specific language governing permissions and *
1414
* limitations under the License. *
1515
* ------------------------------------------------------------------------- */
16-
import { Box, CircularProgress, Grid } from '@mui/material'
17-
import {
18-
BoxIso as ImageIcon,
19-
NetworkAlt as NetworkIcon,
20-
EmptyPage as TemplatesIcon,
21-
ModernTv as VmsIcons,
22-
} from 'iconoir-react'
16+
import { Box, Grid } from '@mui/material'
2317
import PropTypes from 'prop-types'
24-
import { ReactElement, memo, useEffect, useMemo } from 'react'
25-
import { useHistory } from 'react-router-dom'
18+
import { ReactElement, useEffect, useMemo } from 'react'
2619

27-
import {
28-
ImageAPI,
29-
VmAPI,
30-
VmTemplateAPI,
31-
VnAPI,
32-
useAuth,
33-
useGeneralApi,
34-
useViews,
35-
} from '@FeaturesModule'
20+
import { useAuth, useGeneralApi, useViews } from '@FeaturesModule'
21+
import { TranslateProvider } from '@ComponentsModule'
22+
import { RESOURCE_NAMES } from '@ConstantsModule'
23+
import { stringToBoolean } from '@ModelsModule'
3624

3725
import {
38-
NumberEasing,
39-
PATH,
40-
TranslateProvider,
41-
WavesCard,
42-
} from '@ComponentsModule'
43-
import { RESOURCE_NAMES, T, VM_POOL_PAGINATION_SIZE } from '@ConstantsModule'
44-
import { stringToBoolean } from '@ModelsModule'
26+
ResourceSummaryCards,
27+
InfrastructureUtilization,
28+
StorageCapacity,
29+
HostMonitoringGraphs,
30+
VmStateDistribution,
31+
QuickActions,
32+
} from './widgets'
4533

46-
const { VM, VM_TEMPLATE, IMAGE, VNET } = RESOURCE_NAMES
34+
const { HOST, DATASTORE } = RESOURCE_NAMES
4735

4836
/**
37+
* Sunstone admin dashboard with resource overview, utilization metrics,
38+
* monitoring graphs, state distribution, and quick actions.
39+
*
4940
* @param {object} props - Props
50-
* @param {object} props.view - View
41+
* @param {string} props.view - Current view name
5142
* @returns {ReactElement} Sunstone dashboard container
5243
*/
5344
export default function SunstoneDashboard({ view }) {
5445
const { settings: { FIREEDGE: fireedge = {} } = {} } = useAuth()
5546
const { DISABLE_ANIMATIONS } = fireedge
5647
const { hasAccessToResource } = useViews()
5748

58-
// Delete second title
5949
const { setSecondTitle } = useGeneralApi()
6050
useEffect(() => setSecondTitle({}), [])
6151

62-
const { push: goTo } = useHistory()
63-
64-
const vmAccess = useMemo(() => hasAccessToResource(VM), [view])
65-
const templateAccess = useMemo(() => hasAccessToResource(VM_TEMPLATE), [view])
66-
const imageAccess = useMemo(() => hasAccessToResource(IMAGE), [view])
67-
const vnetAccess = useMemo(() => hasAccessToResource(VNET), [view])
52+
const hostAccess = useMemo(() => hasAccessToResource(HOST), [view])
53+
const datastoreAccess = useMemo(
54+
() => hasAccessToResource(DATASTORE),
55+
[view]
56+
)
6857

6958
const styles = useMemo(() => {
7059
if (stringToBoolean(DISABLE_ANIMATIONS))
@@ -76,43 +65,45 @@ export default function SunstoneDashboard({ view }) {
7665
return (
7766
<TranslateProvider>
7867
<Box py={3} sx={styles}>
79-
<Grid
80-
container
81-
data-cy="dashboard-widget-total-sunstone-resources"
82-
spacing={3}
83-
>
84-
<ResourceWidget
85-
type="vms"
86-
bgColor="#fa7892"
87-
text={T.VMs}
88-
icon={VmsIcons}
89-
onClick={vmAccess && (() => goTo(PATH.INSTANCE.VMS.LIST))}
90-
disableAnimations={DISABLE_ANIMATIONS}
91-
/>
92-
<ResourceWidget
93-
type="vmtemples"
94-
bgColor="#b25aff"
95-
text={T.VMTemplates}
96-
icon={TemplatesIcon}
97-
onClick={templateAccess && (() => goTo(PATH.TEMPLATE.VMS.LIST))}
98-
disableAnimations={DISABLE_ANIMATIONS}
99-
/>
100-
<ResourceWidget
101-
type="images"
102-
bgColor="#1fbbc6"
103-
text={T.Images}
104-
icon={ImageIcon}
105-
onClick={imageAccess && (() => goTo(PATH.STORAGE.IMAGES.LIST))}
106-
disableAnimations={DISABLE_ANIMATIONS}
107-
/>
108-
<ResourceWidget
109-
type="vnets"
110-
bgColor="#f09d42"
111-
text={T.VirtualNetworks}
112-
icon={NetworkIcon}
113-
onClick={vnetAccess && (() => goTo(PATH.NETWORK.VNETS.LIST))}
114-
disableAnimations={DISABLE_ANIMATIONS}
115-
/>
68+
{/* Row 1: Enhanced Resource Summary Cards */}
69+
<ResourceSummaryCards
70+
disableAnimations={DISABLE_ANIMATIONS}
71+
view={view}
72+
/>
73+
74+
{/* Row 2: Infrastructure Utilization + Storage Capacity */}
75+
{(hostAccess || datastoreAccess) && (
76+
<Grid container spacing={3} sx={{ mt: 0 }}>
77+
{hostAccess && (
78+
<Grid item xs={12} md={6}>
79+
<InfrastructureUtilization view={view} />
80+
</Grid>
81+
)}
82+
{datastoreAccess && (
83+
<Grid item xs={12} md={6}>
84+
<StorageCapacity view={view} />
85+
</Grid>
86+
)}
87+
</Grid>
88+
)}
89+
90+
{/* Row 3: Host CPU + Memory Monitoring Graphs */}
91+
{hostAccess && (
92+
<Box sx={{ mt: 3 }}>
93+
<HostMonitoringGraphs />
94+
</Box>
95+
)}
96+
97+
{/* Row 4: VM State Distribution + Quick Actions */}
98+
<Grid container spacing={3} sx={{ mt: 0 }}>
99+
<Grid item xs={12} md={8}>
100+
<VmStateDistribution
101+
disableAnimations={stringToBoolean(DISABLE_ANIMATIONS)}
102+
/>
103+
</Grid>
104+
<Grid item xs={12} md={4}>
105+
<QuickActions view={view} />
106+
</Grid>
116107
</Grid>
117108
</Box>
118109
</TranslateProvider>
@@ -124,52 +115,3 @@ SunstoneDashboard.displayName = 'SunstoneDashboard'
124115
SunstoneDashboard.propTypes = {
125116
view: PropTypes.string,
126117
}
127-
128-
const ResourceWidget = memo(
129-
({ type = 'vms', onClick, text, bgColor, icon, disableAnimations }) => {
130-
const options = {
131-
vmtemples: VmTemplateAPI.useGetTemplatesQuery(undefined, {
132-
skip: type !== 'vmtemples',
133-
}),
134-
images: ImageAPI.useGetImagesQuery(undefined, {
135-
skip: type !== 'images',
136-
}),
137-
vnets: VnAPI.useGetVNetworksQuery(undefined, { skip: type !== 'vnets' }),
138-
vms: VmAPI.useGetVmsPaginatedQuery(
139-
{ extended: 0, pageSize: VM_POOL_PAGINATION_SIZE },
140-
{ skip: type !== 'vms' }
141-
),
142-
}
143-
144-
const { data = [], isFetching } = options[type] || {}
145-
146-
const NumberElement = useMemo(() => {
147-
if (stringToBoolean(disableAnimations)) return data?.length
148-
149-
return <NumberEasing value={data?.length} />
150-
}, [disableAnimations, data?.length])
151-
152-
return (
153-
<Grid item xs={12} sm={6} md={3}>
154-
<WavesCard
155-
bgColor={bgColor}
156-
icon={icon}
157-
text={text}
158-
value={isFetching ? <CircularProgress size={20} /> : NumberElement}
159-
onClick={onClick || undefined}
160-
/>
161-
</Grid>
162-
)
163-
}
164-
)
165-
166-
ResourceWidget.displayName = 'ResourceWidget'
167-
168-
ResourceWidget.propTypes = {
169-
type: PropTypes.string,
170-
onClick: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
171-
text: PropTypes.string,
172-
bgColor: PropTypes.string,
173-
icon: PropTypes.any,
174-
disableAnimations: PropTypes.string,
175-
}

0 commit comments

Comments
 (0)