Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
57e8f5c
feat(artifacts): add artifacts queries, mocks
kahboom Oct 27, 2025
9628030
feat(artifacts): add route and setup component
kahboom Oct 27, 2025
317bf40
fix(sidebar): consistent casing
kahboom Nov 7, 2025
9d19918
feat(artifacts): verification first pass
kahboom Nov 10, 2025
faae8f3
chore(artifacts): split up components
kahboom Nov 11, 2025
c23869b
fix: separated ArtifactSignatureItem component
stanislavsemeniuk Nov 11, 2025
c827553
:robot: Updated timestamp file to 2025-11-12,12-45-10.
tommyd450 Nov 12, 2025
8864666
update client/openapi/console.yaml
kahboom Nov 14, 2025
949712c
feat: changed the way isExpanded works inside SignatureItem, used cer…
stanislavsemeniuk Nov 18, 2025
a1b875e
Assisted-by: ChatGPT
kahboom Nov 18, 2025
d1d994a
fix: copy pem for certificate
kahboom Nov 18, 2025
fb7560d
fix: lint
kahboom Nov 18, 2025
6196eb2
feat: used mock datat types for implementing proper signatureItem com…
stanislavsemeniuk Nov 19, 2025
fdf952b
fix: cleaned usage of key attribute as a props
stanislavsemeniuk Nov 19, 2025
0007c80
feat(artifacts): add computed verification status
kahboom Nov 19, 2025
233cb09
chore: move function to utils to enhance testability
kahboom Nov 19, 2025
59b033a
feat(artifacts): digest, identities, and time coherence enhancements
kahboom Nov 19, 2025
9ef8988
extract RekorEntryPanel into a separate reusable component, made atte…
stanislavsemeniuk Nov 20, 2025
aaee47a
Merge pull request #87 from securesign/artifact-signatues-and-attesta…
stanislavsemeniuk Nov 20, 2025
9e3515d
chore: move view-model interfaces to own file
kahboom Nov 20, 2025
a24fe8e
chore: move view-model interfaces to own file
kahboom Nov 20, 2025
2c6a26d
chore: remove content header, margin customizations
kahboom Nov 20, 2025
75aa487
fix: import of ParsedCertificate
stanislavsemeniuk Nov 21, 2025
a1d3471
fix: wrong copying of digest inside signatures and attestations header
stanislavsemeniuk Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions branding/favicon-rh.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed branding/favicon.ico
Binary file not shown.
2 changes: 1 addition & 1 deletion branding/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "RHTAS Console UI",
"icons": [
{
"src": "favicon.ico",
"src": "favicon-rh.svg",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
Expand Down
2 changes: 1 addition & 1 deletion client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<meta name="theme-color" content="#000000"/>
<meta name="version" content="2.0.0"/>
<link rel="manifest" href="/manifest.json"/>
<link rel="icon" href="/favicon.ico"/>
<link rel="icon" href="/favicon-rh.svg"/>
<base href="/"/>
<script>
window._env = "<%= _env %>";
Expand Down
27 changes: 27 additions & 0 deletions client/openapi/console.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,25 @@ components:
required:
- mediaType
- size
Signatures:
type: array
items:
$ref: '#/components/schemas/Signature'
Signature:
type: object
description: A cryptographic signature with its associated certificate chain
properties:
signature:
type: string
description: A single cryptographic signature encoded as a string
certificateChain:
type: array
description: The X.509 certificate chain associated with this signature
items:
type: string
required:
- signature
- certificateChain
ImageMetadataResponse:
type: object
properties:
Expand All @@ -657,6 +676,14 @@ components:
type: string
description: The container image's digest (e.g., SHA256 hash)
example: sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
signatures:
$ref: '#/components/schemas/Signatures'
attestations:
type: array
description: A list of signed attestations associated with the image
items:
type: string
description: A single signed attestation payload or reference
required:
- artifact
- metadata
Expand Down
5 changes: 4 additions & 1 deletion client/src/app/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import { Navigate, useRoutes } from "react-router-dom";
import { Bullseye, Spinner } from "@patternfly/react-core";
import { ErrorFallback } from "./components/ErrorFallback";

const Artifacts = lazy(() => import("./pages/Artifacts"));
const TrustRoot = lazy(() => import("./pages/TrustRoot"));
const RekorSearch = lazy(() => import("./pages/RekorSearch"));

export const Paths = {
trustRoot: "/trust-root",
artifacts: "/artifacts",
rekorSearch: "/rekor-search",
trustRoot: "/trust-root",
} as const;

export const AppRoutes = () => {
const allRoutes = useRoutes([
{ path: "/", element: <Navigate to={Paths.trustRoot} /> },
{ path: Paths.trustRoot, element: <TrustRoot /> },
{ path: Paths.artifacts, element: <Artifacts /> },
{ path: Paths.rekorSearch, element: <RekorSearch /> },
]);

Expand Down
8 changes: 4 additions & 4 deletions client/src/app/layout/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ export const AboutApp: React.FC<IButtonAboutAppProps> = ({ isOpen, onClose }) =>
productName={about.displayName}
brandImageAlt="Logo"
brandImageSrc={about.imageSrc ?? TRANSPARENT_1x1_GIF}
trademark={`COPYRIGHT © 2020, ${new Date().getFullYear()}`}
trademark={`COPYRIGHT © 2025, ${new Date().getFullYear()}`}
>
<Content>
<Content component={ContentVariants.p}>
{about.displayName} is a proactive service that assists in risk management of Open Source Software (OSS)
packages and dependencies. {about.displayName} brings awareness to and remediation of OSS vulnerabilities
discovered within the software supply chain.
{about.displayName} is a web-based UI for interacting with the Red Hat Trusted Artifact Signer (TAS)
ecosystem. It provides user-friendly workflows for retrieving, verifying, and monitoring signed software
artifacts, integrating with Sigstore services like Rekor, Fulcio, and TUF.
</Content>

{about.documentationUrl ? (
Expand Down
10 changes: 9 additions & 1 deletion client/src/app/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ export const SidebarApp: React.FC = () => {
return css(LINK_CLASS, isActive ? ACTIVE_LINK_CLASS : "");
}}
>
Trust root
Trust Root
</NavLink>
<NavLink
to={Paths.artifacts}
className={({ isActive }) => {
return css(LINK_CLASS, isActive ? ACTIVE_LINK_CLASS : "");
}}
>
Artifacts
</NavLink>
</li>
<li className={nav.navItem}>
Expand Down
156 changes: 156 additions & 0 deletions client/src/app/pages/Artifacts/Artifacts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { Fragment, useRef, useState } from "react";
import {
Button,
Content,
Flex,
FlexItem,
Form,
FormGroup,
FormGroupLabelHelp,
FormHelperText,
HelperText,
HelperTextItem,
PageSection,
Popover,
TextInput,
} from "@patternfly/react-core";
import { useFetchArtifactsImageData, useVerifyArtifact } from "@app/queries/artifacts";
import { LoadingWrapper } from "@app/components/LoadingWrapper";
import { ArtifactResults } from "./components/ArtifactResults";
import { Controller, useForm } from "react-hook-form";
import { ExclamationCircleIcon } from "@patternfly/react-icons";

const PLACEHOLDER_URI = "docker.io/library/nginx:latest";

interface FormInputs {
searchInput: string;
}

export const Artifacts = () => {
const [artifactUri, setArtifactUri] = useState<string | null>();
const labelHelpRef = useRef(null);

const {
artifact,
isFetching: isFetchingArtifactMetadata,
fetchError: fetchErrorArtifactMetadata,
} = useFetchArtifactsImageData({ uri: artifactUri });

const { mutate: verifyArtifact, data: verification, error: verifyError } = useVerifyArtifact();

const {
control,
handleSubmit,
watch,
formState: { errors },
} = useForm<FormInputs>({
mode: "all",
reValidateMode: "onChange",
defaultValues: {
searchInput: "",
},
});

const onSubmit = (data: FormInputs) => {
const uri = data.searchInput?.trim();
if (!uri) return;
setArtifactUri(uri);
// kick off verification for this URI as well
verifyArtifact({ uri, expectedSAN: null });
};

const query = watch("searchInput");
const isEmpty = query.trim().length === 0;

return (
<Fragment>
<PageSection>
<Content>
<h1>Artifacts</h1>
<p>Search for an artifact.</p>
</Content>
</PageSection>
<PageSection>
<Form onSubmit={(e) => void handleSubmit(onSubmit)(e)}>
<Flex>
<Flex direction={{ default: "column" }} flex={{ default: "flex_3" }}>
<FlexItem>
<Controller
name="searchInput"
control={control}
rules={{ required: { value: true, message: "A URI is required" } }}
render={({ field, fieldState }) => (
<FormGroup
label="Container Image URI"
labelHelp={
<Popover
triggerRef={labelHelpRef}
headerContent={<div>Uniform Resource Identifier (URI)</div>}
bodyContent={
<div>
The URI identifies where a resource, like a container image, lives (e.g.,{" "}
{PLACEHOLDER_URI})
</div>
}
>
<FormGroupLabelHelp ref={labelHelpRef} aria-label="More info for URI field" />
</Popover>
}
isRequired
fieldId="uri"
>
<TextInput
aria-label={`uri input field`}
{...field}
type="text"
name="searchInput"
id="uri"
aria-describedby="uri-helper"
aria-invalid={errors.searchInput ? "true" : "false"}
placeholder={PLACEHOLDER_URI}
validated={fieldState.invalid ? "error" : "default"}
/>
{fieldState.invalid && (
<FormHelperText>
<HelperText>
<HelperTextItem icon={<ExclamationCircleIcon />} variant={"error"}>
{fieldState.invalid ? fieldState.error?.message : <span>A value is required</span>}
</HelperTextItem>
</HelperText>
</FormHelperText>
)}
</FormGroup>
)}
></Controller>
</FlexItem>
</Flex>
<Flex
direction={{ default: "column" }}
alignSelf={{ default: "alignSelfFlexEnd" }}
flex={{ default: "flex_1" }}
>
<FlexItem>
<Button
variant="primary"
id="search-form-button"
isBlock={true}
isDisabled={isEmpty}
type="submit"
spinnerAriaLabel="Loading"
spinnerAriaLabelledBy="search-form-button"
>
Search
</Button>
</FlexItem>
</Flex>
</Flex>
</Form>
</PageSection>
<PageSection>
<LoadingWrapper isFetching={isFetchingArtifactMetadata} fetchError={fetchErrorArtifactMetadata ?? verifyError}>
{artifact && verification && <ArtifactResults artifact={artifact} verification={verification} />}
</LoadingWrapper>
</PageSection>
</Fragment>
);
};
Loading