Added tag page with vulnerabilities. (#82)
Added the Image Tag details page Signed-off-by: Amelia-Maria Breda <ameliamaria.breda@dxc.com> Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
parent
10a349fdcd
commit
1870b53656
@ -9,6 +9,7 @@ import './App.css';
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom";
|
||||
import { AuthWrapper } from 'utilities/AuthWrapper.jsx';
|
||||
import RepoPage from 'pages/RepoPage.jsx';
|
||||
import TagPage from 'pages/TagPage';
|
||||
import ExplorePage from 'pages/ExplorePage.jsx';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
@ -38,6 +39,7 @@ function App() {
|
||||
<Route path="/home" element={<HomePage keywords={searchKeywords} updateKeywords={setSearchKeywords} data={data} updateData={setData} />} />
|
||||
<Route path="/explore" element={<ExplorePage keywords={searchKeywords} updateKeywords={setSearchKeywords} data={data} updateData={setData} />} />
|
||||
<Route path="/image/:name" element={<RepoPage />} />
|
||||
<Route path="/image/:name/tag/:tag" element={<TagPage />} />
|
||||
</Route>
|
||||
<Route element={<AuthWrapper isLoggedIn={!isLoggedIn} redirect="/"/>}>
|
||||
<Route path="/login" element={<LoginPage isAuthEnabled={isAuthEnabled} setIsAuthEnabled={setIsAuthEnabled} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} />} />
|
||||
|
19
src/__tests__/Explore/FilterCard.test.js
Normal file
19
src/__tests__/Explore/FilterCard.test.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { render, screen , fireEvent } from '@testing-library/react';
|
||||
import FilterCard from 'components/FilterCard';
|
||||
import React, {useState} from 'react';
|
||||
|
||||
const StateFilterCardWrapper = () => {
|
||||
return (<FilterCard title="Products" filters={["Images","Plugins"]} />)
|
||||
}
|
||||
|
||||
describe('Filters components', () => {
|
||||
it('renders the filters cards', () => {
|
||||
render(<StateFilterCardWrapper/> );
|
||||
expect(screen.getAllByRole('checkbox')).toHaveLength(2);
|
||||
|
||||
const checkbox = screen.getAllByRole('checkbox');
|
||||
expect(checkbox[0]).not.toBeChecked()
|
||||
fireEvent.click(checkbox[0])
|
||||
expect(checkbox[0]).toBeChecked()
|
||||
});
|
||||
});
|
@ -66,9 +66,9 @@ describe('Home component', () => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } })
|
||||
render(<StateHomeWrapper/>);
|
||||
await waitFor(() => expect(screen.getAllByText(/alpine/i)).toHaveLength(3));
|
||||
await waitFor(() => expect(screen.getAllByText(/mongo/i)).toHaveLength(3));
|
||||
await waitFor(() => expect(screen.getAllByText(/node/i)).toHaveLength(5));
|
||||
await waitFor(() => expect(screen.getAllByText(/alpine/i)).toHaveLength(2));
|
||||
await waitFor(() => expect(screen.getAllByText(/mongo/i)).toHaveLength(2));
|
||||
await waitFor(() => expect(screen.getAllByText(/node/i)).toHaveLength(1));
|
||||
});
|
||||
|
||||
it('should log an error when data can\'t be fetched', async() => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { fireEvent,waitFor, render, screen } from '@testing-library/react';
|
||||
import Tags from 'components/Tags';
|
||||
import React from 'react';
|
||||
|
||||
@ -10,27 +10,48 @@ jest.mock('react-router-dom', () => ({
|
||||
}));
|
||||
|
||||
const mockedTagsData = {
|
||||
name: 'test',
|
||||
tags: [
|
||||
{
|
||||
"Digest": "2aa7ff5ca352d4d25fc6548f9930a436aacd64d56b1bd1f9ff4423711b9c8718",
|
||||
"Tag": "latest",
|
||||
"Layers": [
|
||||
{
|
||||
"Size": "2798889",
|
||||
"Digest": "2408cc74d12b6cd092bb8b516ba7d5e290f485d3eb9672efc00f0583730179e8"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
"name": "alpine",
|
||||
"images": [
|
||||
{
|
||||
"Digest": "59118d0816d2e8e05cb04c328224056b3ce07d7afc2ad59e2f1f08bb0ba2ff3c",
|
||||
"Tag": "latest",
|
||||
"Layers": [
|
||||
{
|
||||
"Size": "2806054",
|
||||
"Digest": "213ec9aee27d8be045c6a92b7eac22c9a64b44558193775a1a7f626352392b49"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"lastUpdated": "2022-08-09T17:19:53.274069586Z",
|
||||
"size": "2806985",
|
||||
"platforms": [
|
||||
{
|
||||
"Os": "linux",
|
||||
"Arch": "amd64"
|
||||
}
|
||||
],
|
||||
"vendors": [
|
||||
""
|
||||
],
|
||||
"newestTag": null
|
||||
}
|
||||
|
||||
|
||||
describe('Tags component', () => {
|
||||
it('should open and close details dropdown for tags', () => {
|
||||
render(<Tags data={mockedTagsData}/>);
|
||||
const openBtn = screen.getByText(/see layers/i);
|
||||
const openBtn = screen.getByText(/see digests/i);
|
||||
fireEvent.click(openBtn);
|
||||
expect(screen.queryByText(/see layers/i)).not.toBeInTheDocument();
|
||||
expect(screen.getByText(/hide layers/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/see digests/i)).not.toBeInTheDocument();
|
||||
expect(screen.getByText(/hide digests/i)).toBeInTheDocument();
|
||||
});
|
||||
it('should navigate to tag page details when tag is clicked', async() => {
|
||||
render(<Tags data={mockedTagsData}/>);
|
||||
const tagLink = await screen.findByText('latest');
|
||||
fireEvent.click(tagLink);
|
||||
await waitFor(() => {
|
||||
expect(mockedUsedNavigate).toHaveBeenCalledWith('tag/latest');
|
||||
});
|
||||
})
|
||||
})
|
449
src/__tests__/RepoPage/VulnerabilitiesDetails.test.js
Normal file
449
src/__tests__/RepoPage/VulnerabilitiesDetails.test.js
Normal file
@ -0,0 +1,449 @@
|
||||
import { render, screen , waitFor, fireEvent } from '@testing-library/react';
|
||||
import { api } from 'api';
|
||||
import VulnerabilitiesDetails from 'components/VulnerabilitiesDetails';
|
||||
import React, {useState} from 'react';
|
||||
|
||||
const StateVulnerabilitiesWrapper = () => {
|
||||
return (<VulnerabilitiesDetails name='mongo' />)
|
||||
}
|
||||
|
||||
const mockCVEList = {
|
||||
CVEListForImage: {
|
||||
"Tag": "",
|
||||
"CVEList": [
|
||||
{
|
||||
"Id": "CVE-2020-16156",
|
||||
"Title": "perl-CPAN: Bypass of verification of signatures in CHECKSUMS files",
|
||||
"Description": "CPAN 2.28 allows Signature Verification Bypass.",
|
||||
"Severity": "MEDIUM",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "perl-base",
|
||||
"InstalledVersion": "5.30.0-9ubuntu0.2",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2021-36222",
|
||||
"Title": "krb5: Sending a request containing PA-ENCRYPTED-CHALLENGE padata element without using FAST could result in NULL dereference in KDC which leads to DoS",
|
||||
"Description": "ec_verify in kdc/kdc_preauth_ec.c in the Key Distribution Center (KDC) in MIT Kerberos 5 (aka krb5) before 1.18.4 and 1.19.x before 1.19.2 allows remote attackers to cause a NULL pointer dereference and daemon crash. This occurs because a return value is not properly managed in a certain situation.",
|
||||
"Severity": "HIGH",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "krb5-locales",
|
||||
"InstalledVersion": "1.17-6ubuntu4.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libgssapi-krb5-2",
|
||||
"InstalledVersion": "1.17-6ubuntu4.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libk5crypto3",
|
||||
"InstalledVersion": "1.17-6ubuntu4.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libkrb5-3",
|
||||
"InstalledVersion": "1.17-6ubuntu4.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libkrb5support0",
|
||||
"InstalledVersion": "1.17-6ubuntu4.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2021-4209",
|
||||
"Title": "GnuTLS: Null pointer dereference in MD_UPDATE",
|
||||
"Description": "A NULL pointer dereference flaw was found in GnuTLS. As Nettle's hash update functions internally call memcpy, providing zero-length input may cause undefined behavior. This flaw leads to a denial of service after authentication in rare circumstances.",
|
||||
"Severity": "LOW",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libgnutls30",
|
||||
"InstalledVersion": "3.6.13-2ubuntu1.6",
|
||||
"FixedVersion": "3.6.13-2ubuntu1.7"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2022-1586",
|
||||
"Title": "pcre2: Out-of-bounds read in compile_xclass_matchingpath in pcre2_jit_compile.c",
|
||||
"Description": "An out-of-bounds read vulnerability was discovered in the PCRE2 library in the compile_xclass_matchingpath() function of the pcre2_jit_compile.c file. This involves a unicode property matching issue in JIT-compiled regular expressions. The issue occurs because the character was not fully read in case-less matching within JIT.",
|
||||
"Severity": "CRITICAL",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libpcre2-8-0",
|
||||
"InstalledVersion": "10.34-7",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2021-20223",
|
||||
"Title": "",
|
||||
"Description": "An issue was found in fts5UnicodeTokenize() in ext/fts5/fts5_tokenize.c in Sqlite. A unicode61 tokenizer configured to treat unicode \"control-characters\" (class Cc), was treating embedded nul characters as tokens. The issue was fixed in sqlite-3.34.0 and later.",
|
||||
"Severity": "NONE",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libsqlite3-0",
|
||||
"InstalledVersion": "3.31.1-4ubuntu0.3",
|
||||
"FixedVersion": "3.31.1-4ubuntu0.4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2017-11164",
|
||||
"Title": "pcre: OP_KETRMAX feature in the match function in pcre_exec.c",
|
||||
"Description": "In PCRE 8.41, the OP_KETRMAX feature in the match function in pcre_exec.c allows stack exhaustion (uncontrolled recursion) when processing a crafted regular expression.",
|
||||
"Severity": "UNKNOWN",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libpcre3",
|
||||
"InstalledVersion": "2:8.39-12ubuntu0.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2020-35527",
|
||||
"Title": "sqlite: Out of bounds access during table rename",
|
||||
"Description": "In SQLite 3.31.1, there is an out of bounds access problem through ALTER TABLE for views that have a nested FROM clause.",
|
||||
"Severity": "MEDIUM",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libsqlite3-0",
|
||||
"InstalledVersion": "3.31.1-4ubuntu0.3",
|
||||
"FixedVersion": "3.31.1-4ubuntu0.4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2013-4235",
|
||||
"Title": "shadow-utils: TOCTOU race conditions by copying and removing directory trees",
|
||||
"Description": "shadow: TOCTOU (time-of-check time-of-use) race condition when copying and removing directory trees",
|
||||
"Severity": "LOW",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "login",
|
||||
"InstalledVersion": "1:4.8.1-1ubuntu5.20.04.2",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "passwd",
|
||||
"InstalledVersion": "1:4.8.1-1ubuntu5.20.04.2",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2021-43618",
|
||||
"Title": "gmp: Integer overflow and resultant buffer overflow via crafted input",
|
||||
"Description": "GNU Multiple Precision Arithmetic Library (GMP) through 6.2.1 has an mpz/inp_raw.c integer overflow and resultant buffer overflow via crafted input, leading to a segmentation fault on 32-bit platforms.",
|
||||
"Severity": "LOW",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libgmp10",
|
||||
"InstalledVersion": "2:6.2.0+dfsg-4",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2022-2509",
|
||||
"Title": "gnutls: Double free during gnutls_pkcs7_verify.",
|
||||
"Description": "A vulnerability found in gnutls. This security flaw happens because of a double free error occurs during verification of pkcs7 signatures in gnutls_pkcs7_verify function.",
|
||||
"Severity": "MEDIUM",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libgnutls30",
|
||||
"InstalledVersion": "3.6.13-2ubuntu1.6",
|
||||
"FixedVersion": "3.6.13-2ubuntu1.7"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2021-39537",
|
||||
"Title": "ncurses: heap-based buffer overflow in _nc_captoinfo() in captoinfo.c",
|
||||
"Description": "An issue was discovered in ncurses through v6.2-1. _nc_captoinfo in captoinfo.c has a heap-based buffer overflow.",
|
||||
"Severity": "LOW",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libncurses6",
|
||||
"InstalledVersion": "6.2-0ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libncursesw6",
|
||||
"InstalledVersion": "6.2-0ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libtinfo6",
|
||||
"InstalledVersion": "6.2-0ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "ncurses-base",
|
||||
"InstalledVersion": "6.2-0ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "ncurses-bin",
|
||||
"InstalledVersion": "6.2-0ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2022-1587",
|
||||
"Title": "pcre2: Out-of-bounds read in get_recurse_data_length in pcre2_jit_compile.c",
|
||||
"Description": "An out-of-bounds read vulnerability was discovered in the PCRE2 library in the get_recurse_data_length() function of the pcre2_jit_compile.c file. This issue affects recursions in JIT-compiled regular expressions caused by duplicate data transfers.",
|
||||
"Severity": "LOW",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libpcre2-8-0",
|
||||
"InstalledVersion": "10.34-7",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2022-29458",
|
||||
"Title": "ncurses: segfaulting OOB read",
|
||||
"Description": "ncurses 6.3 before patch 20220416 has an out-of-bounds read and segmentation violation in convert_strings in tinfo/read_entry.c in the terminfo library.",
|
||||
"Severity": "LOW",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libncurses6",
|
||||
"InstalledVersion": "6.2-0ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libncursesw6",
|
||||
"InstalledVersion": "6.2-0ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libtinfo6",
|
||||
"InstalledVersion": "6.2-0ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "ncurses-base",
|
||||
"InstalledVersion": "6.2-0ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "ncurses-bin",
|
||||
"InstalledVersion": "6.2-0ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2016-2781",
|
||||
"Title": "coreutils: Non-privileged session can escape to the parent session in chroot",
|
||||
"Description": "chroot in GNU coreutils, when used with --userspec, allows local users to escape to the parent session via a crafted TIOCSTI ioctl call, which pushes characters to the terminal's input buffer.",
|
||||
"Severity": "LOW",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "coreutils",
|
||||
"InstalledVersion": "8.30-3ubuntu2",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2021-3671",
|
||||
"Title": "samba: Null pointer dereference on missing sname in TGS-REQ",
|
||||
"Description": "A null pointer de-reference was found in the way samba kerberos server handled missing sname in TGS-REQ (Ticket Granting Server - Request). An authenticated user could use this flaw to crash the samba server.",
|
||||
"Severity": "LOW",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libasn1-8-heimdal",
|
||||
"InstalledVersion": "7.7.0+dfsg-1ubuntu1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libgssapi3-heimdal",
|
||||
"InstalledVersion": "7.7.0+dfsg-1ubuntu1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libhcrypto4-heimdal",
|
||||
"InstalledVersion": "7.7.0+dfsg-1ubuntu1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libheimbase1-heimdal",
|
||||
"InstalledVersion": "7.7.0+dfsg-1ubuntu1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libheimntlm0-heimdal",
|
||||
"InstalledVersion": "7.7.0+dfsg-1ubuntu1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libhx509-5-heimdal",
|
||||
"InstalledVersion": "7.7.0+dfsg-1ubuntu1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libkrb5-26-heimdal",
|
||||
"InstalledVersion": "7.7.0+dfsg-1ubuntu1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libroken18-heimdal",
|
||||
"InstalledVersion": "7.7.0+dfsg-1ubuntu1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libwind0-heimdal",
|
||||
"InstalledVersion": "7.7.0+dfsg-1ubuntu1",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2016-20013",
|
||||
"Title": "",
|
||||
"Description": "sha256crypt and sha512crypt through 0.6 allow attackers to cause a denial of service (CPU consumption) because the algorithm's runtime is proportional to the square of the length of the password.",
|
||||
"Severity": "LOW",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libc-bin",
|
||||
"InstalledVersion": "2.31-0ubuntu9.9",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libc6",
|
||||
"InstalledVersion": "2.31-0ubuntu9.9",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2022-35252",
|
||||
"Title": "curl: control code in cookie denial of service",
|
||||
"Description": "No description is available for this CVE.",
|
||||
"Severity": "LOW",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libcurl4",
|
||||
"InstalledVersion": "7.68.0-1ubuntu2.12",
|
||||
"FixedVersion": "7.68.0-1ubuntu2.13"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2021-37750",
|
||||
"Title": "krb5: NULL pointer dereference in process_tgs_req() in kdc/do_tgs_req.c via a FAST inner body that lacks server field",
|
||||
"Description": "The Key Distribution Center (KDC) in MIT Kerberos 5 (aka krb5) before 1.18.5 and 1.19.x before 1.19.3 has a NULL pointer dereference in kdc/do_tgs_req.c via a FAST inner body that lacks a server field.",
|
||||
"Severity": "MEDIUM",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "krb5-locales",
|
||||
"InstalledVersion": "1.17-6ubuntu4.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libgssapi-krb5-2",
|
||||
"InstalledVersion": "1.17-6ubuntu4.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libk5crypto3",
|
||||
"InstalledVersion": "1.17-6ubuntu4.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libkrb5-3",
|
||||
"InstalledVersion": "1.17-6ubuntu4.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
},
|
||||
{
|
||||
"Name": "libkrb5support0",
|
||||
"InstalledVersion": "1.17-6ubuntu4.1",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2020-35525",
|
||||
"Title": "sqlite: Null pointer derreference in src/select.c",
|
||||
"Description": "In SQlite 3.31.1, a potential null pointer derreference was found in the INTERSEC query processing.",
|
||||
"Severity": "MEDIUM",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "libsqlite3-0",
|
||||
"InstalledVersion": "3.31.1-4ubuntu0.3",
|
||||
"FixedVersion": "3.31.1-4ubuntu0.4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "CVE-2022-37434",
|
||||
"Title": "zlib: a heap-based buffer over-read or buffer overflow in inflate in inflate.c via a large gzip header extra field",
|
||||
"Description": "zlib through 1.2.12 has a heap-based buffer over-read or buffer overflow in inflate in inflate.c via a large gzip header extra field. NOTE: only applications that call inflateGetHeader are affected. Some common applications bundle the affected zlib source code but may be unable to call inflateGetHeader (e.g., see the nodejs/node reference).",
|
||||
"Severity": "MEDIUM",
|
||||
"PackageList": [
|
||||
{
|
||||
"Name": "zlib1g",
|
||||
"InstalledVersion": "1:1.2.11.dfsg-2ubuntu1.3",
|
||||
"FixedVersion": "Not Specified"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
afterEach(() => {
|
||||
// restore the spy created with spyOn
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Vulnerabilties page', () => {
|
||||
it('renders the vulnerabilities if there are any', async() => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } })
|
||||
render(<StateVulnerabilitiesWrapper/> );
|
||||
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
||||
await waitFor(() => expect(screen.getAllByText(/Title/i)).toHaveLength(20));
|
||||
});
|
||||
|
||||
it('renders no vulnerabilities if there are not any', async() => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: {CVEListForImage: {"Tag": "", "CVEList": []} } } })
|
||||
render(<StateVulnerabilitiesWrapper/> );
|
||||
await waitFor(() => expect(screen.getAllByText('No Vulnerabilities')).toHaveLength(1));
|
||||
|
||||
});
|
||||
|
||||
it('should open and close description dropdown for vulnerabilities', async() => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } })
|
||||
render(<StateVulnerabilitiesWrapper/> );
|
||||
await waitFor(() => expect(screen.getAllByText(/see description/i)).toHaveLength(20));
|
||||
const openText = screen.getAllByText(/see description/i);
|
||||
fireEvent.click(openText[0]);
|
||||
await waitFor(() => expect(screen.getAllByText(/hide description/i)).toHaveLength(1));
|
||||
await waitFor(() => expect(screen.getAllByText(/see description/i)).toHaveLength(19));
|
||||
});
|
||||
|
||||
it('should log an error when data can\'t be fetched', async() => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: { } })
|
||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(<StateVulnerabilitiesWrapper/> );
|
||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
||||
})
|
||||
});
|
100
src/__tests__/TagPage/TagDetails.test.js
Normal file
100
src/__tests__/TagPage/TagDetails.test.js
Normal file
@ -0,0 +1,100 @@
|
||||
import { render, screen , waitFor } from '@testing-library/react';
|
||||
import { api } from 'api';
|
||||
import TagDetails from 'components/TagDetails';
|
||||
import React from 'react';
|
||||
|
||||
const mockImage = {"ExpandedRepoInfo": {
|
||||
"Images": [
|
||||
{
|
||||
"Digest": "7374731e3dd3112d41ece21cf2db5a16f11a51b33bf065e98c767893f50d3dec",
|
||||
"Tag": "latest",
|
||||
"Layers": [
|
||||
{
|
||||
"Size": "28572596",
|
||||
"Digest": "3b65ec22a9e96affe680712973e88355927506aa3f792ff03330f3a3eb601a98"
|
||||
},
|
||||
{
|
||||
"Size": "1835",
|
||||
"Digest": "016bc871e2b33f0e2a37272769ebd6defdb4b702f0d41ec1e685f0366b64e64a"
|
||||
},
|
||||
{
|
||||
"Size": "3059542",
|
||||
"Digest": "9ddd649edd82d79ffc6f573cd5da7909ae50596b95aca684a571aff6e36aa8cb"
|
||||
},
|
||||
{
|
||||
"Size": "6506025",
|
||||
"Digest": "39bf776c01e412c9cf35ea7a41f97370c486dee27a2aab228cf2e850a8863e8b"
|
||||
},
|
||||
{
|
||||
"Size": "149",
|
||||
"Digest": "f7f0405a2fe343547a60a9d4182261ca02d70bb9e47d6cd248f3285d6b41e64c"
|
||||
},
|
||||
{
|
||||
"Size": "1447",
|
||||
"Digest": "89785d0d9c65afe73fbd9bcb29c451090ca84df0e128cf1ecf5712c036e8c9d2"
|
||||
},
|
||||
{
|
||||
"Size": "261",
|
||||
"Digest": "fd40d84c80b0302ca13faab8210d8c7082814f6f2ab576b3a61f467d03e1cb0b"
|
||||
},
|
||||
{
|
||||
"Size": "193228772",
|
||||
"Digest": "d50d65ac4752500ab9f3c24c86b4aa218bea9a0bb0a837ae54ffe2e6d2454f5a"
|
||||
},
|
||||
{
|
||||
"Size": "5067",
|
||||
"Digest": "255e24cbd370c0055e0d31e063e63c792fa68aff9e25a7ac0a21d39cf6d47573"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Summary": {
|
||||
"Name": "mongo",
|
||||
"LastUpdated": "2022-08-02T01:30:49.193203152Z",
|
||||
"Size": "231383863",
|
||||
"Platforms": [
|
||||
{
|
||||
"Os": "linux",
|
||||
"Arch": "amd64"
|
||||
}
|
||||
],
|
||||
"Vendors": [
|
||||
""
|
||||
],
|
||||
"NewestImage": null
|
||||
}
|
||||
}};
|
||||
|
||||
jest.mock("react-router-dom", () => ({
|
||||
// @ts-ignore
|
||||
...jest.requireActual("react-router-dom"),
|
||||
useParams: () =>{return {name:'test'} },
|
||||
}));
|
||||
|
||||
|
||||
afterEach(() => {
|
||||
// restore the spy created with spyOn
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Tags details', () => {
|
||||
it('should show vulnerability tab', async() => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } })
|
||||
render(<TagDetails /> );
|
||||
await waitFor(() => expect(screen.getAllByRole('tab')).toHaveLength(1));
|
||||
});
|
||||
|
||||
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 /> );
|
||||
await waitFor(() => expect(error).toBeCalledTimes(2));
|
||||
})
|
||||
it('should show tag details metadata', async() => {
|
||||
// @ts-ignore
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImage } })
|
||||
render(<TagDetails /> );
|
||||
expect(await screen.findByTestId('tagDetailsMetadata-container')).toBeInTheDocument();
|
||||
})
|
||||
});
|
26
src/__tests__/TagPage/TagPage.test.js
Normal file
26
src/__tests__/TagPage/TagPage.test.js
Normal file
@ -0,0 +1,26 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { api } from 'api';
|
||||
import TagPage from 'pages/TagPage';
|
||||
import React from 'react';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
|
||||
afterEach(() => {
|
||||
// restore the spy created with spyOn
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
jest.mock("components/TagDetails", () => () => {
|
||||
return <div/>;
|
||||
});
|
||||
|
||||
it('renders the tags page component', async () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="*" element={<TagPage updateKeywords={() => {}}/>} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
expect(screen.getByTestId('tag-container')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -61,8 +61,9 @@ const api = {
|
||||
};
|
||||
|
||||
const endpoints = {
|
||||
imageList: '/v2/_zot/ext/search?query={RepoListWithNewestImage(){ NewestImage {RepoName Tag LastUpdated Description Licenses Vendor Size Labels} }}',
|
||||
detailedRepoInfo: (name) => `/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Digest Tag Layers {Size Digest}} Summary {Name LastUpdated Size Platforms {Os Arch} Vendors NewestImage {Tag}}}}`
|
||||
imageList: '/v2/_zot/ext/search?query={RepoListWithNewestImage(){Platforms {Os Arch} NewestImage {RepoName Tag LastUpdated Description Licenses Vendor Size Labels} }}',
|
||||
detailedRepoInfo: (name) => `/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Digest Tag Layers {Size Digest}} Summary {Name LastUpdated Size Platforms {Os Arch} Vendors NewestImage {Tag}}}}`,
|
||||
vulnerabilitiesForRepo: (name) => `/v2/_zot/ext/search?query={CVEListForImage(image: "${name}"){Tag, CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`
|
||||
}
|
||||
|
||||
export {api, endpoints};
|
||||
|
@ -41,7 +41,7 @@ const useStyles = makeStyles(() => ({
|
||||
sortForm:{
|
||||
backgroundColor: '#ffffff',
|
||||
borderColor: "#E0E0E0",
|
||||
borderRadius: "6px",
|
||||
borderRadius: "0.375em",
|
||||
},
|
||||
}));
|
||||
|
||||
@ -63,6 +63,7 @@ function Explore ({ keywords, data, updateData }) {
|
||||
latestVersion: image.NewestImage.Tag,
|
||||
tags: image.NewestImage.Labels,
|
||||
description: image.NewestImage.Description,
|
||||
platforms: image.Platforms,
|
||||
licenses: image.NewestImage.Licenses,
|
||||
size: image.NewestImage.Size,
|
||||
vendor: image.NewestImage.Vendor,
|
||||
@ -105,6 +106,7 @@ function Explore ({ keywords, data, updateData }) {
|
||||
description={item.description}
|
||||
tags={item.tags}
|
||||
vendor={item.vendor}
|
||||
platforms={item.platforms}
|
||||
size={item.size}
|
||||
licenses={item.licenses}
|
||||
key={index}
|
||||
@ -145,25 +147,25 @@ function Explore ({ keywords, data, updateData }) {
|
||||
) : (
|
||||
<Grid container className={classes.gridWrapper}>
|
||||
<Grid container item xs={12}>
|
||||
<Grid item xs={3}>
|
||||
<Grid item xs={0}>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" className={classes.resultsRow}>
|
||||
<Typography variant="body2" className={classes.results}>Results {filteredData.length}</Typography>
|
||||
<FormControl sx={{m:'1', minWidth:"4.6875rem"}} className={classes.sortForm} size="small">
|
||||
{/* <FormControl sx={{m:'1', minWidth:"4.6875rem"}} className={classes.sortForm} size="small">
|
||||
<InputLabel>Sort</InputLabel>
|
||||
<Select label="Sort" value={sortFilter} onChange={handleSortChange} MenuProps={{disableScrollLock: true}}>
|
||||
<MenuItem value='relevance'>Relevance</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FormControl> */}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container item xs={12} spacing={5} pt={1}>
|
||||
<Grid item xs={3}>
|
||||
{/* <Grid item xs={3}>
|
||||
{renderFilterCards()}
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
</Grid> */}
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="column" spacing={2}>{renderRepoCards()}</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -3,7 +3,6 @@ import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||
|
||||
// components
|
||||
import {Typography, Breadcrumbs} from '@mui/material';
|
||||
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
|
||||
// styling
|
||||
@ -26,7 +25,7 @@ const useStyles = makeStyles((theme) => {
|
||||
explore: {
|
||||
color: '#52637A',
|
||||
fontSize: "1rem",
|
||||
letterSpacing: "0.009375rem"
|
||||
letterSpacing: "0.009375rem",
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -36,13 +35,18 @@ function ExploreHeader() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const path = location.pathname;
|
||||
|
||||
const pathWithoutImage = path.replace('tag/', '');
|
||||
const pathToBeDisplayed = pathWithoutImage.replace('/image/', '');
|
||||
const pathHeader = pathToBeDisplayed.replace("/", " / ").replace(/%2F/g,'/');
|
||||
const pathWithTag = path.substring(0, path.lastIndexOf('/'));
|
||||
|
||||
return (
|
||||
<div className={classes.exploreHeader}>
|
||||
<ArrowBackIcon sx={{color: "#14191F",fontSize: "2rem", cursor: "pointer"}} onClick={() => navigate(-1)}/>
|
||||
<Breadcrumbs separator="/" aria-label="breadcrumb">
|
||||
<Link to="/"><Typography variant="body1" className={classes.explore}>Home</Typography></Link>
|
||||
{ path.includes('/image/') && <Typography className={classes.explore} variant="body1">{path.replace('/image/', '')}</Typography> }
|
||||
<Link to={pathWithTag.substring(0, pathWithTag.lastIndexOf('/'))}>{ path.includes('/image/') && <Typography className={classes.explore} variant="body1">{pathHeader}</Typography> }</Link>
|
||||
|
||||
</Breadcrumbs>
|
||||
<div></div>
|
||||
</div>
|
||||
|
@ -77,6 +77,7 @@ function Home({ keywords, data, updateData }) {
|
||||
latestVersion: image.NewestImage.Tag,
|
||||
tags: image.NewestImage.Labels,
|
||||
description: image.NewestImage.Description,
|
||||
platforms: image.Platforms,
|
||||
licenses: image.NewestImage.Licenses,
|
||||
size: image.NewestImage.Size,
|
||||
vendor: image.NewestImage.Vendor,
|
||||
@ -137,6 +138,7 @@ function Home({ keywords, data, updateData }) {
|
||||
description={item.description}
|
||||
tags={item.tags}
|
||||
vendor={item.vendor}
|
||||
platforms={item.platforms}
|
||||
size={item.size}
|
||||
licenses={item.licenses}
|
||||
key={index}
|
||||
@ -160,6 +162,7 @@ function Home({ keywords, data, updateData }) {
|
||||
description={item.description}
|
||||
tags={item.tags}
|
||||
vendor={item.vendor}
|
||||
platforms={item.platforms}
|
||||
size={item.size}
|
||||
licenses={item.licenses}
|
||||
key={index}
|
||||
@ -188,10 +191,10 @@ function Home({ keywords, data, updateData }) {
|
||||
{renderPreviewCards()}
|
||||
</Grid> <Grid >
|
||||
</Grid>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
{/* <Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Bookmarks
|
||||
</Typography>
|
||||
{renderBookmarks()}
|
||||
{renderBookmarks()} */}
|
||||
<Stack></Stack>
|
||||
<Typography variant="h4" align="left" className={classes.sectionTitle}>
|
||||
Recently updated repositories
|
||||
|
@ -67,9 +67,9 @@ const useStyles = makeStyles(() => ({
|
||||
}));
|
||||
|
||||
//function that returns a random element from an array
|
||||
function getRandom (list) {
|
||||
return list[Math.floor((Math.random()*list.length))];
|
||||
}
|
||||
// function getRandom (list) {
|
||||
// return list[Math.floor((Math.random()*list.length))];
|
||||
// }
|
||||
|
||||
function PreviewCard(props) {
|
||||
const classes = useStyles();
|
||||
@ -77,29 +77,29 @@ function PreviewCard(props) {
|
||||
const { name } = props;
|
||||
|
||||
const goToDetails = (repo) => {
|
||||
navigate(`/image/${name}`);
|
||||
navigate(`/image/${encodeURIComponent(name)}`);
|
||||
};
|
||||
|
||||
const vulnerabilityCheck = () => {
|
||||
const noneVulnerability = <PestControlOutlinedIcon sx={{ color: "#43A047!important", padding:"0.2rem", background: "#E8F5E9", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
const unknownVulnerability = <PestControlOutlinedIcon sx={{ color: "#52637A!important", padding:"0.2rem", background: "#ECEFF1", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
const lowVulnerability = <PestControlOutlinedIcon sx={{ color: "#FB8C00!important", padding:"0.2rem", background: "#FFF3E0", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
const mediumVulnerability = <PestControlIcon sx={{ color: "#FB8C00!important", padding:"0.2rem", background: "#FFF3E0", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
const highVulnerability = <PestControlOutlinedIcon sx={{ color: "#E53935!important", padding:"0.2rem", background: "#FEEBEE", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
const criticalVulnerability = <PestControlIcon sx={{ color: "#E53935!important", padding:"0.2rem", background: "#FEEBEE", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
// const vulnerabilityCheck = () => {
|
||||
// const noneVulnerability = <PestControlOutlinedIcon sx={{ color: "#43A047!important", padding:"0.2rem", background: "#E8F5E9", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
// const unknownVulnerability = <PestControlOutlinedIcon sx={{ color: "#52637A!important", padding:"0.2rem", background: "#ECEFF1", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
// const lowVulnerability = <PestControlOutlinedIcon sx={{ color: "#FB8C00!important", padding:"0.2rem", background: "#FFF3E0", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
// const mediumVulnerability = <PestControlIcon sx={{ color: "#FB8C00!important", padding:"0.2rem", background: "#FFF3E0", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
// const highVulnerability = <PestControlOutlinedIcon sx={{ color: "#E53935!important", padding:"0.2rem", background: "#FEEBEE", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
// const criticalVulnerability = <PestControlIcon sx={{ color: "#E53935!important", padding:"0.2rem", background: "#FEEBEE", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
|
||||
const arrVulnerability = [noneVulnerability, unknownVulnerability, lowVulnerability, mediumVulnerability, highVulnerability, criticalVulnerability]
|
||||
return(getRandom(arrVulnerability));
|
||||
}
|
||||
// const arrVulnerability = [noneVulnerability, unknownVulnerability, lowVulnerability, mediumVulnerability, highVulnerability, criticalVulnerability]
|
||||
// return(getRandom(arrVulnerability));
|
||||
// }
|
||||
|
||||
const signatureCheck = () => {
|
||||
const unverifiedSignature = <GppBadOutlinedIcon sx={{ color: "#E53935!important", padding:"0.2rem", background: "#FEEBEE", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
const untrustedSignature = <GppMaybeOutlinedIcon sx={{ color: "#52637A!important", padding:"0.2rem", background: "#ECEFF1", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
const verifiedSignature = <GppGoodOutlinedIcon sx={{ color: "#43A047!important", padding:"0.2rem", background: "#E8F5E9", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
// const signatureCheck = () => {
|
||||
// const unverifiedSignature = <GppBadOutlinedIcon sx={{ color: "#E53935!important", padding:"0.2rem", background: "#FEEBEE", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
// const untrustedSignature = <GppMaybeOutlinedIcon sx={{ color: "#52637A!important", padding:"0.2rem", background: "#ECEFF1", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
// const verifiedSignature = <GppGoodOutlinedIcon sx={{ color: "#43A047!important", padding:"0.2rem", background: "#E8F5E9", borderRadius: "1rem", height:"1.5rem", width:"1.6rem" }} />;
|
||||
|
||||
const arrSignature = [unverifiedSignature, untrustedSignature, verifiedSignature]
|
||||
return(getRandom(arrSignature));
|
||||
}
|
||||
// const arrSignature = [unverifiedSignature, untrustedSignature, verifiedSignature]
|
||||
// return(getRandom(arrSignature));
|
||||
// }
|
||||
|
||||
return (
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
@ -107,7 +107,7 @@ function PreviewCard(props) {
|
||||
<CardContent className={classes.content}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid container item xs={12}>
|
||||
<Stack direction="row" spacing={3} sx={{display:"flex",alignItems:"center", flexWrap:"wrap"}}>
|
||||
<Stack direction="row" spacing={3} sx={{display:"flex",alignItems:"center", flexWrap:"wrap", }}>
|
||||
<CardMedia classes={{
|
||||
root: classes.media,
|
||||
img: classes.avatar,
|
||||
@ -116,17 +116,17 @@ function PreviewCard(props) {
|
||||
image={randomImage()}
|
||||
alt="icon"
|
||||
/>
|
||||
<Typography variant="h5" component="div" sx={{size:"1.5rem", lineHeight:"2rem", color:"#220052"}}>
|
||||
<Typography variant="h5" component="div" sx={{size:"1.5rem", lineHeight:"2rem", color:"#220052", width:"10rem",whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}}>
|
||||
{name}
|
||||
</Typography>
|
||||
{vulnerabilityCheck()}
|
||||
{signatureCheck()}
|
||||
{/* {vulnerabilityCheck()}
|
||||
{signatureCheck()} */}
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} mt={2}>
|
||||
<Stack alignItems="flex-end" justifyContent="space-between" direction="row">
|
||||
<Typography variant="body2" sx={{fontSize:"0.875rem", lineHeight:"143%", letterSpacing:"0.010625rem"}}>Official</Typography>
|
||||
<BookmarkBorderOutlinedIcon/>
|
||||
{/* <BookmarkBorderOutlinedIcon/> */}
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
|
@ -93,62 +93,69 @@ const useStyles = makeStyles(() => ({
|
||||
function RepoCard(props) {
|
||||
const classes = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const { name, vendor, description, lastUpdated, downloads, rating, version } =
|
||||
const { name, vendor, platforms, description, lastUpdated, downloads, rating, version } =
|
||||
props;
|
||||
|
||||
|
||||
//function that returns a random element from an array
|
||||
function getRandom(list) {
|
||||
return list[Math.floor(Math.random() * list.length)];
|
||||
}
|
||||
// function getRandom(list) {
|
||||
// return list[Math.floor(Math.random() * list.length)];
|
||||
// }
|
||||
|
||||
const goToDetails = (repo) => {
|
||||
navigate(`/image/${name}`);
|
||||
const goToDetails = () => {
|
||||
navigate(`/image/${encodeURIComponent(name)}`);
|
||||
};
|
||||
|
||||
const vulnerabilityCheck = () => {
|
||||
const noneVulnerability = <Chip label="None Vulnerability" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
const unknownVulnerability = <Chip label="Unknown Vulnerability" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
const lowVulnerability = <Chip label="Low Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
const mediumVulnerability = <Chip label="Medium Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
const highVulnerability = <Chip label="High Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
const criticalVulnerability = <Chip label="Critical Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const vulnerabilityCheck = () => {
|
||||
// const noneVulnerability = <Chip label="None Vulnerability" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
// const unknownVulnerability = <Chip label="Unknown Vulnerability" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
// const lowVulnerability = <Chip label="Low Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
// const mediumVulnerability = <Chip label="Medium Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
// const highVulnerability = <Chip label="High Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const criticalVulnerability = <Chip label="Critical Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
|
||||
const arrVulnerability = [noneVulnerability, unknownVulnerability, lowVulnerability, mediumVulnerability, highVulnerability, criticalVulnerability]
|
||||
return(getRandom(arrVulnerability));
|
||||
};
|
||||
// const arrVulnerability = [noneVulnerability, unknownVulnerability, lowVulnerability, mediumVulnerability, highVulnerability, criticalVulnerability]
|
||||
// return(getRandom(arrVulnerability));
|
||||
// };
|
||||
|
||||
const signatureCheck = () => {
|
||||
const unverifiedSignature = <Chip label="Unverified Signature" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppBadOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
const untrustedSignature = <Chip label="Untrusted Signature" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppMaybeOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
const verifiedSignature = <Chip label="Verified Signature" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppGoodOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
// const signatureCheck = () => {
|
||||
// const unverifiedSignature = <Chip label="Unverified Signature" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppBadOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const untrustedSignature = <Chip label="Untrusted Signature" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppMaybeOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
// const verifiedSignature = <Chip label="Verified Signature" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppGoodOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
|
||||
const arrSignature = [unverifiedSignature, untrustedSignature, verifiedSignature]
|
||||
return(getRandom(arrSignature));
|
||||
}
|
||||
// const arrSignature = [unverifiedSignature, untrustedSignature, verifiedSignature]
|
||||
// return(getRandom(arrSignature));
|
||||
// }
|
||||
|
||||
const platformChips = () => {
|
||||
// if platforms not received, mock data
|
||||
const platforms = props.platforms || [
|
||||
"Windows",
|
||||
"PowerPC64LE",
|
||||
"IBM Z",
|
||||
"Linux",
|
||||
];
|
||||
return platforms.map((platform, index) => (
|
||||
<Chip
|
||||
key={index}
|
||||
label={platform}
|
||||
sx={{
|
||||
backgroundColor: "#E0E5EB",
|
||||
color: "#52637A",
|
||||
fontSize: "0.8125rem",
|
||||
}}
|
||||
/>
|
||||
const platformsOsArch = platforms || [];
|
||||
return platformsOsArch.map((platform,index) => (
|
||||
<Stack key={`stack${platform?.Os}${platform?.Arch}`} alignItems="center" direction="row" spacing={2}>
|
||||
<Chip
|
||||
key={`${name}${platform?.Os}${index}`}
|
||||
label={platform?.Os}
|
||||
sx={{
|
||||
backgroundColor: "#E0E5EB",
|
||||
color: "#52637A",
|
||||
fontSize: "0.8125rem",
|
||||
}}
|
||||
/>
|
||||
<Chip
|
||||
key={`${name}${platform?.Arch}${index}`}
|
||||
label={platform?.Arch}
|
||||
sx={{
|
||||
backgroundColor: "#E0E5EB",
|
||||
color: "#52637A",
|
||||
fontSize: "0.8125rem",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
));
|
||||
};
|
||||
|
||||
const getVendor = () => {
|
||||
return `${vendor || "andrewc"} •`;
|
||||
return `${vendor || "N/A"} •`;
|
||||
};
|
||||
const getVersion = () => {
|
||||
const lastDate = lastUpdated
|
||||
@ -182,8 +189,8 @@ function RepoCard(props) {
|
||||
<Typography variant="h5" component="div">
|
||||
{name}
|
||||
</Typography>
|
||||
{vulnerabilityCheck()}
|
||||
{signatureCheck()}
|
||||
{/* {vulnerabilityCheck()}
|
||||
{signatureCheck()} */}
|
||||
{/* <Chip label="Verified licensee" sx={{ backgroundColor: "#E8F5E9", color: "#388E3C" }} variant="filled" onDelete={() => { return }} deleteIcon={vulnerabilityCheck()} /> */}
|
||||
</Stack>
|
||||
<Typography
|
||||
@ -193,7 +200,7 @@ function RepoCard(props) {
|
||||
gutterBottom
|
||||
>
|
||||
{description ||
|
||||
"The complete solution for node.js command-line programs"}
|
||||
"N/A"}
|
||||
</Typography>
|
||||
<Stack alignItems="center" direction="row" spacing={2} pt={1}>
|
||||
{platformChips()}
|
||||
@ -218,14 +225,14 @@ function RepoCard(props) {
|
||||
className={classes.contentRight}
|
||||
>
|
||||
<Stack direction="column" alignItems="flex-end">
|
||||
<Typography variant="body2">
|
||||
{/* <Typography variant="body2">
|
||||
Downloads • {downloads || "-"}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
Rating • {rating || "-"}
|
||||
</Typography>
|
||||
</Typography> */}
|
||||
</Stack>
|
||||
<BookmarkIcon sx={{color:"#52637A"}}/>
|
||||
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -82,7 +82,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
flexDirection:"row",
|
||||
alignItems:"start",
|
||||
background:"#FFFFFF",
|
||||
border: "1px solid #E0E5EB",
|
||||
border: "0.0625rem solid #E0E5EB",
|
||||
borderRadius:"2rem",
|
||||
flex:"none",
|
||||
alignSelf:"stretch",
|
||||
@ -101,7 +101,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
},
|
||||
inputForm:{
|
||||
'& fieldset':{
|
||||
border: "2px solid #52637A",
|
||||
border: "0.125rem solid #52637A",
|
||||
},
|
||||
|
||||
},
|
||||
@ -142,12 +142,14 @@ function RepoDetails (props) {
|
||||
let repoInfo = response.data.data.ExpandedRepoInfo;
|
||||
let imageData = {
|
||||
name: name,
|
||||
tags: repoInfo.Manifests,
|
||||
images: repoInfo.Images,
|
||||
lastUpdated: repoInfo.Summary?.LastUpdated,
|
||||
size: repoInfo.Summary?.Size,
|
||||
//latestDigest: repoInfo.Images[0].Digest,
|
||||
//layers: repoInfo.Images[0].Layers,
|
||||
platforms: repoInfo.Summary?.Platforms,
|
||||
vendors: repoInfo.Summary?.Vendors,
|
||||
newestTag: repoInfo.Summary?.NewestTag
|
||||
newestTag: repoInfo.Summary?.NewestImage
|
||||
}
|
||||
setRepoDetailData(imageData);
|
||||
setIsLoading(false);
|
||||
@ -159,41 +161,57 @@ function RepoDetails (props) {
|
||||
});
|
||||
}, [name])
|
||||
//function that returns a random element from an array
|
||||
function getRandom(list) {
|
||||
return list[Math.floor(Math.random() * list.length)];
|
||||
}
|
||||
// function getRandom(list) {
|
||||
// return list[Math.floor(Math.random() * list.length)];
|
||||
// }
|
||||
|
||||
const vulnerabilityCheck = () => {
|
||||
const noneVulnerability = <Chip label="None Vulnerability" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
const unknownVulnerability = <Chip label="Unknown Vulnerability" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
const lowVulnerability = <Chip label="Low Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
const mediumVulnerability = <Chip label="Medium Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
const highVulnerability = <Chip label="High Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
const criticalVulnerability = <Chip label="Critical Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const vulnerabilityCheck = () => {
|
||||
// const noneVulnerability = <Chip label="None Vulnerability" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
// const unknownVulnerability = <Chip label="Unknown Vulnerability" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
// const lowVulnerability = <Chip label="Low Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
// const mediumVulnerability = <Chip label="Medium Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
// const highVulnerability = <Chip label="High Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const criticalVulnerability = <Chip label="Critical Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
|
||||
const arrVulnerability = [noneVulnerability, unknownVulnerability, lowVulnerability, mediumVulnerability, highVulnerability, criticalVulnerability]
|
||||
return(getRandom(arrVulnerability));
|
||||
};
|
||||
// const arrVulnerability = [noneVulnerability, unknownVulnerability, lowVulnerability, mediumVulnerability, highVulnerability, criticalVulnerability]
|
||||
// return(getRandom(arrVulnerability));
|
||||
// };
|
||||
|
||||
const signatureCheck = () => {
|
||||
const unverifiedSignature = <Chip label="Unverified Signature" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppBadOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
const untrustedSignature = <Chip label="Untrusted Signature" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppMaybeOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
const verifiedSignature = <Chip label="Verified Signature" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppGoodOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
// const signatureCheck = () => {
|
||||
// const unverifiedSignature = <Chip label="Unverified Signature" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppBadOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const untrustedSignature = <Chip label="Untrusted Signature" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppMaybeOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
// const verifiedSignature = <Chip label="Verified Signature" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppGoodOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
|
||||
const arrSignature = [unverifiedSignature, untrustedSignature, verifiedSignature]
|
||||
return(getRandom(arrSignature));
|
||||
}
|
||||
// const arrSignature = [unverifiedSignature, untrustedSignature, verifiedSignature]
|
||||
// return(getRandom(arrSignature));
|
||||
// }
|
||||
|
||||
|
||||
const platformChips = () => {
|
||||
// if platforms not received, mock data
|
||||
// @ts-ignore
|
||||
const platforms = repoDetailData.platforms || ["Windows","PowerPC64LE","IBM Z","Linux"];
|
||||
const platforms = repoDetailData?.platforms || [];
|
||||
|
||||
return platforms.map((platform, index) => (
|
||||
<Chip key={index} label={platform.Os} sx={{
|
||||
backgroundColor: "#E0E5EB",
|
||||
color: "#52637A",
|
||||
fontSize: "0.8125rem",
|
||||
}}/>
|
||||
<Stack key={`stack${platform?.Os}${platform?.Arch}`} alignItems="center" direction="row" spacing={2}>
|
||||
<Chip
|
||||
key={`${name}${platform?.Os}${index}`}
|
||||
label={platform?.Os}
|
||||
sx={{
|
||||
backgroundColor: "#E0E5EB",
|
||||
color: "#52637A",
|
||||
fontSize: "0.8125rem",
|
||||
}}
|
||||
/>
|
||||
<Chip
|
||||
key={`${name}${platform?.Arch}${index}`}
|
||||
label={platform?.Arch}
|
||||
sx={{
|
||||
backgroundColor: "#E0E5EB",
|
||||
color: "#52637A",
|
||||
fontSize: "0.8125rem",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
));
|
||||
}
|
||||
|
||||
@ -207,7 +225,7 @@ function RepoDetails (props) {
|
||||
<Card className={classes.card} data-testid='overview-container'>
|
||||
<CardContent>
|
||||
<Typography variant="h4" align="left">{overviewTitle || 'Quickstart'}</Typography>
|
||||
<Typography variant="body1" sx={{color:"rgba(0, 0, 0, 0.6)", fontSize:"1rem",lineHeight:"150%", marginTop:"5%", alignSelf:"stretch"}}>{description || mockData.loremIpsum}</Typography>
|
||||
<Typography variant="body1" sx={{color:"rgba(0, 0, 0, 0.6)", fontSize:"1rem",lineHeight:"150%", marginTop:"5%", alignSelf:"stretch"}}>{description || "N/A"}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@ -256,12 +274,12 @@ function RepoDetails (props) {
|
||||
<Typography variant="h3" className={classes.repoName}>
|
||||
{name}
|
||||
</Typography>
|
||||
{vulnerabilityCheck()}
|
||||
{signatureCheck()}
|
||||
<BookmarkIcon sx={{color:"#52637A"}}/>
|
||||
{/* {vulnerabilityCheck()}
|
||||
{signatureCheck()} */}
|
||||
{/* <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">
|
||||
{description || 'The complete solution for node.js command-line programs'}
|
||||
{description || 'N/A'}
|
||||
</Typography>
|
||||
<Stack alignItems="center" sx={{ paddingLeft:"4rem"}} direction="row" spacing={2} pt={1}>
|
||||
{platformChips()}
|
||||
@ -271,9 +289,10 @@ function RepoDetails (props) {
|
||||
<Typography variant="body1" sx={{color:"#52637A", fontSize: "1rem"}}>Copy and pull to pull this image</Typography>
|
||||
<FormControl sx={{ m: 1, paddingLeft:"1.5rem"}} variant="outlined">
|
||||
<OutlinedInput
|
||||
value={`Pull ${name}`}
|
||||
// value={`Pull ${name}`}
|
||||
value= 'N/A'
|
||||
className={classes.inputForm}
|
||||
sx={{ m: 1, width: '20.625rem', borderRadius: "8px", color: "#14191F"}}
|
||||
sx={{ m: 1, width: '20.625rem', borderRadius: "0.5rem", color: "#14191F"}}
|
||||
endAdornment={
|
||||
<InputAdornment position="end" >
|
||||
<IconButton aria-label='copy' edge="end" onClick={() => navigator.clipboard.writeText(`Pull ${name}`)} data-testid='pullcopy-btn'>
|
||||
@ -332,11 +351,11 @@ function RepoDetails (props) {
|
||||
<Grid item xs={4} className={classes.metadata}>
|
||||
<RepoDetailsMetadata
|
||||
// @ts-ignore
|
||||
lastUpdated={repoDetailData.lastUpdated}
|
||||
lastUpdated={repoDetailData?.lastUpdated}
|
||||
// @ts-ignore
|
||||
size={repoDetailData.size}
|
||||
size={repoDetailData?.size}
|
||||
// @ts-ignore
|
||||
latestTag={repoDetailData.newestTag}
|
||||
latestTag={repoDetailData?.newestTag}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -43,7 +43,7 @@ function RepoDetailsMetadata (props) {
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant="body2" align="left" className={classes.metadataHeader}>Repository</Typography>
|
||||
<Typography variant="body1" className={classes.metadataBody}>{repoURL || `----`}</Typography>
|
||||
<Typography variant="body1" align="left" className={classes.metadataBody}>{repoURL || `N/A`}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
@ -51,7 +51,7 @@ function RepoDetailsMetadata (props) {
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant="body2" align="left" className={classes.metadataHeader}>Weekly downloads</Typography>
|
||||
<Typography variant="body1" align="left" className={classes.metadataBody}>{weeklyDownloads || `----`}</Typography>
|
||||
<Typography variant="body1" align="left" className={classes.metadataBody}>{weeklyDownloads || `N/A`}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
@ -60,7 +60,7 @@ function RepoDetailsMetadata (props) {
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant="body2" align="left" className={classes.metadataHeader}>Last publish</Typography>
|
||||
<Typography variant="body1" className={classes.metadataBody}>{lastDate || `35 days ago`}</Typography>
|
||||
<Typography variant="body1" align="left" className={classes.metadataBody}>{lastDate || `35 days ago`}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
@ -68,12 +68,12 @@ function RepoDetailsMetadata (props) {
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant="body2" align="left" className={classes.metadataHeader}>Total size</Typography>
|
||||
<Typography variant="body1" className={classes.metadataBody}>{transform.formatBytes(size) || `----`}</Typography>
|
||||
<Typography variant="body1" align="left" className={classes.metadataBody}>{transform.formatBytes(size) || `----`}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container item xs={12} spacing={2}>
|
||||
{/* <Grid container item xs={12} spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
<CardContent>
|
||||
@ -82,7 +82,7 @@ function RepoDetailsMetadata (props) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid> */}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
margin: "0 auto",
|
||||
padding: "10px",
|
||||
padding: "0.625rem",
|
||||
position: "relative",
|
||||
},
|
||||
loginCard: {
|
||||
@ -42,7 +42,8 @@ const useStyles = makeStyles((theme) => ({
|
||||
background: "#FFFFFF",
|
||||
gap: "0.625em",
|
||||
boxShadow: "0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)",
|
||||
borderRadius: "1.5rem",
|
||||
borderRadius: "1.5rem",
|
||||
minWidth: "30rem"
|
||||
},
|
||||
loginCardContent: {
|
||||
alignItems: "center",
|
||||
@ -65,33 +66,33 @@ const useStyles = makeStyles((theme) => ({
|
||||
|
||||
},
|
||||
textField: {
|
||||
borderRadius: "4px",
|
||||
borderRadius: "0.25rem",
|
||||
},
|
||||
button: {
|
||||
textTransform: "none",
|
||||
color: "##FFFFFF",
|
||||
fontSize: "1.4375rem",
|
||||
fontWeight: "500",
|
||||
height: "50px",
|
||||
borderRadius: "4px",
|
||||
height: "3.125rem",
|
||||
borderRadius: "0.25rem",
|
||||
letterSpacing:"0.01rem",
|
||||
},
|
||||
gitLogo: {
|
||||
height: "24px",
|
||||
borderRadius: "4px",
|
||||
borderRadius: "0.25rem",
|
||||
paddingLeft: "1rem",
|
||||
},
|
||||
line: {
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
borderBottom: "1px solid #C2CBD6",
|
||||
borderBottom: "0.0625rem solid #C2CBD6",
|
||||
lineHeight: "0.1rem",
|
||||
margin: "10px 0 20px",
|
||||
margin: "0.625rem 0 1.25rem",
|
||||
},
|
||||
lineSpan:{
|
||||
background: "#ffffff",
|
||||
color: "#C2CBD6",
|
||||
padding: "0 10px",
|
||||
padding: "0 0.625rem",
|
||||
fontSize: "1rem",
|
||||
fontWeight: "400",
|
||||
paddingLeft: "1rem",
|
||||
|
307
src/components/TagDetails.jsx
Normal file
307
src/components/TagDetails.jsx
Normal file
@ -0,0 +1,307 @@
|
||||
// react global
|
||||
import { useParams } from 'react-router-dom'
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
// utility
|
||||
import {api, endpoints} from '../api';
|
||||
import mockData from '../utilities/mockData';
|
||||
|
||||
// components
|
||||
import {Box, Card, CardContent, CardMedia, Chip, FormControl, Grid, IconButton, InputAdornment, OutlinedInput, Stack, Tab, Typography} from '@mui/material';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { host } from '../host';
|
||||
import PestControlOutlinedIcon from "@mui/icons-material/PestControlOutlined";
|
||||
import PestControlIcon from "@mui/icons-material/PestControl";
|
||||
import GppBadOutlinedIcon from "@mui/icons-material/GppBadOutlined";
|
||||
import GppGoodOutlinedIcon from "@mui/icons-material/GppGoodOutlined";
|
||||
import GppMaybeOutlinedIcon from "@mui/icons-material/GppMaybeOutlined";
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import BookmarkIcon from '@mui/icons-material/Bookmark';
|
||||
|
||||
// placeholder images
|
||||
import repocube1 from '../assets/repocube-1.png';
|
||||
import repocube2 from '../assets/repocube-2.png';
|
||||
import repocube3 from '../assets/repocube-3.png';
|
||||
import repocube4 from '../assets/repocube-4.png';
|
||||
import { TabContext, TabList, TabPanel } from '@mui/lab';
|
||||
import TagDetailsMetadata from './TagDetailsMetadata';
|
||||
import VulnerabilitiesDetails from './VulnerabilitiesDetails';
|
||||
import { padding } from '@mui/system';
|
||||
|
||||
// @ts-ignore
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
pageWrapper: {
|
||||
backgroundColor: "#FFFFFF",
|
||||
height: '100vh',
|
||||
},
|
||||
container: {
|
||||
paddingTop: 5,
|
||||
paddingBottom: 5,
|
||||
marginTop: 100,
|
||||
backgroundColor: "#FFFFFF",
|
||||
},
|
||||
repoName: {
|
||||
fontWeight:"700",
|
||||
fontSize:"2.5rem",
|
||||
color:"#0F2139"
|
||||
},
|
||||
avatar: {
|
||||
height:"3rem",
|
||||
width:"3rem"
|
||||
},
|
||||
cardBtn: {
|
||||
height: "100%",
|
||||
width: "100%"
|
||||
},
|
||||
media: {
|
||||
borderRadius: '3.125em',
|
||||
},
|
||||
tabs: {
|
||||
marginTop: "3rem",
|
||||
padding:"0.5rem",
|
||||
height: "100%"
|
||||
},
|
||||
tabContent:{
|
||||
height:"100%"
|
||||
},
|
||||
selectedTab: {
|
||||
background:"#D83C0E",
|
||||
borderRadius:"1.5rem"
|
||||
},
|
||||
tabPanel: {
|
||||
height:"100%",
|
||||
paddingLeft: "0rem!important"
|
||||
},
|
||||
metadata: {
|
||||
marginTop: "8rem",
|
||||
paddingLeft:"1.5rem",
|
||||
},
|
||||
card: {
|
||||
marginBottom: 2,
|
||||
display:"flex",
|
||||
flexDirection:"row",
|
||||
alignItems:"start",
|
||||
background:"#FFFFFF",
|
||||
border: "0.0625rem solid #E0E5EB",
|
||||
borderRadius:"2rem",
|
||||
flex:"none",
|
||||
alignSelf:"stretch",
|
||||
flexGrow:0,
|
||||
order:0,
|
||||
width:"100%",
|
||||
boxShadow: "none!important"
|
||||
},
|
||||
platformText:{
|
||||
backgroundColor:"#EDE7F6",
|
||||
color: "#220052",
|
||||
fontWeight:'400',
|
||||
fontSize:'0.8125rem',
|
||||
lineHeight:'1.125rem',
|
||||
letterSpacing:'0.01rem'
|
||||
},
|
||||
inputForm:{
|
||||
'& fieldset':{
|
||||
border: "0.125rem solid #52637A",
|
||||
},
|
||||
|
||||
},
|
||||
cardRoot:{
|
||||
boxShadow: "none!important",
|
||||
},
|
||||
header:{
|
||||
paddingLeft:"2rem"
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// temporary utility to get image
|
||||
const randomIntFromInterval = (min, max) => {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min)
|
||||
};
|
||||
|
||||
const randomImage = () => {
|
||||
const imageArray = [repocube1,repocube2,repocube3,repocube4];
|
||||
return imageArray[randomIntFromInterval(0,3)];
|
||||
};
|
||||
|
||||
function TagDetails (props) {
|
||||
const [repoDetailData, setRepoDetailData] = useState({});
|
||||
// @ts-ignore
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedTab, setSelectedTab] = useState("Vulnerabilities");
|
||||
|
||||
// get url param from <Route here (i.e. image name)
|
||||
const {name} = useParams();
|
||||
const classes = useStyles();
|
||||
const {description, overviewTitle, dependencies, dependents} = props;
|
||||
|
||||
useEffect(() => {
|
||||
api.get(`${host()}${endpoints.detailedRepoInfo(name)}`)
|
||||
.then(response => {
|
||||
if (response.data && response.data.data) {
|
||||
let repoInfo = response.data.data.ExpandedRepoInfo;
|
||||
let imageData = {
|
||||
name: name,
|
||||
tags: repoInfo.Images[0].Tag,
|
||||
lastUpdated: repoInfo.Summary?.LastUpdated,
|
||||
size: repoInfo.Summary?.Size,
|
||||
latestDigest: repoInfo.Images[0].Digest,
|
||||
layers: repoInfo.Images[0].Layers,
|
||||
platforms: repoInfo.Summary?.Platforms,
|
||||
vendors: repoInfo.Summary?.Vendors,
|
||||
newestTag: repoInfo.Summary?.NewestImage
|
||||
}
|
||||
setRepoDetailData(imageData);
|
||||
setIsLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setRepoDetailData({});
|
||||
});
|
||||
}, [name])
|
||||
//function that returns a random element from an array
|
||||
// function getRandom(list) {
|
||||
// return list[Math.floor(Math.random() * list.length)];
|
||||
// }
|
||||
|
||||
// const vulnerabilityCheck = () => {
|
||||
// const noneVulnerability = <Chip label="No Vulnerability" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
// const unknownVulnerability = <Chip label="Unknown Vulnerability" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
// const lowVulnerability = <Chip label="Low Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
// const mediumVulnerability = <Chip label="Medium Vulnerability" sx={{backgroundColor: "#FFF3E0",color: "#FB8C00",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#FB8C00!important" }} />}/>;
|
||||
// const highVulnerability = <Chip label="High Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const criticalVulnerability = <Chip label="Critical Vulnerability" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <PestControlIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
|
||||
// const arrVulnerability = [noneVulnerability, unknownVulnerability, lowVulnerability, mediumVulnerability, highVulnerability, criticalVulnerability]
|
||||
// return(getRandom(arrVulnerability));
|
||||
// };
|
||||
|
||||
// const signatureCheck = () => {
|
||||
// const unverifiedSignature = <Chip label="Unverified Signature" sx={{backgroundColor: "#FEEBEE",color: "#E53935",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppBadOutlinedIcon sx={{ color: "#E53935!important" }} />}/>;
|
||||
// const untrustedSignature = <Chip label="Untrusted Signature" sx={{backgroundColor: "#ECEFF1",color: "#52637A",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppMaybeOutlinedIcon sx={{ color: "#52637A!important" }} />}/>;
|
||||
// const verifiedSignature = <Chip label="Verified Signature" sx={{backgroundColor: "#E8F5E9",color: "#388E3C",fontSize: "0.8125rem",}} variant="filled" onDelete={() => { return; }} deleteIcon={ <GppGoodOutlinedIcon sx={{ color: "#388E3C!important" }} />}/>;
|
||||
|
||||
// const arrSignature = [unverifiedSignature, untrustedSignature, verifiedSignature]
|
||||
// return(getRandom(arrSignature));
|
||||
// }
|
||||
|
||||
const getPlatform = () => {
|
||||
// @ts-ignore
|
||||
return repoDetailData?.platforms? repoDetailData.platforms[0] : '--/--';
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const handleTabChange = (event, newValue) => {
|
||||
setSelectedTab(newValue);
|
||||
};
|
||||
|
||||
//will need this but not for now
|
||||
// const renderDependencies = () => {
|
||||
// return (<Card className={classes.card}>
|
||||
// <CardContent>
|
||||
// <Typography variant="h4" align="left">Dependecies ({dependencies || '---'})</Typography>
|
||||
// </CardContent>
|
||||
// </Card>);
|
||||
// };
|
||||
|
||||
// const renderDependents = () => {
|
||||
// return (<Card className={classes.card}>
|
||||
// <CardContent>
|
||||
// <Typography variant="h4" align="left">Dependents ({dependents || '---'})</Typography>
|
||||
// </CardContent>
|
||||
// </Card>);
|
||||
// };
|
||||
|
||||
// const renderVulnerabilities = () => {
|
||||
// return (<Card className={classes.card}>
|
||||
// <CardContent>
|
||||
// <Typography variant="h4" align="left">Vulnerabilities</Typography>
|
||||
// </CardContent>
|
||||
// </Card>);
|
||||
// };
|
||||
|
||||
|
||||
return (
|
||||
<div className={classes.pageWrapper}>
|
||||
<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"
|
||||
image={randomImage()}
|
||||
alt="icon"
|
||||
/>
|
||||
<Typography variant="h3" className={classes.repoName}>
|
||||
{name}:{repoDetailData?.
|
||||
// @ts-ignore
|
||||
tags}
|
||||
</Typography>
|
||||
{/* {vulnerabilityCheck()}
|
||||
{signatureCheck()} */}
|
||||
{/* <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">
|
||||
Digest: {repoDetailData?.
|
||||
// @ts-ignore
|
||||
latestDigest}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
<Grid container>
|
||||
<Grid item xs={8} className={classes.tabs}>
|
||||
<TabContext value={selectedTab}>
|
||||
<Box >
|
||||
<TabList
|
||||
onChange={handleTabChange}
|
||||
TabIndicatorProps={{ className: classes.selectedTab }}
|
||||
sx={{ "& button.Mui-selected": {color:"#14191F", fontWeight:"600"}}}
|
||||
>
|
||||
{/* <Tab value="Layers" label="Layers" className={classes.tabContent}/>
|
||||
<Tab value="DependsOn" label="Depends on" className={classes.tabContent}/>
|
||||
<Tab value="IsDependentOn" label="Is Dependent On" className={classes.tabContent}/> */}
|
||||
<Tab value="Vulnerabilities" label="Vulnerabilities" className={classes.tabContent}/>
|
||||
</TabList>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
{/* <TabPanel value="Layers" className={classes.tabPanel}>
|
||||
<Typography> Layers </Typography>
|
||||
</TabPanel>
|
||||
<TabPanel value="DependsOn" className={classes.tabPanel}>
|
||||
<Typography> Depends On </Typography>
|
||||
</TabPanel>
|
||||
<TabPanel value="IsDependentOn" className={classes.tabPanel}>
|
||||
<Typography> Is Dependent On </Typography>
|
||||
</TabPanel> */}
|
||||
<TabPanel value="Vulnerabilities" className={classes.tabPanel}>
|
||||
<VulnerabilitiesDetails name={name}/>
|
||||
</TabPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</TabContext>
|
||||
</Grid>
|
||||
<Grid item xs={4} className={classes.metadata}>
|
||||
<TagDetailsMetadata
|
||||
// @ts-ignore
|
||||
platforms={getPlatform()}
|
||||
// @ts-ignore
|
||||
size={repoDetailData?.size}
|
||||
// @ts-ignore
|
||||
lastUpdated={repoDetailData?.lastUpdated}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TagDetails;
|
72
src/components/TagDetailsMetadata.jsx
Normal file
72
src/components/TagDetailsMetadata.jsx
Normal file
@ -0,0 +1,72 @@
|
||||
import { Card, CardContent, Grid, Typography } from '@mui/material';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { DateTime } from 'luxon';
|
||||
import React from 'react';
|
||||
import transform from '../utilities/transform';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
card: {
|
||||
marginBottom: 2,
|
||||
display:"flex",
|
||||
flexDirection:"row",
|
||||
alignItems:"start",
|
||||
background:"#FFFFFF",
|
||||
boxShadow:"0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)",
|
||||
borderRadius:"1.5rem",
|
||||
flex:"none",
|
||||
alignSelf:"stretch",
|
||||
flexGrow:0,
|
||||
order:0,
|
||||
width:"100%"
|
||||
},
|
||||
metadataHeader: {
|
||||
color: "rgba(0, 0, 0, 0.6)"
|
||||
},
|
||||
metadataBody: {
|
||||
color: "rgba(0, 0, 0, 0.87)",
|
||||
fontFamily: 'Roboto',
|
||||
fontStyle: "normal",
|
||||
fontWeight: 400,
|
||||
fontSize: "1rem",
|
||||
lineHeight: "150%",
|
||||
align:"left"
|
||||
}
|
||||
}));
|
||||
|
||||
function TagDetailsMetadata (props) {
|
||||
const classes = useStyles();
|
||||
const {platforms, lastUpdated, size} = props;
|
||||
const lastDate = (lastUpdated ? DateTime.fromISO(lastUpdated) : DateTime.now().minus({ days: 1 })).toRelative({ unit: 'days' })
|
||||
return (
|
||||
<Grid container spacing={1} data-testid='tagDetailsMetadata-container'>
|
||||
<Grid container item xs={12}>
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant="body2" align="left" className={classes.metadataHeader}>OS/Arch</Typography>
|
||||
<Typography variant="body1" className={classes.metadataBody}>{platforms.Os || `----`} / {platforms.Arch || `----`}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid container item xs={12}>
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant="body2" align="left" className={classes.metadataHeader}>Total Size</Typography>
|
||||
<Typography variant="body1" align="left" className={classes.metadataBody}>{transform.formatBytes(size) || `----`}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid container item xs={12} spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Card variant="outlined" className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant="body2" align="left" className={classes.metadataHeader}>Last Published</Typography>
|
||||
<Typography variant="body1" align="left" className={classes.metadataBody}>{lastDate || `----`}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagDetailsMetadata;
|
@ -1,78 +1,117 @@
|
||||
// react global
|
||||
import * as React from 'react';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import PropTypes from 'prop-types';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
// components
|
||||
import Box from '@mui/material/Box';
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell, {tableCellClasses} from '@mui/material/TableCell';
|
||||
import TableCell, { tableCellClasses } from '@mui/material/TableCell';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import transform from 'utilities/transform';
|
||||
import { Card, CardContent, Divider } from '@mui/material';
|
||||
import { Card, CardContent, Divider, Stack } from '@mui/material';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
tagCard: {
|
||||
marginBottom: 2,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
background: "#FFFFFF",
|
||||
boxShadow: "none!important",
|
||||
borderRadius: "1.875rem",
|
||||
flex: "none",
|
||||
alignSelf: "stretch",
|
||||
flexGrow: 0,
|
||||
order: 0,
|
||||
width: "100%"
|
||||
},
|
||||
card: {
|
||||
marginBottom: 2,
|
||||
display:"flex",
|
||||
flexDirection:"row",
|
||||
alignItems:"center",
|
||||
background:"#FFFFFF",
|
||||
boxShadow:"0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)",
|
||||
borderRadius:"1.875rem",
|
||||
flex:"none",
|
||||
alignSelf:"stretch",
|
||||
flexGrow:0,
|
||||
order:0,
|
||||
width:"100%"
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
background: "#FFFFFF",
|
||||
boxShadow: "0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)",
|
||||
borderRadius: "1.875rem",
|
||||
flex: "none",
|
||||
alignSelf: "stretch",
|
||||
flexGrow: 0,
|
||||
order: 0,
|
||||
width: "100%"
|
||||
},
|
||||
content: {
|
||||
textAlign: "left",
|
||||
color: "#606060",
|
||||
padding: "2% 3% 2% 3%",
|
||||
width:"100%"
|
||||
width: "100%"
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
|
||||
|
||||
function TagCard(props) {
|
||||
const {data, row} = props;
|
||||
const tags = data && data.tags;
|
||||
const { row, lastUpdated, vendors, size, platform } = props;
|
||||
|
||||
//const tags = data && data.tags;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [digests, setDigests] = React.useState([]);
|
||||
const classes = useStyles();
|
||||
const tagRow = row;
|
||||
const lastDate = (lastUpdated ? DateTime.fromISO(lastUpdated) : DateTime.now().minus({ days: 1 })).toRelative({ unit: 'days' })
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const tagDigest = [{ digest: tagRow.Digest, osArch: platform[0], size: size }];
|
||||
setDigests(tagDigest);
|
||||
}, []);
|
||||
|
||||
const goToTags = (tag) => {
|
||||
navigate(`tag/${tag}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={classes.card} raised>
|
||||
<CardContent className={classes.content}>
|
||||
<Typography variant="body1" align="left" sx={{color:"#828282"}}>{row.Tag}</Typography>
|
||||
<Typography variant="caption">Last pushed {row.lastUpdated || '----'} by {row.vendor || '----'}</Typography>
|
||||
<Typography sx={{color:"#7C4DFF", cursor:'pointer'}} onClick={() => setOpen(!open)}>{!open? 'See layers' : 'Hide layers'}</Typography>
|
||||
<Typography variant="body1" align="left" sx={{ color: "#828282", fontSize: "1rem", paddingBottom: "0.5rem" }}>Tag</Typography>
|
||||
<Typography variant="body1" align="left" sx={{ color: "#1479FF", fontSize: "1rem", textDecorationLine: "underline", cursor: 'pointer' }} onClick={() => goToTags(tagRow.Tag)}>{tagRow.Tag}</Typography>
|
||||
|
||||
<Stack sx={{ display: "inline" }} direction="row" spacing={0.5}>
|
||||
<Typography variant="caption" sx={{ fontWeight: "400", fontSize: "0.8125rem" }} >
|
||||
Last pushed
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: "600", fontSize: "0.8125rem" }} >
|
||||
{lastDate || '----'} by {vendors[0] || 'N/A'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Typography sx={{ color: "#1479FF", paddingTop: "1rem", fontSize: "0.8125rem", fontWeight: "600", cursor: 'pointer' }} onClick={() => setOpen(!open)}>{!open ? 'See digests' : 'Hide digests'}</Typography>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
{
|
||||
// Layers
|
||||
}
|
||||
</Typography>
|
||||
<Table size="small" padding="none" sx={{[`& .${tableCellClasses.root}`]: {borderBottom: "none"}}}>
|
||||
<Table size="small" padding="none" sx={{ [`& .${tableCellClasses.root}`]: { borderBottom: "none" } }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{color: "#696969"}}><Typography variant="body1">Digest</Typography></TableCell>
|
||||
<TableCell style={{color: "#696969"}}><Typography variant="body1">OS/ARCH</Typography></TableCell>
|
||||
<TableCell style={{color: "#696969"}}><Typography variant="body1">Size</Typography></TableCell>
|
||||
<TableCell style={{ color: "#696969" }}><Typography variant="body1">Digest</Typography></TableCell>
|
||||
<TableCell style={{ color: "#696969" }}><Typography variant="body1">OS/ARCH</Typography></TableCell>
|
||||
<TableCell style={{ color: "#696969" }}><Typography variant="body1">Size</Typography></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{row.Layers.map((layer) => (
|
||||
<TableRow key={layer.Digest} onClick={() => {navigator.clipboard.writeText(layer.Digest)}}>
|
||||
<TableCell style={{color: "#696969"}}><Typography variant="body1">{layer.Digest?.substr(0,12)}</Typography></TableCell>
|
||||
<TableCell style={{color: "#696969"}}><Typography variant="body1">-----------</Typography></TableCell>
|
||||
<TableCell component="th" scope="row" style={{color: "#696969"}}>
|
||||
<Typography variant="body1">{transform.formatBytes(layer.Size)}</Typography>
|
||||
{digests.map((layer) => (
|
||||
<TableRow key={layer.digest} onClick={() => { navigator.clipboard.writeText(layer.digest) }}>
|
||||
<TableCell style={{ color: "#696969" }}><Typography variant="body1">{layer.digest?.substr(0, 12)}</Typography></TableCell>
|
||||
<TableCell style={{ color: "#696969" }}><Typography variant="body1"> {layer.osArch?.Os}/{layer.osArch?.Arch} </Typography></TableCell>
|
||||
<TableCell component="th" scope="row" style={{ color: "#696969" }}>
|
||||
<Typography variant="body1">{transform.formatBytes(layer.size)}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
@ -85,24 +124,24 @@ function TagCard(props) {
|
||||
);
|
||||
}
|
||||
|
||||
TagCard.propTypes = {
|
||||
row: PropTypes.shape({
|
||||
Layers: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
Digest: PropTypes.string.isRequired,
|
||||
Size: PropTypes.string.isRequired,
|
||||
}),
|
||||
).isRequired,
|
||||
Tag: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
// TagCard.propTypes = {
|
||||
// row: PropTypes.shape({
|
||||
// Layers: PropTypes.arrayOf(
|
||||
// PropTypes.shape({
|
||||
// Digest: PropTypes.string.isRequired,
|
||||
// Size: PropTypes.string.isRequired,
|
||||
// }),
|
||||
// ).isRequired,
|
||||
// Tag: PropTypes.string.isRequired,
|
||||
// }).isRequired,
|
||||
// };
|
||||
|
||||
|
||||
const renderTags = (tags) => {
|
||||
const renderTags = (tags, lastUpdated, vendors, size, platform) => {
|
||||
const cmp = tags && tags.map((tag, index) => {
|
||||
return (
|
||||
<TagCard key={tag.Tag} row={tag} />
|
||||
);
|
||||
return (
|
||||
<TagCard key={tag.Tag} row={tag} lastUpdated={lastUpdated} vendors={vendors} size={size} platform={platform} />
|
||||
);
|
||||
});
|
||||
return cmp;
|
||||
}
|
||||
@ -110,15 +149,16 @@ const renderTags = (tags) => {
|
||||
|
||||
export default function Tags(props) {
|
||||
const classes = useStyles();
|
||||
const {data} = props;
|
||||
const {tags} = data;
|
||||
const { data } = props;
|
||||
const { images, lastUpdated, vendors, size, platforms } = data;
|
||||
|
||||
|
||||
return (
|
||||
<Card className={classes.card} data-testid='tags-container'>
|
||||
<Card className={classes.tagCard} data-testid='tags-container'>
|
||||
<CardContent className={classes.content}>
|
||||
<Typography variant="h4" gutterBottom component="div" align="left" style={{color: "rgba(0, 0, 0, 0.87)"}}>Tags history</Typography>
|
||||
<Divider variant="fullWidth" sx={{margin:"5% 0% 5% 0%", background:"rgba(0, 0, 0, 0.38)", height:"0.0625rem", width:"100%"}}/>
|
||||
{renderTags(tags)}
|
||||
<Typography variant="h4" gutterBottom component="div" align="left" style={{ color: "rgba(0, 0, 0, 0.87)", fontSize: "1.5rem", fontWeight: "600" }}>Tags History</Typography>
|
||||
<Divider variant="fullWidth" sx={{ margin: "5% 0% 5% 0%", background: "rgba(0, 0, 0, 0.38)", height: "0.00625rem", width: "100%" }} />
|
||||
{renderTags(images, lastUpdated, vendors, size, platforms)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
175
src/components/VulnerabilitiesDetails.jsx
Normal file
175
src/components/VulnerabilitiesDetails.jsx
Normal file
@ -0,0 +1,175 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
// utility
|
||||
import { api, endpoints } from '../api';
|
||||
import mockData from '../utilities/mockData';
|
||||
|
||||
// components
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import { Box, Card, CardContent, Divider, Chip, FormControl, Grid, IconButton, InputAdornment, OutlinedInput, Stack, Tab, Typography } from '@mui/material';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { host } from '../host';
|
||||
import PestControlOutlinedIcon from "@mui/icons-material/PestControlOutlined";
|
||||
import PestControlIcon from "@mui/icons-material/PestControl";
|
||||
import GppBadOutlinedIcon from "@mui/icons-material/GppBadOutlined";
|
||||
import GppGoodOutlinedIcon from "@mui/icons-material/GppGoodOutlined";
|
||||
import GppMaybeOutlinedIcon from "@mui/icons-material/GppMaybeOutlined";
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
card: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
background: "#FFFFFF",
|
||||
boxShadow: "0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)",
|
||||
borderRadius: "1.875rem",
|
||||
flex: "none",
|
||||
alignSelf: "stretch",
|
||||
flexGrow: 0,
|
||||
order: 0,
|
||||
width: "100%",
|
||||
marginTop: "2rem",
|
||||
marginBottom: "2rem"
|
||||
},
|
||||
content: {
|
||||
textAlign: "left",
|
||||
color: "#606060",
|
||||
padding: "2% 3% 2% 3%",
|
||||
width: "100%"
|
||||
},
|
||||
title: {
|
||||
color: "#828282",
|
||||
fontSize: "1rem",
|
||||
paddingRight: "0.5rem",
|
||||
paddingBottom: "0.5rem",
|
||||
paddingTop: "0.5rem"
|
||||
|
||||
},
|
||||
values: {
|
||||
color: "#000000",
|
||||
fontSize: "1rem",
|
||||
fontWeight: "600",
|
||||
paddingBottom: "0.5rem",
|
||||
paddingTop: "0.5rem"
|
||||
}
|
||||
}));
|
||||
|
||||
const vulnerabilityCheck = (status) => {
|
||||
const noneVulnerability = <Chip label="None" sx={{ backgroundColor: "#E8F5E9", color: "#388E3C", fontSize: "0.8125rem", }} variant="filled" onDelete={() => { return; }} deleteIcon={<PestControlOutlinedIcon sx={{ color: "#388E3C!important" }} />} />;
|
||||
const unknownVulnerability = <Chip label="Unknown" sx={{ backgroundColor: "#ECEFF1", color: "#52637A", fontSize: "0.8125rem", }} variant="filled" onDelete={() => { return; }} deleteIcon={<PestControlOutlinedIcon sx={{ color: "#52637A!important" }} />} />;
|
||||
const lowVulnerability = <Chip label="Low" sx={{ backgroundColor: "#FFF3E0", color: "#FB8C00", fontSize: "0.8125rem", }} variant="filled" onDelete={() => { return; }} deleteIcon={<PestControlOutlinedIcon sx={{ color: "#FB8C00!important" }} />} />;
|
||||
const mediumVulnerability = <Chip label="Medium" sx={{ backgroundColor: "#FFF3E0", color: "#FB8C00", fontSize: "0.8125rem", }} variant="filled" onDelete={() => { return; }} deleteIcon={<PestControlIcon sx={{ color: "#FB8C00!important" }} />} />;
|
||||
const highVulnerability = <Chip label="High" sx={{ backgroundColor: "#FEEBEE", color: "#E53935", fontSize: "0.8125rem", }} variant="filled" onDelete={() => { return; }} deleteIcon={<PestControlOutlinedIcon sx={{ color: "#E53935!important" }} />} />;
|
||||
const criticalVulnerability = <Chip label="Critical" sx={{ backgroundColor: "#FEEBEE", color: "#E53935", fontSize: "0.8125rem", }} variant="filled" onDelete={() => { return; }} deleteIcon={<PestControlIcon sx={{ color: "#E53935!important" }} />} />;
|
||||
|
||||
let result;
|
||||
switch (status) {
|
||||
case "NONE":
|
||||
result = noneVulnerability;
|
||||
break;
|
||||
case "LOW":
|
||||
result = lowVulnerability;
|
||||
break;
|
||||
case "MEDIUM":
|
||||
result = mediumVulnerability;
|
||||
break;
|
||||
case "HIGH":
|
||||
result = highVulnerability;
|
||||
break;
|
||||
case "CRITICAL":
|
||||
result = criticalVulnerability;
|
||||
break;
|
||||
default:
|
||||
result = unknownVulnerability;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
function VulnerabilitiyCard(props) {
|
||||
const classes = useStyles();
|
||||
const { cve } = props;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<Card className={classes.card} raised>
|
||||
<CardContent className={classes.content}>
|
||||
<Stack sx={{ flexDirection: "row" }}>
|
||||
<Typography variant="body1" align="left" className={classes.title}>ID: </Typography>
|
||||
<Typography variant="body1" align="left" className={classes.values}> {cve.Id}</Typography>
|
||||
</Stack>
|
||||
{vulnerabilityCheck(cve.Severity)}
|
||||
<Stack sx={{ flexDirection: "row" }}>
|
||||
<Typography variant="body1" align="left" className={classes.title}>Title: </Typography>
|
||||
<Typography variant="body1" align="left" className={classes.values}> {cve.Title}</Typography>
|
||||
</Stack>
|
||||
<Typography sx={{ color: "#1479FF", paddingTop: "1rem", fontSize: "0.8125rem", fontWeight: "600", cursor: 'pointer' }} onClick={() => setOpen(!open)}>{!open ? 'See description' : 'Hide description'}</Typography>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box>
|
||||
<Typography variant="body2" align="left" sx={{ color: "#0F2139", fontSize: "1rem" }}> {cve.Description} </Typography>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function VulnerabilitiesDetails(props) {
|
||||
const [cveData, setCveData] = useState({});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const { name } = props;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
api.get(`${host()}${endpoints.vulnerabilitiesForRepo(name)}`)
|
||||
.then(response => {
|
||||
if (response.data && response.data.data) {
|
||||
let cveInfo = response.data.data.CVEListForImage;
|
||||
let cveListData = {
|
||||
cveList: cveInfo?.CVEList
|
||||
}
|
||||
setCveData(cveListData);
|
||||
setIsLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setCveData({});
|
||||
});
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
const renderCVEs = (cves) => {
|
||||
if (cves?.length !== 0) {
|
||||
return (cves && cves.map((cve, index) => {
|
||||
return (
|
||||
<VulnerabilitiyCard key={index} cve={cve} />
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (<Typography> No Vulnerabilities </Typography>);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="h4" gutterBottom component="div" align="left" style={{ color: "rgba(0, 0, 0, 0.87)", fontSize: "1.5rem", fontWeight: "600", paddingTop:"0.5rem" }}>Vulnerabilities</Typography>
|
||||
<Divider variant="fullWidth" sx={{ margin: "5% 0% 5% 0%", background: "rgba(0, 0, 0, 0.38)", height: "0.00625rem", width: "100%" }} />
|
||||
{renderCVEs(cveData?.
|
||||
// @ts-ignore
|
||||
cveList)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VulnerabilitiesDetails;
|
@ -1,11 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
margin: 0rem 0rem 5rem 0rem;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
|
||||
/* background-image: url(./assets/background.png); */
|
||||
background-color: #F6F7F9 !important;
|
||||
}
|
||||
|
50
src/pages/TagPage.jsx
Normal file
50
src/pages/TagPage.jsx
Normal file
@ -0,0 +1,50 @@
|
||||
// react global
|
||||
import React from 'react';
|
||||
|
||||
// components
|
||||
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { Container, Grid, Stack } from '@mui/material';
|
||||
import Header from 'components/Header';
|
||||
import TagDetails from 'components/TagDetails';
|
||||
import ExploreHeader from 'components/ExploreHeader';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
pageWrapper: {
|
||||
height:"100%"
|
||||
},
|
||||
container: {
|
||||
paddingTop: 5,
|
||||
paddingBottom: 5,
|
||||
backgroundColor: "#FFFFFF",
|
||||
},
|
||||
parentWrapper: {
|
||||
height: '100vh',
|
||||
},
|
||||
gridWrapper: {
|
||||
paddingTop: 10,
|
||||
paddingBottom: 10,
|
||||
backgroundColor: "#fff",
|
||||
width:"100%",
|
||||
},
|
||||
}));
|
||||
|
||||
function TagPage(props) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Stack direction="column" className={classes.pageWrapper} data-testid='tag-container'>
|
||||
<Header updateKeywords={props.updateKeywords}></Header>
|
||||
<Container className={classes.container} >
|
||||
<ExploreHeader/>
|
||||
<Grid container className={classes.gridWrapper}>
|
||||
<Grid item xs={12}>
|
||||
<TagDetails/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default TagPage;
|
Loading…
Reference in New Issue
Block a user