feat: Implemented mobile responsiveness across app
Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
parent
1c5efb6e40
commit
1bfee961f5
@ -72,3 +72,9 @@
|
||||
-webkit-animation-name: bounce;
|
||||
animation-name: bounce;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.hide-on-mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
10
src/__mocks__/MockThemeProvider.jsx
Normal file
10
src/__mocks__/MockThemeProvider.jsx
Normal file
@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
|
||||
const theme = createTheme();
|
||||
|
||||
function MockThemeProvier({ children }) {
|
||||
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
|
||||
}
|
||||
|
||||
export default MockThemeProvier;
|
@ -6,6 +6,7 @@ import React from 'react';
|
||||
import { createSearchParams, MemoryRouter } from 'react-router-dom';
|
||||
import filterConstants from 'utilities/filterConstants.js';
|
||||
import { sortByCriteria } from 'utilities/sortCriteria.js';
|
||||
import MockThemeProvier from '__mocks__/MockThemeProvider';
|
||||
|
||||
// router mock
|
||||
const mockedUsedNavigate = jest.fn();
|
||||
@ -17,9 +18,11 @@ jest.mock('react-router-dom', () => ({
|
||||
const StateExploreWrapper = (props) => {
|
||||
const queryString = props.search || '';
|
||||
return (
|
||||
<MemoryRouter initialEntries={[`/explore?${queryString.toString()}`]}>
|
||||
<Explore />
|
||||
</MemoryRouter>
|
||||
<MockThemeProvier>
|
||||
<MemoryRouter initialEntries={[`/explore?${queryString.toString()}`]}>
|
||||
<Explore />
|
||||
</MemoryRouter>
|
||||
</MockThemeProvier>
|
||||
);
|
||||
};
|
||||
const mockImageList = {
|
||||
|
@ -3,6 +3,15 @@ import RepoDetails from 'components/RepoDetails';
|
||||
import React from 'react';
|
||||
import { api } from 'api';
|
||||
import { createSearchParams } from 'react-router-dom';
|
||||
import MockThemeProvier from '__mocks__/MockThemeProvider';
|
||||
|
||||
const RepoDetailsThemeWrapper = () => {
|
||||
return (
|
||||
<MockThemeProvier>
|
||||
<RepoDetails />
|
||||
</MockThemeProvier>
|
||||
);
|
||||
};
|
||||
|
||||
// uselocation mock
|
||||
const mockUseLocationValue = {
|
||||
@ -190,44 +199,44 @@ afterEach(() => {
|
||||
describe('Repo details component', () => {
|
||||
it('fetches repo detailed data and renders', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
expect(await screen.findByText('test')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders vulnerability icons', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
expect(await screen.findAllByTestId('critical-vulnerability-icon')).toHaveLength(1);
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsNone } });
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
expect(await screen.findAllByTestId('none-vulnerability-icon')).toHaveLength(1);
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsUnknown } });
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
expect(await screen.findAllByTestId('unknown-vulnerability-icon')).toHaveLength(1);
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsFailed } });
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
expect(await screen.findAllByTestId('failed-vulnerability-icon')).toHaveLength(1);
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsLow } });
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
expect(await screen.findAllByTestId('low-vulnerability-icon')).toHaveLength(1);
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsMedium } });
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
expect(await screen.findAllByTestId('medium-vulnerability-icon')).toHaveLength(1);
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsHigh } });
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
expect(await screen.findAllByTestId('high-vulnerability-icon')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should log error if data can't be fetched", async () => {
|
||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
|
||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
||||
});
|
||||
|
||||
it('should switch between tabs', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
expect(await screen.findByTestId('overview-container')).toBeInTheDocument();
|
||||
fireEvent.click(await screen.findByText(/tags/i));
|
||||
expect(await screen.findByTestId('tags-container')).toBeInTheDocument();
|
||||
@ -236,7 +245,7 @@ describe('Repo details component', () => {
|
||||
|
||||
it('should render platform chips and they should redirect to explore page', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
|
||||
render(<RepoDetails />);
|
||||
render(<RepoDetailsThemeWrapper />);
|
||||
const osChip = await screen.findByText(/linux/i);
|
||||
fireEvent.click(osChip);
|
||||
expect(mockUseNavigate).toHaveBeenCalledWith({
|
||||
|
@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
|
||||
import RepoPage from 'pages/RepoPage';
|
||||
import React from 'react';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
import MockThemeProvier from '__mocks__/MockThemeProvider';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
@ -28,7 +29,14 @@ it('renders the repository page component', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="*" element={<RepoPage />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<MockThemeProvier>
|
||||
<RepoPage />
|
||||
</MockThemeProvier>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
@ -1,8 +1,17 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { api } from 'api';
|
||||
import TagDetails from 'components/TagDetails';
|
||||
import React from 'react';
|
||||
import MockThemeProvier from '__mocks__/MockThemeProvider';
|
||||
|
||||
const TagDetailsThemeWrapper = () => {
|
||||
return (
|
||||
<MockThemeProvier>
|
||||
<TagDetails />
|
||||
</MockThemeProvier>
|
||||
);
|
||||
};
|
||||
|
||||
const mockImage = {
|
||||
Image: {
|
||||
@ -222,7 +231,7 @@ afterEach(() => {
|
||||
describe('Tags details', () => {
|
||||
it('should show tabs and allow nagivation between them', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
const dependenciesTab = await screen.findByTestId('dependencies-tab');
|
||||
fireEvent.click(dependenciesTab);
|
||||
expect(await screen.findByTestId('depends-on-container')).toBeInTheDocument();
|
||||
@ -232,52 +241,49 @@ describe('Tags details', () => {
|
||||
it("should log an error when data can't be fetched", async () => {
|
||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
|
||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
||||
});
|
||||
|
||||
it('should show tag details metadata', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
expect(await screen.findByTestId('tagDetailsMetadata-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders vulnerability icons', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
expect(await screen.findByTestId('critical-vulnerability-icon')).toBeInTheDocument();
|
||||
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageNone } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
expect(await screen.findByTestId('none-vulnerability-icon')).toBeInTheDocument();
|
||||
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageUnknown } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
expect(await screen.findByTestId('unknown-vulnerability-icon')).toBeInTheDocument();
|
||||
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageFailed } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
expect(await screen.findByTestId('failed-vulnerability-icon')).toBeInTheDocument();
|
||||
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageLow } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
expect(await screen.findByTestId('low-vulnerability-icon')).toBeInTheDocument();
|
||||
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageMedium } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
expect(await screen.findByTestId('medium-vulnerability-icon')).toBeInTheDocument();
|
||||
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageHigh } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
expect(await screen.findByTestId('high-vulnerability-icon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should copy the docker pull string to clipboard', async () => {
|
||||
jest
|
||||
.spyOn(api, 'get')
|
||||
|
||||
.mockResolvedValue({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetails />);
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
const dropdown = await screen.findByText('Pull Image');
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
userEvent.click(dropdown);
|
||||
@ -287,11 +293,8 @@ describe('Tags details', () => {
|
||||
});
|
||||
|
||||
it('should copy the podman pull string to clipboard', async () => {
|
||||
jest
|
||||
.spyOn(api, 'get')
|
||||
|
||||
.mockResolvedValue({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetails />);
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
const dropdown = await screen.findByText('Pull Image');
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
userEvent.click(dropdown);
|
||||
@ -307,7 +310,7 @@ describe('Tags details', () => {
|
||||
.spyOn(api, 'get')
|
||||
|
||||
.mockResolvedValue({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
const dropdown = await screen.findByText('Pull Image');
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
userEvent.click(dropdown);
|
||||
@ -320,7 +323,7 @@ describe('Tags details', () => {
|
||||
|
||||
it('should show pull tabs in dropdown and allow nagivation between them', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
const dropdown = await screen.findByText('Pull Image');
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
userEvent.click(dropdown);
|
||||
@ -333,7 +336,7 @@ describe('Tags details', () => {
|
||||
|
||||
it('should show the copied successfully button for 3 seconds', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetails />);
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
const dropdown = await screen.findByText('Pull Image');
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
userEvent.click(dropdown);
|
||||
|
@ -16,6 +16,14 @@ jest.mock(
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
'components/ExploreHeader',
|
||||
() =>
|
||||
function ExploreHeader() {
|
||||
return <div />;
|
||||
}
|
||||
);
|
||||
|
||||
it('renders the tags page component', async () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
|
BIN
src/assets/zotLogoSmall.png
Normal file
BIN
src/assets/zotLogoSmall.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -7,7 +7,7 @@ import Loading from './Loading';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Sticky from 'react-sticky-el';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import { Container, FormControl, Grid, InputLabel, MenuItem, Select, Stack } from '@mui/material';
|
||||
import { Container, FormControl, Grid, InputLabel, MenuItem, Select, Stack, Button } from '@mui/material';
|
||||
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
|
||||
@ -21,8 +21,9 @@ import { isEmpty } from 'lodash';
|
||||
import filterConstants from 'utilities/filterConstants.js';
|
||||
import { sortByCriteria } from 'utilities/sortCriteria.js';
|
||||
import { EXPLORE_PAGE_SIZE } from 'utilities/paginationConstants.js';
|
||||
import FilterDialog from './FilterDialog.jsx';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
gridWrapper: {
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem'
|
||||
@ -52,6 +53,13 @@ const useStyles = makeStyles(() => ({
|
||||
borderRadius: '0.375em',
|
||||
width: '25%',
|
||||
textAlign: 'left'
|
||||
},
|
||||
filterButton: {
|
||||
borderRadius: '0.4rem',
|
||||
marginBottom: '1rem',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
display: 'none'
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@ -73,6 +81,9 @@ function Explore() {
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
const classes = useStyles();
|
||||
|
||||
// Filterdialog props
|
||||
const [filterDialogOpen, setFilterDialogOpen] = useState(false);
|
||||
|
||||
const buildFilterQuery = () => {
|
||||
let filter = {};
|
||||
// workaround until backend bugfix
|
||||
@ -194,6 +205,10 @@ function Explore() {
|
||||
setSortFilter(event.target.value);
|
||||
};
|
||||
|
||||
const handleFilterDialogOpen = () => {
|
||||
setFilterDialogOpen(true);
|
||||
};
|
||||
|
||||
const renderRepoCards = () => {
|
||||
return (
|
||||
exploreData &&
|
||||
@ -250,7 +265,7 @@ function Explore() {
|
||||
|
||||
const renderListBottom = () => {
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
return filterDialogOpen ? <div /> : <Loading />;
|
||||
}
|
||||
if (!isLoading && !isEndOfList) {
|
||||
return <div ref={listBottom} />;
|
||||
@ -262,16 +277,21 @@ function Explore() {
|
||||
<Container maxWidth="lg">
|
||||
<Grid container className={classes.gridWrapper}>
|
||||
<Grid container item xs={12}>
|
||||
<Grid item xs={3}></Grid>
|
||||
<Grid item xs={9}>
|
||||
<Grid item xs={3} className="hide-on-mobile"></Grid>
|
||||
<Grid item xs={12} md={9}>
|
||||
<Stack direction="row" className={classes.resultsRow}>
|
||||
<Typography variant="body2" className={classes.results}>
|
||||
<Typography variant="body2" className={`${classes.results} hide-on-mobile`}>
|
||||
Showing {exploreData?.length} results out of {totalItems}
|
||||
</Typography>
|
||||
{!isLoading && (
|
||||
<Button variant="contained" onClick={handleFilterDialogOpen} className={`${classes.filterButton}`}>
|
||||
Filter results
|
||||
</Button>
|
||||
)}
|
||||
<FormControl
|
||||
sx={{ m: '1', minWidth: '4.6875rem' }}
|
||||
sx={{ minWidth: '4.6875rem' }}
|
||||
disabled={isLoading}
|
||||
className={classes.sortForm}
|
||||
className={`${classes.sortForm} hide-on-mobile`}
|
||||
size="small"
|
||||
>
|
||||
<InputLabel>Sort</InputLabel>
|
||||
@ -292,20 +312,20 @@ function Explore() {
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container item xs={12} spacing={5} pt={1}>
|
||||
<Grid item xs={3}>
|
||||
<Grid item xs={3} md={3} className="hide-on-mobile">
|
||||
<Sticky>{renderFilterCards()}</Sticky>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Grid item xs={12} md={9}>
|
||||
{!(exploreData && exploreData.length) && !isLoading ? (
|
||||
<Grid container className={classes.nodataWrapper}>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Alert style={{ marginTop: 10, width: '100%' }} variant="outlined" severity="warning">
|
||||
<Alert style={{ marginTop: 10 }} variant="outlined" severity="warning">
|
||||
Looks like we don't have anything matching that search. Try searching something else.
|
||||
</Alert>
|
||||
</div>
|
||||
</Grid>
|
||||
) : (
|
||||
<Stack direction="column" spacing={2}>
|
||||
<Stack direction="column" spacing={{ xs: 4, md: 2 }}>
|
||||
{renderRepoCards()}
|
||||
{renderListBottom()}
|
||||
</Stack>
|
||||
@ -313,6 +333,13 @@ function Explore() {
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<FilterDialog
|
||||
open={filterDialogOpen}
|
||||
setOpen={setFilterDialogOpen}
|
||||
sortValue={sortFilter}
|
||||
setSortValue={setSortFilter}
|
||||
renderFilterCards={renderFilterCards}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import React from 'react';
|
||||
|
||||
const useStyles = makeStyles(() => {
|
||||
const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
exploreHeader: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
@ -20,12 +20,18 @@ const useStyles = makeStyles(() => {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '2rem'
|
||||
padding: '2rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '1rem'
|
||||
}
|
||||
},
|
||||
explore: {
|
||||
color: '#52637A',
|
||||
fontSize: '1rem',
|
||||
letterSpacing: '0.009375rem'
|
||||
letterSpacing: '0.009375rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
fontSize: '0.8rem'
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -42,7 +48,10 @@ function ExploreHeader() {
|
||||
|
||||
return (
|
||||
<div className={classes.exploreHeader}>
|
||||
<ArrowBackIcon sx={{ color: '#14191F', fontSize: '2rem', cursor: 'pointer' }} onClick={() => navigate(-1)} />
|
||||
<ArrowBackIcon
|
||||
sx={{ color: '#14191F', fontSize: { xs: '1.5rem', md: '2rem' }, cursor: 'pointer' }}
|
||||
onClick={() => navigate(-1)}
|
||||
/>
|
||||
<Breadcrumbs separator="/" aria-label="breadcrumb">
|
||||
<Link to="/">
|
||||
<Typography variant="body1" className={classes.explore}>
|
||||
|
66
src/components/FilterDialog.jsx
Normal file
66
src/components/FilterDialog.jsx
Normal file
@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
FormControl,
|
||||
Select,
|
||||
MenuItem,
|
||||
DialogActions,
|
||||
Button
|
||||
} from '@mui/material';
|
||||
import { sortByCriteria } from 'utilities/sortCriteria.js';
|
||||
|
||||
const useStyles = makeStyles(() => ({}));
|
||||
|
||||
function FilterDialog(props) {
|
||||
const {
|
||||
open,
|
||||
setOpen,
|
||||
sortValue,
|
||||
setSortValue,
|
||||
renderFilterCards
|
||||
// imageFilters,
|
||||
// setImageFilters,
|
||||
// osFilters,
|
||||
// setOsFilters,
|
||||
// archFilters,
|
||||
// setArchFilters
|
||||
} = props;
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
const handleSortChange = (event) => {
|
||||
setSortValue(event.target.value);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} fullScreen>
|
||||
<DialogTitle>Filter</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>Sort results</DialogContentText>
|
||||
<FormControl sx={{ m: '1', width: '80%' }} className={`${classes.sortForm}`} size="small">
|
||||
<Select label="Sort" value={sortValue} onChange={handleSortChange} MenuProps={{ disableScrollLock: true }}>
|
||||
{Object.values(sortByCriteria).map((el) => (
|
||||
<MenuItem key={el.value} value={el.value}>
|
||||
{el.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{renderFilterCards()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Confirm</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterDialog;
|
@ -3,25 +3,13 @@ import React from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
|
||||
// components
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Popper,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
ClickAwayListener,
|
||||
Paper,
|
||||
Grow,
|
||||
Stack,
|
||||
Grid
|
||||
//IconButton
|
||||
} from '@mui/material';
|
||||
import { AppBar, Toolbar, Stack, Grid } from '@mui/material';
|
||||
|
||||
// styling
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import logo from '../assets/zotLogo.svg';
|
||||
//import placeholderProfileButton from '../assets/Profile_button_placeholder.svg';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import logoxs from '../assets/zotLogoSmall.png';
|
||||
import { useState, useEffect } from 'react';
|
||||
import SearchSuggestion from './SearchSuggestion';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
@ -39,13 +27,16 @@ const useStyles = makeStyles(() => ({
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingLeft: 0,
|
||||
padding: 0,
|
||||
backgroundColor: '#fff',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
borderBottom: '0.0625rem solid #BDBDBD',
|
||||
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)'
|
||||
},
|
||||
headerContainer: {
|
||||
minWidth: '60%'
|
||||
},
|
||||
searchIcon: {
|
||||
color: '#52637A',
|
||||
paddingRight: '3%'
|
||||
@ -66,7 +57,8 @@ const useStyles = makeStyles(() => ({
|
||||
},
|
||||
logoWrapper: {},
|
||||
logo: {
|
||||
width: '130px'
|
||||
maxWidth: '130px',
|
||||
maxHeight: '50px'
|
||||
},
|
||||
userAvatar: {
|
||||
height: 46,
|
||||
@ -78,7 +70,8 @@ const useStyles = makeStyles(() => ({
|
||||
grid: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}
|
||||
}));
|
||||
|
||||
@ -119,73 +112,32 @@ function Header() {
|
||||
const path = useLocation().pathname;
|
||||
// const navigate = useNavigate();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const anchorRef = useRef(null);
|
||||
|
||||
const handleToggle = () => {
|
||||
setOpen((prevOpen) => !prevOpen);
|
||||
};
|
||||
|
||||
const handleClose = (event) => {
|
||||
localStorage.removeItem('token');
|
||||
window.location.reload();
|
||||
if (anchorRef.current && anchorRef.current.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
return (
|
||||
<AppBar position={show ? 'fixed' : 'absolute'} sx={{ height: '10vh' }}>
|
||||
<Toolbar className={classes.header}>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ minWidth: '60%' }}>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" className={classes.headerContainer}>
|
||||
<Grid container className={classes.grid}>
|
||||
<Grid item xs={2} sx={{ display: 'flex', justifyContent: 'start' }}>
|
||||
<Link to="/home" className={classes.grid}>
|
||||
<img alt="zot" src={logo} className={classes.logo} />
|
||||
{/* <img
|
||||
alt="zot"
|
||||
src={logo}
|
||||
srcSet={`${logoxs} 192w, ${logo} 489w`}
|
||||
sizes="(max-width: 480px) 192px, 489px"
|
||||
className={classes.logo}
|
||||
/> */}
|
||||
<picture>
|
||||
<source media="(min-width:600px)" srcSet={logo} />
|
||||
<img alt="zot" src={logoxs} className={classes.logo} />
|
||||
</picture>
|
||||
</Link>
|
||||
</Grid>
|
||||
<Grid item xs={8}>
|
||||
{path !== '/' && <SearchSuggestion />}
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<Grid item md={2} xs={0}>
|
||||
<div>{''}</div>
|
||||
</Grid>
|
||||
{/* <IconButton
|
||||
ref={anchorRef}
|
||||
id="composition-button"
|
||||
aria-controls={open ? 'composition-menu' : undefined}
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
aria-haspopup="true"
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<Avatar alt="profile" src={placeholderProfileButton} className={classes.userAvatar} variant="rounded" />
|
||||
</IconButton> */}
|
||||
<Popper
|
||||
open={open}
|
||||
anchorEl={anchorRef.current}
|
||||
role={undefined}
|
||||
placement="bottom-start"
|
||||
transition
|
||||
disablePortal
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin: placement === 'bottom-start' ? 'left top' : 'left bottom'
|
||||
}}
|
||||
>
|
||||
<Paper>
|
||||
<ClickAwayListener onClickAway={handleToggle}>
|
||||
<MenuList autoFocusItem={open} id="composition-menu" aria-labelledby="composition-button">
|
||||
<MenuItem onClick={handleClose}>Logout</MenuItem>
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
|
@ -37,15 +37,6 @@ const useStyles = makeStyles(() => ({
|
||||
textAlign: 'center',
|
||||
letterSpacing: '-0.02rem'
|
||||
},
|
||||
titleRed: {
|
||||
fontWeight: '700',
|
||||
color: '#D83C0E',
|
||||
width: '100%',
|
||||
display: 'inline',
|
||||
fontSize: '2.5rem',
|
||||
textAlign: 'center',
|
||||
letterSpacing: '-0.02rem'
|
||||
},
|
||||
sectionTitle: {
|
||||
fontWeight: '700',
|
||||
color: '#000000DE',
|
||||
@ -63,8 +54,7 @@ const useStyles = makeStyles(() => ({
|
||||
viewAll: {
|
||||
color: '#00000099',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'right',
|
||||
width: '100%'
|
||||
textAlign: 'left'
|
||||
}
|
||||
}));
|
||||
|
||||
@ -128,29 +118,6 @@ function Home() {
|
||||
);
|
||||
};
|
||||
|
||||
// const renderBookmarks = () => {
|
||||
// return (
|
||||
// homeData &&
|
||||
// homeData.slice(0, 2).map((item, index) => {
|
||||
// return (
|
||||
// <RepoCard
|
||||
// name={item.name}
|
||||
// version={item.latestVersion}
|
||||
// description={item.description}
|
||||
// tags={item.tags}
|
||||
// vendor={item.vendor}
|
||||
// platforms={item.platforms}
|
||||
// size={item.size}
|
||||
// licenses={item.licenses}
|
||||
// key={index}
|
||||
// data={item}
|
||||
// lastUpdated={item.lastUpdated}
|
||||
// />
|
||||
// );
|
||||
// })
|
||||
// );
|
||||
// };
|
||||
|
||||
const renderRecentlyUpdated = () => {
|
||||
return (
|
||||
homeData &&
|
||||
@ -185,31 +152,25 @@ function Home() {
|
||||
<Stack spacing={4} alignItems="center" className={classes.gridWrapper}>
|
||||
<Stack
|
||||
justifyContent="space-between"
|
||||
alignItems="end"
|
||||
direction="row"
|
||||
alignItems={{ xs: 'flex-start', md: 'flex-end' }}
|
||||
direction={{ xs: 'column', md: 'row' }}
|
||||
sx={{ width: '100%', paddingTop: '3rem' }}
|
||||
>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Most popular images
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className={classes.viewAll}
|
||||
onClick={() => handleClickViewAll(sortByCriteria.downloads.value)}
|
||||
>
|
||||
View all
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Most popular images
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.viewAll} onClick={() => handleClickViewAll(sortByCriteria.downloads.value)}>
|
||||
<Typography variant="body2">View all</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
{renderMostPopular()}
|
||||
{/* currently most popular will be by downloads until stars are implemented */}
|
||||
{/* <Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Bookmarks
|
||||
</Typography>
|
||||
{renderBookmarks()} */}
|
||||
<Stack
|
||||
justifyContent="space-between"
|
||||
alignItems="end"
|
||||
direction="row"
|
||||
alignItems={{ xs: 'flex-start', md: 'flex-end' }}
|
||||
direction={{ xs: 'column', md: 'row' }}
|
||||
sx={{ width: '100%', paddingTop: '1rem' }}
|
||||
>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
|
@ -168,7 +168,7 @@ function RepoCard(props) {
|
||||
>
|
||||
<CardContent className={classes.content}>
|
||||
<Grid container>
|
||||
<Grid item xs={10}>
|
||||
<Grid item xs={12} md={10}>
|
||||
<Stack alignItems="center" direction="row" spacing={2}>
|
||||
<CardMedia
|
||||
classes={{
|
||||
@ -184,9 +184,12 @@ function RepoCard(props) {
|
||||
{name}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<VulnerabilityIconCheck {...vulnerabilityData} />
|
||||
<SignatureIconCheck isSigned={isSigned} />
|
||||
{/* <Chip label="Verified licensee" sx={{ backgroundColor: "#E8F5E9", color: "#388E3C" }} variant="filled" onDelete={() => { return }} deleteIcon={vulnerabilityCheck()} /> */}
|
||||
<div className="hide-on-mobile">
|
||||
<VulnerabilityIconCheck {...vulnerabilityData} className="hide-on-mobile" />
|
||||
</div>
|
||||
<div className="hide-on-mobile">
|
||||
<SignatureIconCheck isSigned={isSigned} className="hide-on-mobile" />
|
||||
</div>
|
||||
</Stack>
|
||||
<Tooltip title={description || 'Description not available'} placement="top">
|
||||
<Typography className={classes.versionLast} pt={1} sx={{ fontSize: 12 }} gutterBottom noWrap>
|
||||
@ -197,12 +200,12 @@ function RepoCard(props) {
|
||||
{platformChips()}
|
||||
</Stack>
|
||||
<Stack alignItems="center" direction="row" spacing={1} pt={2}>
|
||||
<Tooltip title={getVendor()} placement="top">
|
||||
<Tooltip title={getVendor()} placement="top" className="hide-on-mobile">
|
||||
<Typography className={classes.vendor} variant="body2" noWrap>
|
||||
{<Markdown options={{ forceInline: true }}>{getVendor()}</Markdown>}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Tooltip title={getVersion()} placement="top">
|
||||
<Tooltip title={getVersion()} placement="top" className="hide-on-mobile">
|
||||
<Typography className={classes.versionLast} variant="body2" noWrap>
|
||||
{getVersion()}
|
||||
</Typography>
|
||||
@ -214,7 +217,7 @@ function RepoCard(props) {
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<Grid item xs={2} md={2} className="hide-on-mobile">
|
||||
<Stack
|
||||
alignItems="flex-end"
|
||||
justifyContent="space-between"
|
||||
|
@ -24,7 +24,7 @@ import { isEmpty } from 'lodash';
|
||||
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
||||
import { mapToRepoFromRepoInfo } from 'utilities/objectModels';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
pageWrapper: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
height: '100vh'
|
||||
@ -37,7 +37,6 @@ const useStyles = makeStyles(() => ({
|
||||
},
|
||||
repoName: {
|
||||
fontWeight: '700',
|
||||
fontSize: '2.5rem',
|
||||
color: '#0F2139',
|
||||
textAlign: 'left'
|
||||
},
|
||||
@ -56,7 +55,10 @@ const useStyles = makeStyles(() => ({
|
||||
tabs: {
|
||||
marginTop: '3rem',
|
||||
padding: '0.5rem',
|
||||
height: '100%'
|
||||
height: '100%',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '0'
|
||||
}
|
||||
},
|
||||
tabContent: {
|
||||
height: '100%'
|
||||
@ -67,11 +69,18 @@ const useStyles = makeStyles(() => ({
|
||||
},
|
||||
tabPanel: {
|
||||
height: '100%',
|
||||
paddingLeft: '0rem!important'
|
||||
paddingLeft: '0rem!important',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '1.5rem 0'
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
marginTop: '8rem',
|
||||
paddingLeft: '1.5rem'
|
||||
paddingLeft: '1.5rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
marginTop: '1rem',
|
||||
paddingLeft: '0'
|
||||
}
|
||||
},
|
||||
card: {
|
||||
marginBottom: 2,
|
||||
@ -106,7 +115,27 @@ const useStyles = makeStyles(() => ({
|
||||
boxShadow: 'none!important'
|
||||
},
|
||||
header: {
|
||||
paddingLeft: '2rem'
|
||||
paddingLeft: '2rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '0'
|
||||
}
|
||||
},
|
||||
repoTitle: {
|
||||
textAlign: 'left',
|
||||
fontSize: '1rem',
|
||||
lineHeight: '1.5rem',
|
||||
color: 'rgba(0, 0, 0, 0.6)',
|
||||
padding: '0.5rem 0 0 4rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '0.5rem 0 0 0'
|
||||
}
|
||||
},
|
||||
platformChipsContainer: {
|
||||
alignItems: 'center',
|
||||
padding: '0.5rem 0 0 4rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '0.5rem 0 0 0'
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@ -205,7 +234,7 @@ function RepoDetails() {
|
||||
color: 'rgba(0, 0, 0, 0.6)',
|
||||
fontSize: '1rem',
|
||||
lineHeight: '150%',
|
||||
marginTop: '5%',
|
||||
marginTop: '1.3rem',
|
||||
alignSelf: 'stretch'
|
||||
}}
|
||||
>
|
||||
@ -225,47 +254,46 @@ function RepoDetails() {
|
||||
<Card className={classes.cardRoot}>
|
||||
<CardContent>
|
||||
<Grid container className={classes.header}>
|
||||
<Grid item xs={8}>
|
||||
<Stack alignItems="center" direction="row" spacing={2}>
|
||||
<CardMedia
|
||||
classes={{
|
||||
root: classes.media,
|
||||
img: classes.avatar
|
||||
}}
|
||||
component="img"
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
<Grid item xs={12} md={8}>
|
||||
<Stack alignItems="center" direction={{ xs: 'column', md: 'row' }} spacing={2}>
|
||||
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={2}>
|
||||
<CardMedia
|
||||
classes={{
|
||||
root: classes.media,
|
||||
img: classes.avatar
|
||||
}}
|
||||
component="img"
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
image={
|
||||
!isEmpty(repoDetailData?.logo)
|
||||
? `data:image/png;base64, ${repoDetailData?.logo}`
|
||||
: randomImage()
|
||||
}
|
||||
alt="icon"
|
||||
/>
|
||||
<Typography variant="h3" className={classes.repoName}>
|
||||
{name}
|
||||
</Typography>
|
||||
<VulnerabilityIconCheck
|
||||
vulnerabilitySeverity={repoDetailData.vulnerabiltySeverity}
|
||||
count={repoDetailData?.vulnerabilityCount}
|
||||
/>
|
||||
<SignatureIconCheck isSigned={repoDetailData.isSigned} />
|
||||
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
|
||||
!isEmpty(repoDetailData?.logo)
|
||||
? `data:image/png;base64, ${repoDetailData?.logo}`
|
||||
: randomImage()
|
||||
}
|
||||
alt="icon"
|
||||
/>
|
||||
<Typography variant="h4" className={classes.repoName}>
|
||||
{name}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={2}>
|
||||
<VulnerabilityIconCheck
|
||||
vulnerabilitySeverity={repoDetailData.vulnerabiltySeverity}
|
||||
count={repoDetailData?.vulnerabilityCount}
|
||||
/>
|
||||
<SignatureIconCheck isSigned={repoDetailData.isSigned} />
|
||||
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Typography
|
||||
pt={1}
|
||||
sx={{ fontSize: 16, lineHeight: '1.5rem', color: 'rgba(0, 0, 0, 0.6)', paddingLeft: '4rem' }}
|
||||
gutterBottom
|
||||
align="left"
|
||||
>
|
||||
<Typography gutterBottom className={classes.repoTitle}>
|
||||
{repoDetailData?.title || 'Title not available'}
|
||||
</Typography>
|
||||
<Stack alignItems="center" sx={{ paddingLeft: '4rem' }} direction="row" spacing={2} pt={1}>
|
||||
<Stack direction="row" spacing={2} className={classes.platformChipsContainer}>
|
||||
{platformChips()}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
<Grid item xs={8} className={classes.tabs}>
|
||||
<Grid item xs={12} md={8} className={classes.tabs}>
|
||||
<TabContext value={selectedTab}>
|
||||
<Box>
|
||||
<TabList
|
||||
@ -289,7 +317,7 @@ function RepoDetails() {
|
||||
</Box>
|
||||
</TabContext>
|
||||
</Grid>
|
||||
<Grid item xs={4} className={classes.metadata}>
|
||||
<Grid item xs={12} md={4} className={classes.metadata}>
|
||||
<RepoDetailsMetadata
|
||||
totalDownloads={repoDetailData?.downloads}
|
||||
repoURL={repoDetailData?.source}
|
||||
|
@ -127,28 +127,28 @@ export default function TagCard(props) {
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box>
|
||||
<Grid container item xs={12} direction={'row'}>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} md={4}>
|
||||
<Typography variant="body1">DIGEST</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={4} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Grid item xs={6} md={4} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Typography variant="body1">OS/Arch</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={4} sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Grid item xs={0} md={4} className="hide-on-mobile" sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Typography variant="body1"> Size </Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container item xs={12} direction={'row'}>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6} md={4}>
|
||||
<Tooltip title={digest || ''} placement="top">
|
||||
<Typography variant="body1">{digest?.substr(0, 12)}</Typography>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
<Grid item xs={4} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Grid item xs={6} md={4} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Typography variant="body1">
|
||||
{platform?.Os}/{platform?.Arch}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={4} sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Grid item xs={0} md={4} className="hide-on-mobile" sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Typography sx={{ textAlign: 'right' }} variant="body1">
|
||||
{transform.formatBytes(size)}
|
||||
</Typography>
|
||||
|
@ -42,7 +42,7 @@ import Loading from './Loading';
|
||||
import { dockerPull, podmanPull, skopeoPull } from 'utilities/pullStrings';
|
||||
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
pageWrapper: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
height: '100vh'
|
||||
@ -55,7 +55,6 @@ const useStyles = makeStyles(() => ({
|
||||
},
|
||||
repoName: {
|
||||
fontWeight: '700',
|
||||
fontSize: '2.5rem',
|
||||
color: '#0F2139',
|
||||
textAlign: 'left'
|
||||
},
|
||||
@ -64,13 +63,28 @@ const useStyles = makeStyles(() => ({
|
||||
width: '3rem',
|
||||
objectFit: 'fill'
|
||||
},
|
||||
digest: {
|
||||
textAlign: 'left',
|
||||
fontSize: '1rem',
|
||||
lineHeight: '1.5rem',
|
||||
color: 'rgba(0, 0, 0, 0.6)',
|
||||
padding: '0.5rem 0 0 4rem',
|
||||
maxWidth: '100%',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '0.5rem 0 0 0',
|
||||
fontSize: '0.5rem'
|
||||
}
|
||||
},
|
||||
media: {
|
||||
borderRadius: '3.125em'
|
||||
},
|
||||
tabs: {
|
||||
marginTop: '3rem',
|
||||
padding: '0.5rem',
|
||||
height: '100%'
|
||||
height: '100%',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '0'
|
||||
}
|
||||
},
|
||||
tabContent: {
|
||||
height: '100%'
|
||||
@ -86,11 +100,17 @@ const useStyles = makeStyles(() => ({
|
||||
tabPanel: {
|
||||
height: '100%',
|
||||
paddingLeft: '0rem!important',
|
||||
marginRight: '2rem!important'
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '1.5rem 0'
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
marginTop: '8rem',
|
||||
paddingLeft: '1.5rem'
|
||||
paddingLeft: '1.5rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
marginTop: '1rem',
|
||||
paddingLeft: '0'
|
||||
}
|
||||
},
|
||||
pull: {
|
||||
paddingLeft: '1.5rem',
|
||||
@ -123,7 +143,10 @@ const useStyles = makeStyles(() => ({
|
||||
boxShadow: 'none!important'
|
||||
},
|
||||
header: {
|
||||
paddingLeft: '2rem'
|
||||
paddingLeft: '2rem',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: '0'
|
||||
}
|
||||
},
|
||||
tabBox: {
|
||||
padding: '0.5rem'
|
||||
@ -259,41 +282,46 @@ function TagDetails() {
|
||||
<Card className={classes.cardRoot}>
|
||||
<CardContent>
|
||||
<Grid container>
|
||||
<Grid item xs={8} className={classes.header}>
|
||||
<Stack alignItems="center" direction="row" spacing={2}>
|
||||
<CardMedia
|
||||
classes={{
|
||||
root: classes.media,
|
||||
img: classes.avatar
|
||||
}}
|
||||
component="img"
|
||||
image={
|
||||
!isEmpty(imageDetailData?.logo)
|
||||
? `data:image/ png;base64, ${imageDetailData?.logo}`
|
||||
: randomImage()
|
||||
}
|
||||
alt="icon"
|
||||
/>
|
||||
<Typography variant="h3" className={classes.repoName}>
|
||||
{reponame}:{tag}
|
||||
</Typography>
|
||||
<VulnerabilityIconCheck
|
||||
vulnerabilitySeverity={imageDetailData.vulnerabiltySeverity}
|
||||
count={imageDetailData.vulnerabilityCount}
|
||||
/>
|
||||
<SignatureIconCheck isSigned={imageDetailData.isSigned} />
|
||||
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
|
||||
</Stack>
|
||||
<Typography
|
||||
pt={1}
|
||||
sx={{ fontSize: 16, lineHeight: '1.5rem', color: 'rgba(0, 0, 0, 0.6)', paddingLeft: '4rem' }}
|
||||
gutterBottom
|
||||
align="left"
|
||||
<Grid item xs={12} md={8} className={classes.header}>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
sx={{ width: { xs: '100%', md: 'auto' } }}
|
||||
direction={{ xs: 'column', md: 'row' }}
|
||||
spacing={2}
|
||||
>
|
||||
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={2}>
|
||||
<CardMedia
|
||||
classes={{
|
||||
root: classes.media,
|
||||
img: classes.avatar
|
||||
}}
|
||||
component="img"
|
||||
image={
|
||||
!isEmpty(imageDetailData?.logo)
|
||||
? `data:image/ png;base64, ${imageDetailData?.logo}`
|
||||
: randomImage()
|
||||
}
|
||||
alt="icon"
|
||||
/>
|
||||
<Typography variant="h4" className={classes.repoName}>
|
||||
<span className="hide-on-mobile">{reponame}</span>:{tag}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={2}>
|
||||
<VulnerabilityIconCheck
|
||||
vulnerabilitySeverity={imageDetailData.vulnerabiltySeverity}
|
||||
count={imageDetailData.vulnerabilityCount}
|
||||
/>
|
||||
<SignatureIconCheck isSigned={imageDetailData.isSigned} />
|
||||
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Typography gutterBottom className={classes.digest}>
|
||||
DIGEST: {imageDetailData?.digest}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={4} className={classes.pull}>
|
||||
<Grid item xs={0} md={4} className={`${classes.pull} hide-on-mobile`}>
|
||||
{isCopied ? (
|
||||
<Button className={classes.pullStringBoxCopied} data-testid="successPulled-buton">
|
||||
Copied Pull Command
|
||||
@ -465,13 +493,14 @@ function TagDetails() {
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
<Grid item xs={8} className={classes.tabs}>
|
||||
<Grid item xs={12} md={8} className={classes.tabs}>
|
||||
<TabContext value={selectedTab}>
|
||||
<Box>
|
||||
<TabList
|
||||
onChange={handleTabChange}
|
||||
TabIndicatorProps={{ className: classes.selectedTab }}
|
||||
sx={{ '& button.Mui-selected': { color: '#14191F', fontWeight: '600' } }}
|
||||
variant="scrollable"
|
||||
>
|
||||
<Tab value="Layers" label="Layers" className={classes.tabContent} />
|
||||
<Tab
|
||||
@ -502,7 +531,7 @@ function TagDetails() {
|
||||
</Box>
|
||||
</TabContext>
|
||||
</Grid>
|
||||
<Grid item xs={4} className={classes.metadata}>
|
||||
<Grid item xs={12} md={4} className={classes.metadata}>
|
||||
<TagDetailsMetadata
|
||||
platform={getPlatform()}
|
||||
size={imageDetailData?.size}
|
||||
|
@ -25,6 +25,13 @@ const theme = createTheme(
|
||||
})
|
||||
);
|
||||
|
||||
theme.typography.h4 = {
|
||||
fontSize: '2.5rem',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
fontSize: '1.5rem'
|
||||
}
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<StyledEngineProvider injectFirst>
|
||||
|
Loading…
Reference in New Issue
Block a user