Skip to content

Commit 5ecef95

Browse files
[IAC-5075]: resource drill down v2 (#172)
* feat: [IAC-5066]: add data source tab * feat: [IAC-5075]: side drawer implementation to show resource details * feat: [IAC-5075]: update default page size to 10 * feat: [IAC-5075]: fix ling issues * feat: [IAC-5075]: fix ling issues * feat: [IAC-5075]: prettier issues fixes * feat: [IAC-5075]: fix font issues * feat: [IAC-5075]: fix font issues * feat: [IAC-5075]: dark theme support * feat: [IAC-5075]: dark theme color issue fix * feat: [IAC-5075]: fix prettier issue * feat: [IAC-5075]: version update
1 parent f246b84 commit 5ecef95

File tree

19 files changed

+995
-72
lines changed

19 files changed

+995
-72
lines changed

plugins/harness-iacm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@harnessio/backstage-plugin-harness-iacm",
3-
"version": "0.3.0",
3+
"version": "0.4.0",
44
"main": "src/index.ts",
55
"types": "src/index.ts",
66
"license": "Apache-2.0",
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React from 'react';
2+
import {
3+
CircularProgress,
4+
Typography,
5+
Box,
6+
makeStyles,
7+
} from '@material-ui/core';
8+
import { AsyncStatus } from '../types';
9+
import { ClassNameMap } from '@material-ui/core/styles/withStyles';
10+
11+
const useStyles = makeStyles(theme => ({
12+
emptyContainer: {
13+
display: 'flex',
14+
flexDirection: 'column',
15+
alignItems: 'center',
16+
justifyContent: 'center',
17+
padding: theme.spacing(8, 2),
18+
minHeight: 200,
19+
},
20+
emptyText: {
21+
color: theme.palette.text.secondary,
22+
marginTop: theme.spacing(2),
23+
},
24+
loadingContainer: {
25+
display: 'flex',
26+
justifyContent: 'center',
27+
alignItems: 'center',
28+
padding: theme.spacing(4),
29+
minHeight: 200,
30+
},
31+
}));
32+
33+
interface TableEmptyStateProps {
34+
status?: AsyncStatus;
35+
hasData: boolean;
36+
classes: ClassNameMap<'empty'>;
37+
}
38+
39+
const TableEmptyState: React.FC<TableEmptyStateProps> = ({
40+
status,
41+
hasData,
42+
}) => {
43+
const localClasses = useStyles();
44+
const isLoading =
45+
status === AsyncStatus.Init || status === AsyncStatus.Loading;
46+
const isEmpty = status === AsyncStatus.Success && !hasData;
47+
48+
if (isLoading) {
49+
return (
50+
<div className={localClasses.loadingContainer}>
51+
<CircularProgress />
52+
</div>
53+
);
54+
}
55+
56+
if (isEmpty) {
57+
return (
58+
<Box className={localClasses.emptyContainer}>
59+
<Typography variant="h6" color="textSecondary">
60+
No data available
61+
</Typography>
62+
<Typography variant="body2" className={localClasses.emptyText}>
63+
There are no items to display in this table.
64+
</Typography>
65+
</Box>
66+
);
67+
}
68+
69+
return null;
70+
};
71+
72+
export default TableEmptyState;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import React from 'react';
2+
import { Typography, Box } from '@material-ui/core';
3+
import DeleteIcon from '@material-ui/icons/Delete';
4+
import { useStyles } from './styles';
5+
import { getDriftIcon } from './DriftIcon';
6+
import ValueDisplay from './ValueDisplay';
7+
import { AttributeListProps } from './types';
8+
9+
const AttributeList: React.FC<AttributeListProps> = ({
10+
attributes,
11+
driftStatus,
12+
isDeleted,
13+
allDeleted,
14+
}) => {
15+
const classes = useStyles();
16+
17+
if (attributes.length === 0) {
18+
return (
19+
<Typography variant="body2" color="textSecondary">
20+
No attributes found
21+
</Typography>
22+
);
23+
}
24+
25+
return (
26+
<>
27+
{attributes.map((item, index) => {
28+
const hasDrift = item.hasDrift;
29+
const driftValue = item.driftValue;
30+
const attributeIcon =
31+
hasDrift && driftStatus ? getDriftIcon(driftStatus) : null;
32+
33+
return (
34+
<Box
35+
key={`${item.key}-${index}`}
36+
className={
37+
hasDrift
38+
? `${classes.attributeRow} ${classes.attributeRowDrift}`
39+
: classes.attributeRow
40+
}
41+
>
42+
{/* Key/Label */}
43+
<Typography component="div" className={classes.attributeKey}>
44+
{attributeIcon && (
45+
<span className={classes.iconContainerInline}>
46+
{attributeIcon}
47+
</span>
48+
)}
49+
{item.key}
50+
{(isDeleted || (allDeleted && hasDrift)) && (
51+
<span className={classes.deletedBadge}>
52+
<DeleteIcon style={{ fontSize: 12 }} />
53+
DELETED
54+
</span>
55+
)}
56+
</Typography>
57+
58+
{/* Value with Copy Button */}
59+
{hasDrift && driftValue ? (
60+
<Box className={classes.valueComparison}>
61+
<ValueDisplay
62+
value={driftValue}
63+
isDrift
64+
label="Actual Value:"
65+
copyTopOffset="25px"
66+
/>
67+
<ValueDisplay
68+
value={item.value}
69+
isDrift
70+
label="Expected Value:"
71+
copyTopOffset="25px"
72+
/>
73+
</Box>
74+
) : (
75+
<ValueDisplay value={item.value} />
76+
)}
77+
</Box>
78+
);
79+
})}
80+
</>
81+
);
82+
};
83+
84+
export default AttributeList;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import ReplayIcon from '@material-ui/icons/Replay';
3+
import DeleteIcon from '@material-ui/icons/Delete';
4+
import LensIcon from '@material-ui/icons/Lens';
5+
6+
export const getDriftIcon = (driftStatus?: string): React.ReactNode => {
7+
switch (driftStatus?.toLowerCase()) {
8+
case 'drifted':
9+
return <ReplayIcon style={{ fontSize: 18, color: '#ff9800' }} />;
10+
case 'changed':
11+
return <ReplayIcon style={{ fontSize: 18, color: '#ff9800' }} />;
12+
case 'deleted':
13+
return <DeleteIcon style={{ fontSize: 18, color: '#9e9e9e' }} />;
14+
case 'unchanged':
15+
return <LensIcon style={{ fontSize: 18, color: '#9e9e9e' }} />;
16+
default:
17+
return null;
18+
}
19+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import { Box, Typography, IconButton } from '@material-ui/core';
3+
import CloseIcon from '@material-ui/icons/Close';
4+
import { useStyles } from './styles';
5+
6+
interface HeaderProps {
7+
title: string;
8+
icon?: React.ReactNode;
9+
onClose: () => void;
10+
}
11+
12+
const Header: React.FC<HeaderProps> = ({ title, icon, onClose }) => {
13+
const classes = useStyles();
14+
15+
return (
16+
<Box className={classes.drawerHeader}>
17+
<Box display="flex" alignItems="center">
18+
{icon && <Box className={classes.iconContainer}>{icon}</Box>}
19+
<Typography variant="h6" className={classes.drawerTitle}>
20+
{title}
21+
</Typography>
22+
</Box>
23+
<IconButton
24+
onClick={onClose}
25+
aria-label="close drawer"
26+
edge="end"
27+
style={{ padding: 0 }}
28+
>
29+
<CloseIcon />
30+
</IconButton>
31+
</Box>
32+
);
33+
};
34+
35+
export default Header;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
import { TextField, InputAdornment, Box, IconButton } from '@material-ui/core';
3+
import SearchIcon from '@material-ui/icons/Search';
4+
import CloseIcon from '@material-ui/icons/Close';
5+
import { useStyles } from './styles';
6+
7+
interface SearchFieldProps {
8+
value: string;
9+
onChange: (value: string) => void;
10+
sticky?: boolean;
11+
}
12+
13+
const SearchField: React.FC<SearchFieldProps> = ({ value, onChange }) => {
14+
const classes = useStyles();
15+
16+
const handleClear = () => {
17+
onChange('');
18+
};
19+
20+
return (
21+
<Box className={classes.searchFieldSticky}>
22+
<TextField
23+
placeholder="Search"
24+
variant="outlined"
25+
size="small"
26+
fullWidth
27+
value={value}
28+
onChange={e => onChange(e.target.value)}
29+
InputProps={{
30+
startAdornment: (
31+
<InputAdornment position="start">
32+
<SearchIcon fontSize="small" />
33+
</InputAdornment>
34+
),
35+
endAdornment: value && (
36+
<InputAdornment position="end">
37+
<IconButton
38+
size="small"
39+
onClick={handleClear}
40+
aria-label="clear search"
41+
edge="end"
42+
>
43+
<CloseIcon fontSize="small" />
44+
</IconButton>
45+
</InputAdornment>
46+
),
47+
}}
48+
/>
49+
</Box>
50+
);
51+
};
52+
53+
export default SearchField;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
import { Box, Typography, Divider } from '@material-ui/core';
3+
import { useStyles } from './styles';
4+
5+
interface SubHeaderProps {
6+
name?: string;
7+
provider?: string;
8+
module?: string;
9+
}
10+
11+
const SubHeader: React.FC<SubHeaderProps> = ({ name, provider, module }) => {
12+
const classes = useStyles();
13+
14+
if (!name && !provider && !module) return null;
15+
16+
return (
17+
<Box className={classes.drawerSubHeader}>
18+
<Box className={classes.subHeaderItem}>
19+
<Typography className={classes.subHeaderLabel}>Name:</Typography>
20+
<Typography className={classes.subHeaderValue}>
21+
{name || '-'}
22+
</Typography>
23+
</Box>
24+
<Divider
25+
orientation="vertical"
26+
className={classes.subHeaderDivider}
27+
flexItem
28+
/>
29+
<Box className={classes.subHeaderItem}>
30+
<Typography className={classes.subHeaderLabel}>Provider:</Typography>
31+
<Typography className={classes.subHeaderValue}>
32+
{provider || '-'}
33+
</Typography>
34+
</Box>
35+
<Divider
36+
orientation="vertical"
37+
className={classes.subHeaderDivider}
38+
flexItem
39+
/>
40+
<Box className={classes.subHeaderItem}>
41+
<Typography className={classes.subHeaderLabel}>Module:</Typography>
42+
<Typography className={classes.subHeaderValue}>
43+
{module || '-'}
44+
</Typography>
45+
</Box>
46+
</Box>
47+
);
48+
};
49+
50+
export default SubHeader;

0 commit comments

Comments
 (0)