@sanity-labs/backstage-plugin-trivy 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # Trivy Frontend Plugin
2
+
3
+ This frontend plugin displays Trivy security scan results in Backstage.
4
+
5
+ ## Features
6
+
7
+ - **Security Scans Page**: View all security scans across repositories
8
+ - **Entity Card**: Display latest scan results on catalog entity pages
9
+ - **Severity-based filtering**: See critical, high, medium, and low severity findings
10
+ - **Links to GitHub PRs and CircleCI builds**
11
+ - **Expandable finding details** with remediation guidance
12
+
13
+ ## Installation
14
+
15
+ ### 1. Add to your Backstage app dependencies
16
+
17
+ Add to `packages/app/package.json`:
18
+ ```json
19
+ {
20
+ "dependencies": {
21
+ "@internal/backstage-plugin-trivy": "^0.1.0"
22
+ }
23
+ }
24
+ ```
25
+
26
+ ### 2. Configure the plugin (optional)
27
+
28
+ Add to your `app-config.yaml`:
29
+ ```yaml
30
+ trivy:
31
+ # GCS bucket name (default: sanity-trivy-logs)
32
+ bucketName: sanity-trivy-logs
33
+
34
+ # Cache times in minutes
35
+ cacheTime: 30 # Frontend cache duration
36
+ staleTime: 5 # When to refetch in background
37
+
38
+ # Severity filter (default: CRITICAL, HIGH, MEDIUM)
39
+ severityFilter:
40
+ - CRITICAL
41
+ - HIGH
42
+ - MEDIUM
43
+ # - LOW # Uncomment to include low severity
44
+ ```
45
+
46
+ ### 3. Add the route to your app
47
+
48
+ In `packages/app/src/App.tsx`:
49
+
50
+ ```tsx
51
+ import { TrivyPage } from '@internal/backstage-plugin-trivy';
52
+
53
+ // ... inside <FlatRoutes>
54
+ <Route path="/trivy" element={<TrivyPage />} />
55
+ ```
56
+
57
+ ### 4. Add to navigation (optional)
58
+
59
+ In `packages/app/src/components/Root/Root.tsx`:
60
+
61
+ ```tsx
62
+ import SecurityIcon from '@material-ui/icons/Security';
63
+
64
+ // ... inside <SidebarGroup>
65
+ <SidebarItem icon={SecurityIcon} to="trivy" text="Security" />
66
+ ```
67
+
68
+ ### 5. Add Entity Card (optional)
69
+
70
+ To show scan results on entity pages, add to your entity page layout in `packages/app/src/components/catalog/EntityPage.tsx`:
71
+
72
+ ```tsx
73
+ import { EntityTrivyCard } from '@internal/backstage-plugin-trivy';
74
+
75
+ // For service entities
76
+ const serviceEntityPage = (
77
+ <EntityLayout>
78
+ <EntityLayout.Route path="/" title="Overview">
79
+ <Grid container spacing={3}>
80
+ {/* ... other cards ... */}
81
+ <Grid item md={6}>
82
+ <EntityTrivyCard />
83
+ </Grid>
84
+ </Grid>
85
+ </EntityLayout.Route>
86
+ </EntityLayout>
87
+ );
88
+ ```
89
+
90
+ ### 6. Use Standalone Card (optional)
91
+
92
+ For custom pages without entity context:
93
+
94
+ ```tsx
95
+ import { StandaloneTrivyCard } from '@internal/backstage-plugin-trivy';
96
+
97
+ // Use anywhere with explicit repository
98
+ <StandaloneTrivyCard repository="alert-relay" />
99
+ // or
100
+ <StandaloneTrivyCard repository="sanity-io/alert-relay" title="Custom Title" />
101
+ ```
102
+
103
+ ## Usage
104
+
105
+ ### Viewing All Scans
106
+
107
+ Navigate to `/trivy` to see a table of all security scans across your repositories. The table shows:
108
+
109
+ - Repository name and branch
110
+ - Finding counts by severity
111
+ - Git commit hash
112
+ - Links to GitHub PRs and CircleCI builds
113
+ - Scan timestamp
114
+
115
+ ### Entity Card
116
+
117
+ The `EntityTrivyCard` automatically displays the latest scan for the entity's repository. It reads the repository name from:
118
+
119
+ - `github.com/project-slug` annotation, or
120
+ - `backstage.io/source-location` annotation
121
+
122
+ Example entity:
123
+ ```yaml
124
+ apiVersion: backstage.io/v1alpha1
125
+ kind: Component
126
+ metadata:
127
+ name: my-service
128
+ annotations:
129
+ github.com/project-slug: sanity-io/alert-relay
130
+ ```
131
+
132
+ ### Interpreting Results
133
+
134
+ Findings are color-coded by severity:
135
+ - 🔴 **CRITICAL**: Red - Requires immediate attention
136
+ - 🟠 **HIGH**: Orange - Should be fixed soon
137
+ - 🟡 **MEDIUM**: Yellow - Should be addressed
138
+ - 🔵 **LOW**: Blue - Consider fixing
139
+ - ⚪ **UNKNOWN**: Gray - Needs review
140
+
141
+ Each finding includes:
142
+ - Affected file and line numbers
143
+ - Description of the issue
144
+ - Link to detailed remediation guidance
145
+
146
+ ## API & Hooks
147
+
148
+ ### React Query Hooks (Recommended)
149
+
150
+ The plugin provides React Query hooks with built-in caching:
151
+
152
+ ```tsx
153
+ import { useTrivyScans, useTrivyLatestScan, useTrivyScan } from '@internal/backstage-plugin-trivy';
154
+
155
+ const MyComponent = () => {
156
+ // Get all scans with caching
157
+ const { data, isLoading, error } = useTrivyScans();
158
+
159
+ // Get latest scan for a repo
160
+ const { data: latest } = useTrivyLatestScan('alert-relay');
161
+
162
+ // Get specific scan
163
+ const { data: scan } = useTrivyScan('repo', 'branch', 'scanId');
164
+ };
165
+ ```
166
+
167
+ ### Direct API (Low-level)
168
+
169
+ For custom implementations without React Query:
170
+
171
+ ```tsx
172
+ import { useApi } from '@backstage/core-plugin-api';
173
+ import { trivyApiRef } from '@internal/backstage-plugin-trivy';
174
+
175
+ const MyComponent = () => {
176
+ const trivyApi = useApi(trivyApiRef);
177
+
178
+ // Get all scans
179
+ const scans = await trivyApi.getScans();
180
+
181
+ // Get specific scan
182
+ const scan = await trivyApi.getScan('repo', 'branch', 'scanId');
183
+
184
+ // Get latest scan for a repo
185
+ const latest = await trivyApi.getLatestScan('repo');
186
+ };
187
+ ```
188
+
189
+ ## Configuration
190
+
191
+ ### Severity Filtering
192
+
193
+ Control which severity levels are displayed:
194
+
195
+ ```yaml
196
+ trivy:
197
+ severityFilter:
198
+ - CRITICAL # Always recommended
199
+ - HIGH # Always recommended
200
+ - MEDIUM # Recommended for most teams
201
+ # - LOW # Uncomment for comprehensive view
202
+ ```
203
+
204
+ ### Caching
205
+
206
+ The plugin uses two-level caching:
207
+
208
+ 1. **Frontend Cache** (React Query):
209
+ - `cacheTime`: How long data stays in memory (default: 30 min)
210
+ - `staleTime`: When to refetch in background (default: 5 min)
211
+
212
+ 2. **Backend Cache** (In-memory):
213
+ - Caches GCS responses to reduce API calls
214
+ - Configurable via `trivy.cacheTime` (default: 5 min)
215
+
216
+ ```yaml
217
+ trivy:
218
+ cacheTime: 30 # Frontend: keep in cache for 30 minutes
219
+ staleTime: 5 # Frontend: consider stale after 5 minutes
220
+ # Backend cache uses the same cacheTime value
221
+ ```
222
+
223
+ ### Custom Bucket
224
+
225
+ If using a different GCS bucket:
226
+
227
+ ```yaml
228
+ trivy:
229
+ bucketName: my-custom-bucket
230
+ ```
@@ -0,0 +1,104 @@
1
+ import React from 'react';
2
+ import { Progress, Table, Link, Page, Header, Content } from '@backstage/core-components';
3
+ import { useApi, configApiRef } from '@backstage/core-plugin-api';
4
+ import { getTrivyConfig, useTrivyScans, TrivyQueryProvider } from '../index.esm.js';
5
+ import { Chip, Box, Typography } from '@material-ui/core';
6
+ import Alert from '@material-ui/lab/Alert';
7
+ import '@material-ui/icons/ExpandMore';
8
+ import '@material-ui/icons/Warning';
9
+ import '@material-ui/icons/Error';
10
+ import '@material-ui/icons/Info';
11
+ import '@backstage/plugin-catalog-react';
12
+ import '@tanstack/react-query';
13
+ import '@tanstack/query-sync-storage-persister';
14
+ import '@tanstack/react-query-persist-client';
15
+
16
+ const getSeverityCounts = (scan) => {
17
+ const counts = {
18
+ CRITICAL: 0,
19
+ HIGH: 0,
20
+ MEDIUM: 0,
21
+ LOW: 0,
22
+ UNKNOWN: 0
23
+ };
24
+ scan.findings.forEach((finding) => {
25
+ counts[finding.severity] = (counts[finding.severity] || 0) + 1;
26
+ });
27
+ return counts;
28
+ };
29
+ const TrivyScansTable = () => {
30
+ const configApi = useApi(configApiRef);
31
+ const config = getTrivyConfig(configApi);
32
+ const { data, isLoading, error } = useTrivyScans();
33
+ if (isLoading) {
34
+ return /* @__PURE__ */ React.createElement(Progress, null);
35
+ }
36
+ if (error) {
37
+ return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, "Failed to load scans: ", error instanceof Error ? error.message : "Unknown error");
38
+ }
39
+ const filteredScans = (data || []).map((scan) => ({
40
+ ...scan,
41
+ findings: scan.findings.filter(
42
+ (finding) => config.severityFilter.includes(finding.severity)
43
+ )
44
+ }));
45
+ const columns = [
46
+ {
47
+ title: "Repository",
48
+ field: "repo",
49
+ highlight: true
50
+ },
51
+ {
52
+ title: "Branch",
53
+ field: "branch"
54
+ },
55
+ {
56
+ title: "Findings",
57
+ render: (row) => {
58
+ const counts = getSeverityCounts(row);
59
+ const total = row.findings.length;
60
+ if (total === 0) {
61
+ return /* @__PURE__ */ React.createElement(Chip, { label: "\u2713 No issues", size: "small", style: { backgroundColor: "#4caf50", color: "white" } });
62
+ }
63
+ return /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 4 } }, counts.CRITICAL > 0 && /* @__PURE__ */ React.createElement(Chip, { label: `${counts.CRITICAL} Critical`, size: "small", color: "secondary" }), counts.HIGH > 0 && /* @__PURE__ */ React.createElement(Chip, { label: `${counts.HIGH} High`, size: "small", color: "secondary" }), counts.MEDIUM > 0 && /* @__PURE__ */ React.createElement(Chip, { label: `${counts.MEDIUM} Medium`, size: "small", color: "primary" }), counts.LOW > 0 && /* @__PURE__ */ React.createElement(Chip, { label: `${counts.LOW} Low`, size: "small", color: "default" }));
64
+ }
65
+ },
66
+ {
67
+ title: "Commit",
68
+ render: (row) => {
69
+ if (row.metadata.gitCommit) {
70
+ return /* @__PURE__ */ React.createElement(Typography, { variant: "body2", style: { fontFamily: "monospace" } }, row.metadata.gitCommit.substring(0, 7));
71
+ }
72
+ return "-";
73
+ }
74
+ },
75
+ {
76
+ title: "Links",
77
+ render: (row) => {
78
+ return /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 8 } }, row.metadata.githubPr && /* @__PURE__ */ React.createElement(Link, { to: row.metadata.githubPr, target: "_blank" }, "PR"), row.metadata.circleciUrl && /* @__PURE__ */ React.createElement(Link, { to: row.metadata.circleciUrl, target: "_blank" }, "Build"));
79
+ }
80
+ },
81
+ {
82
+ title: "Timestamp",
83
+ field: "timestamp",
84
+ type: "datetime"
85
+ }
86
+ ];
87
+ return /* @__PURE__ */ React.createElement(
88
+ Table,
89
+ {
90
+ title: "Trivy Security Scans",
91
+ subtitle: `Showing findings: ${config.severityFilter.join(", ")}`,
92
+ options: { search: true, paging: true, pageSize: 20 },
93
+ columns,
94
+ data: filteredScans
95
+ }
96
+ );
97
+ };
98
+
99
+ const TrivyPage = () => {
100
+ return /* @__PURE__ */ React.createElement(TrivyQueryProvider, null, /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(Header, { title: "Security Scans", subtitle: "Trivy vulnerability and misconfiguration findings" }), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(TrivyScansTable, null))));
101
+ };
102
+
103
+ export { TrivyPage };
104
+ //# sourceMappingURL=index-e137a1bb.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-e137a1bb.esm.js","sources":["../../src/components/TrivyScansTable/TrivyScansTable.tsx","../../src/components/TrivyPage/TrivyPage.tsx"],"sourcesContent":["import React from 'react';\nimport { Table, TableColumn, Progress, Link } from '@backstage/core-components';\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\nimport { TrivyScanResult } from '../../api/TrivyApi';\nimport { useTrivyScans } from '../../api/hooks';\nimport { getTrivyConfig } from '../../api/config';\nimport {\n Box,\n Chip,\n Typography,\n} from '@material-ui/core';\nimport Alert from '@material-ui/lab/Alert';\n\nconst getSeverityCounts = (scan: TrivyScanResult) => {\n const counts = {\n CRITICAL: 0,\n HIGH: 0,\n MEDIUM: 0,\n LOW: 0,\n UNKNOWN: 0,\n };\n\n scan.findings.forEach(finding => {\n counts[finding.severity] = (counts[finding.severity] || 0) + 1;\n });\n\n return counts;\n};\n\nexport const TrivyScansTable = () => {\n const configApi = useApi(configApiRef);\n const config = getTrivyConfig(configApi);\n const { data, isLoading, error } = useTrivyScans();\n\n if (isLoading) {\n return <Progress />;\n }\n\n if (error) {\n return <Alert severity=\"error\">Failed to load scans: {error instanceof Error ? error.message : 'Unknown error'}</Alert>;\n }\n\n // Filter scans by configured severity levels\n const filteredScans = (data || []).map(scan => ({\n ...scan,\n findings: scan.findings.filter(finding =>\n config.severityFilter.includes(finding.severity),\n ),\n }));\n\n const columns: TableColumn<TrivyScanResult>[] = [\n {\n title: 'Repository',\n field: 'repo',\n highlight: true,\n },\n {\n title: 'Branch',\n field: 'branch',\n },\n {\n title: 'Findings',\n render: (row: TrivyScanResult) => {\n const counts = getSeverityCounts(row);\n const total = row.findings.length;\n\n if (total === 0) {\n return <Chip label=\"✓ No issues\" size=\"small\" style={{ backgroundColor: '#4caf50', color: 'white' }} />;\n }\n\n return (\n <Box display=\"flex\" style={{ gap: 4 }}>\n {counts.CRITICAL > 0 && (\n <Chip label={`${counts.CRITICAL} Critical`} size=\"small\" color=\"secondary\" />\n )}\n {counts.HIGH > 0 && (\n <Chip label={`${counts.HIGH} High`} size=\"small\" color=\"secondary\" />\n )}\n {counts.MEDIUM > 0 && (\n <Chip label={`${counts.MEDIUM} Medium`} size=\"small\" color=\"primary\" />\n )}\n {counts.LOW > 0 && (\n <Chip label={`${counts.LOW} Low`} size=\"small\" color=\"default\" />\n )}\n </Box>\n );\n },\n },\n {\n title: 'Commit',\n render: (row: TrivyScanResult) => {\n if (row.metadata.gitCommit) {\n return (\n <Typography variant=\"body2\" style={{ fontFamily: 'monospace' }}>\n {row.metadata.gitCommit.substring(0, 7)}\n </Typography>\n );\n }\n return '-';\n },\n },\n {\n title: 'Links',\n render: (row: TrivyScanResult) => {\n return (\n <Box display=\"flex\" style={{ gap: 8 }}>\n {row.metadata.githubPr && (\n <Link to={row.metadata.githubPr} target=\"_blank\">\n PR\n </Link>\n )}\n {row.metadata.circleciUrl && (\n <Link to={row.metadata.circleciUrl} target=\"_blank\">\n Build\n </Link>\n )}\n </Box>\n );\n },\n },\n {\n title: 'Timestamp',\n field: 'timestamp',\n type: 'datetime',\n },\n ];\n\n return (\n <Table\n title=\"Trivy Security Scans\"\n subtitle={`Showing findings: ${config.severityFilter.join(', ')}`}\n options={{ search: true, paging: true, pageSize: 20 }}\n columns={columns}\n data={filteredScans}\n />\n );\n};\n","import React from 'react';\nimport { Page, Header, Content } from '@backstage/core-components';\nimport { TrivyScansTable } from '../TrivyScansTable';\nimport { TrivyQueryProvider } from '../../api/QueryProvider';\n\nexport const TrivyPage = () => {\n return (\n <TrivyQueryProvider>\n <Page themeId=\"tool\">\n <Header title=\"Security Scans\" subtitle=\"Trivy vulnerability and misconfiguration findings\" />\n <Content>\n <TrivyScansTable />\n </Content>\n </Page>\n </TrivyQueryProvider>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import * as React from 'react';
2
+ import React__default from 'react';
3
+ import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
4
+ import * as _tanstack_react_query from '@tanstack/react-query';
5
+ import { UseQueryOptions } from '@tanstack/react-query';
6
+ import { Config } from '@backstage/config';
7
+
8
+ declare const trivyPlugin: _backstage_core_plugin_api.BackstagePlugin<{
9
+ root: _backstage_core_plugin_api.RouteRef<undefined>;
10
+ }, {}, {}>;
11
+ declare const TrivyPage: () => React.JSX.Element;
12
+
13
+ interface TrivyMetadata {
14
+ circleciUrl?: string;
15
+ githubPr?: string;
16
+ gitCommit?: string;
17
+ committer?: string;
18
+ }
19
+ interface TrivyFinding {
20
+ file: string;
21
+ severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'UNKNOWN';
22
+ title: string;
23
+ description: string;
24
+ lines: string;
25
+ avdId?: string;
26
+ }
27
+ interface TrivyScanResult {
28
+ repo: string;
29
+ branch: string;
30
+ scanId: string;
31
+ metadata: TrivyMetadata;
32
+ findings: TrivyFinding[];
33
+ timestamp: string;
34
+ }
35
+ interface TrivyApi {
36
+ getScans(): Promise<TrivyScanResult[]>;
37
+ getScan(repo: string, branch: string, scanId: string): Promise<TrivyScanResult>;
38
+ getLatestScan(repo: string): Promise<TrivyScanResult>;
39
+ }
40
+ declare const trivyApiRef: _backstage_core_plugin_api.ApiRef<TrivyApi>;
41
+
42
+ interface TrivyFindingsCardProps {
43
+ findings: TrivyFinding[];
44
+ title?: string;
45
+ }
46
+ declare const TrivyFindingsCard: ({ findings, title }: TrivyFindingsCardProps) => React__default.JSX.Element;
47
+
48
+ declare const EntityTrivyCard: () => React__default.JSX.Element;
49
+
50
+ interface StandaloneTrivyCardProps {
51
+ /** Repository identifier (e.g., "alert-relay" or "sanity-io/alert-relay") */
52
+ repository: string;
53
+ /** Optional title override */
54
+ title?: string;
55
+ }
56
+ declare const StandaloneTrivyCard: ({ repository, title }: StandaloneTrivyCardProps) => React__default.JSX.Element;
57
+
58
+ declare const TrivyQueryProvider: ({ children }: {
59
+ children: React__default.ReactNode;
60
+ }) => React__default.JSX.Element;
61
+ declare const SimpleTrivyQueryProvider: ({ children }: {
62
+ children: React__default.ReactNode;
63
+ }) => React__default.JSX.Element;
64
+
65
+ declare const useTrivyScans: (options?: Omit<UseQueryOptions<TrivyScanResult[]>, "queryKey" | "queryFn">) => _tanstack_react_query.UseQueryResult<TrivyScanResult[], Error>;
66
+ declare const useTrivyScan: (repo: string, branch: string, scanId: string, options?: Omit<UseQueryOptions<TrivyScanResult>, "queryKey" | "queryFn">) => _tanstack_react_query.UseQueryResult<TrivyScanResult, Error>;
67
+ declare const useTrivyLatestScan: (repo: string | undefined, options?: Omit<UseQueryOptions<TrivyScanResult>, "queryKey" | "queryFn">) => _tanstack_react_query.UseQueryResult<TrivyScanResult, Error>;
68
+
69
+ interface TrivyConfig {
70
+ bucketName: string;
71
+ severityFilter: string[];
72
+ cacheTime: number;
73
+ staleTime: number;
74
+ }
75
+ declare const DEFAULT_TRIVY_CONFIG: TrivyConfig;
76
+ declare function getTrivyConfig(config: Config): TrivyConfig;
77
+
78
+ export { DEFAULT_TRIVY_CONFIG, EntityTrivyCard, SimpleTrivyQueryProvider, StandaloneTrivyCard, TrivyApi, TrivyConfig, TrivyFinding, TrivyFindingsCard, TrivyMetadata, TrivyPage, TrivyQueryProvider, TrivyScanResult, getTrivyConfig, trivyApiRef, trivyPlugin, useTrivyLatestScan, useTrivyScan, useTrivyScans };
@@ -0,0 +1,269 @@
1
+ import { createRouteRef, createApiRef, createPlugin, createApiFactory, discoveryApiRef, fetchApiRef, createRoutableExtension, useApi, configApiRef } from '@backstage/core-plugin-api';
2
+ import React from 'react';
3
+ import { InfoCard, Link, Progress } from '@backstage/core-components';
4
+ import { makeStyles, Box, Chip, Accordion, AccordionSummary, Typography, AccordionDetails } from '@material-ui/core';
5
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
6
+ import WarningIcon from '@material-ui/icons/Warning';
7
+ import ErrorIcon from '@material-ui/icons/Error';
8
+ import InfoIcon from '@material-ui/icons/Info';
9
+ import { useEntity } from '@backstage/plugin-catalog-react';
10
+ import { useQuery, QueryClient, QueryClientProvider } from '@tanstack/react-query';
11
+ import Alert from '@material-ui/lab/Alert';
12
+ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
13
+ import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
14
+
15
+ const rootRouteRef = createRouteRef({
16
+ id: "trivy"
17
+ });
18
+
19
+ var __defProp = Object.defineProperty;
20
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
21
+ var __publicField = (obj, key, value) => {
22
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
23
+ return value;
24
+ };
25
+ const trivyApiRef = createApiRef({
26
+ id: "plugin.trivy.service"
27
+ });
28
+ class TrivyClient {
29
+ constructor(options) {
30
+ __publicField(this, "discoveryApi");
31
+ __publicField(this, "fetchApi");
32
+ this.discoveryApi = options.discoveryApi;
33
+ this.fetchApi = options.fetchApi;
34
+ }
35
+ async fetch(path) {
36
+ const baseUrl = await this.discoveryApi.getBaseUrl("trivy");
37
+ const response = await this.fetchApi.fetch(`${baseUrl}${path}`);
38
+ if (!response.ok) {
39
+ throw new Error(`Failed to fetch ${path}: ${response.statusText}`);
40
+ }
41
+ return response.json();
42
+ }
43
+ async getScans() {
44
+ return this.fetch("/scans");
45
+ }
46
+ async getScan(repo, branch, scanId) {
47
+ return this.fetch(`/scans/${repo}/${branch}/${scanId}`);
48
+ }
49
+ async getLatestScan(repo) {
50
+ return this.fetch(`/scans/${repo}/latest`);
51
+ }
52
+ }
53
+
54
+ const trivyPlugin = createPlugin({
55
+ id: "trivy",
56
+ routes: {
57
+ root: rootRouteRef
58
+ },
59
+ apis: [
60
+ createApiFactory({
61
+ api: trivyApiRef,
62
+ deps: {
63
+ discoveryApi: discoveryApiRef,
64
+ fetchApi: fetchApiRef
65
+ },
66
+ factory: ({ discoveryApi, fetchApi }) => new TrivyClient({ discoveryApi, fetchApi })
67
+ })
68
+ ]
69
+ });
70
+ const TrivyPage = trivyPlugin.provide(
71
+ createRoutableExtension({
72
+ name: "TrivyPage",
73
+ component: () => import('./esm/index-e137a1bb.esm.js').then((m) => m.TrivyPage),
74
+ mountPoint: rootRouteRef
75
+ })
76
+ );
77
+
78
+ const useStyles = makeStyles((theme) => ({
79
+ finding: {
80
+ marginBottom: theme.spacing(1)
81
+ },
82
+ critical: {
83
+ borderLeft: `4px solid ${theme.palette.error.main}`
84
+ },
85
+ high: {
86
+ borderLeft: `4px solid ${theme.palette.error.light}`
87
+ },
88
+ medium: {
89
+ borderLeft: `4px solid ${theme.palette.warning.main}`
90
+ },
91
+ low: {
92
+ borderLeft: `4px solid ${theme.palette.info.main}`
93
+ },
94
+ unknown: {
95
+ borderLeft: `4px solid ${theme.palette.grey[400]}`
96
+ }
97
+ }));
98
+ const SeverityIcon = ({ severity }) => {
99
+ switch (severity) {
100
+ case "CRITICAL":
101
+ return /* @__PURE__ */ React.createElement(ErrorIcon, { style: { color: "#d32f2f" } });
102
+ case "HIGH":
103
+ return /* @__PURE__ */ React.createElement(ErrorIcon, { style: { color: "#f44336" } });
104
+ case "MEDIUM":
105
+ return /* @__PURE__ */ React.createElement(WarningIcon, { style: { color: "#ff9800" } });
106
+ case "LOW":
107
+ return /* @__PURE__ */ React.createElement(InfoIcon, { style: { color: "#2196f3" } });
108
+ default:
109
+ return /* @__PURE__ */ React.createElement(InfoIcon, { style: { color: "#9e9e9e" } });
110
+ }
111
+ };
112
+ const TrivyFindingsCard = ({ findings, title = "Security Findings" }) => {
113
+ const classes = useStyles();
114
+ const sortedFindings = [...findings].sort((a, b) => {
115
+ const severityOrder = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, UNKNOWN: 4 };
116
+ return severityOrder[a.severity] - severityOrder[b.severity];
117
+ });
118
+ const getSeverityClass = (severity) => {
119
+ switch (severity) {
120
+ case "CRITICAL":
121
+ return classes.critical;
122
+ case "HIGH":
123
+ return classes.high;
124
+ case "MEDIUM":
125
+ return classes.medium;
126
+ case "LOW":
127
+ return classes.low;
128
+ default:
129
+ return classes.unknown;
130
+ }
131
+ };
132
+ if (findings.length === 0) {
133
+ return /* @__PURE__ */ React.createElement(InfoCard, { title }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", style: { gap: 8 } }, /* @__PURE__ */ React.createElement(Chip, { label: "\u2713 No security issues found", style: { backgroundColor: "#4caf50", color: "white" } })));
134
+ }
135
+ return /* @__PURE__ */ React.createElement(InfoCard, { title }, /* @__PURE__ */ React.createElement(Box, null, sortedFindings.map((finding, index) => /* @__PURE__ */ React.createElement(Accordion, { key: index, className: `${classes.finding} ${getSeverityClass(finding.severity)}` }, /* @__PURE__ */ React.createElement(AccordionSummary, { expandIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, null) }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", style: { gap: 8 }, width: "100%" }, /* @__PURE__ */ React.createElement(SeverityIcon, { severity: finding.severity }), /* @__PURE__ */ React.createElement(Chip, { label: finding.severity, size: "small" }), /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle2", style: { flex: 1 } }, finding.title), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, finding.file, finding.lines && `:${finding.lines}`))), /* @__PURE__ */ React.createElement(AccordionDetails, null, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", paragraph: true }, finding.description), finding.avdId && /* @__PURE__ */ React.createElement(Box, { mt: 1 }, /* @__PURE__ */ React.createElement(Link, { to: finding.avdId, target: "_blank" }, "View documentation \u2192"))))))));
136
+ };
137
+
138
+ const DEFAULT_TRIVY_CONFIG = {
139
+ bucketName: "sanity-trivy-logs",
140
+ severityFilter: ["CRITICAL", "HIGH", "MEDIUM"],
141
+ cacheTime: 30,
142
+ staleTime: 5
143
+ };
144
+ function getTrivyConfig(config) {
145
+ const trivyConfig = config.getOptionalConfig("trivy");
146
+ return {
147
+ bucketName: (trivyConfig == null ? void 0 : trivyConfig.getOptionalString("bucketName")) || DEFAULT_TRIVY_CONFIG.bucketName,
148
+ severityFilter: (trivyConfig == null ? void 0 : trivyConfig.getOptionalStringArray("severityFilter")) || DEFAULT_TRIVY_CONFIG.severityFilter,
149
+ cacheTime: (trivyConfig == null ? void 0 : trivyConfig.getOptionalNumber("cacheTime")) || DEFAULT_TRIVY_CONFIG.cacheTime,
150
+ staleTime: (trivyConfig == null ? void 0 : trivyConfig.getOptionalNumber("staleTime")) || DEFAULT_TRIVY_CONFIG.staleTime
151
+ };
152
+ }
153
+
154
+ const useTrivyScans = (options) => {
155
+ const trivyApi = useApi(trivyApiRef);
156
+ const configApi = useApi(configApiRef);
157
+ const config = getTrivyConfig(configApi);
158
+ return useQuery({
159
+ queryKey: ["trivy", "scans"],
160
+ queryFn: () => trivyApi.getScans(),
161
+ staleTime: config.staleTime * 60 * 1e3,
162
+ gcTime: config.cacheTime * 60 * 1e3,
163
+ ...options
164
+ });
165
+ };
166
+ const useTrivyScan = (repo, branch, scanId, options) => {
167
+ const trivyApi = useApi(trivyApiRef);
168
+ const configApi = useApi(configApiRef);
169
+ const config = getTrivyConfig(configApi);
170
+ return useQuery({
171
+ queryKey: ["trivy", "scan", repo, branch, scanId],
172
+ queryFn: () => trivyApi.getScan(repo, branch, scanId),
173
+ staleTime: config.staleTime * 60 * 1e3,
174
+ gcTime: config.cacheTime * 60 * 1e3,
175
+ enabled: !!(repo && branch && scanId),
176
+ ...options
177
+ });
178
+ };
179
+ const useTrivyLatestScan = (repo, options) => {
180
+ const trivyApi = useApi(trivyApiRef);
181
+ const configApi = useApi(configApiRef);
182
+ const config = getTrivyConfig(configApi);
183
+ return useQuery({
184
+ queryKey: ["trivy", "latest", repo],
185
+ queryFn: () => trivyApi.getLatestScan(repo),
186
+ staleTime: config.staleTime * 60 * 1e3,
187
+ gcTime: config.cacheTime * 60 * 1e3,
188
+ enabled: !!repo,
189
+ ...options
190
+ });
191
+ };
192
+
193
+ const EntityTrivyCard = () => {
194
+ var _a, _b, _c;
195
+ const { entity } = useEntity();
196
+ const configApi = useApi(configApiRef);
197
+ const config = getTrivyConfig(configApi);
198
+ const repoAnnotation = ((_a = entity.metadata.annotations) == null ? void 0 : _a["github.com/project-slug"]) || ((_b = entity.metadata.annotations) == null ? void 0 : _b["backstage.io/source-location"]);
199
+ const repoName = (_c = repoAnnotation == null ? void 0 : repoAnnotation.split("/").pop()) == null ? void 0 : _c.replace(/^url:.*\//, "");
200
+ const { data, isLoading, error } = useTrivyLatestScan(repoName);
201
+ if (!repoName) {
202
+ return /* @__PURE__ */ React.createElement(Alert, { severity: "info" }, "No repository configured for this entity. Add a", " ", /* @__PURE__ */ React.createElement("code", null, "github.com/project-slug"), " annotation to enable security scanning.");
203
+ }
204
+ if (isLoading) {
205
+ return /* @__PURE__ */ React.createElement(Progress, null);
206
+ }
207
+ if (error) {
208
+ return /* @__PURE__ */ React.createElement(Alert, { severity: "warning" }, "No security scans found for repository: ", repoName);
209
+ }
210
+ if (!data) {
211
+ return /* @__PURE__ */ React.createElement(Alert, { severity: "info" }, "No security scans available for ", repoName);
212
+ }
213
+ const filteredFindings = data.findings.filter(
214
+ (finding) => config.severityFilter.includes(finding.severity)
215
+ );
216
+ return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(TrivyFindingsCard, { findings: filteredFindings, title: "Latest Security Scan" }), /* @__PURE__ */ React.createElement(Box, { mt: 2, display: "flex", justifyContent: "space-between", alignItems: "center" }, /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "Scanned: ", new Date(data.timestamp).toLocaleString(), " | Showing: ", config.severityFilter.join(", ")), /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 8 } }, data.metadata.githubPr && /* @__PURE__ */ React.createElement(Link, { to: data.metadata.githubPr, target: "_blank" }, "View PR"), data.metadata.circleciUrl && /* @__PURE__ */ React.createElement(Link, { to: data.metadata.circleciUrl, target: "_blank" }, "View Build"))));
217
+ };
218
+
219
+ const StandaloneTrivyCard = ({ repository, title }) => {
220
+ const configApi = useApi(configApiRef);
221
+ const config = getTrivyConfig(configApi);
222
+ const repoName = repository.split("/").pop() || repository;
223
+ const { data, isLoading, error } = useTrivyLatestScan(repoName);
224
+ if (isLoading) {
225
+ return /* @__PURE__ */ React.createElement(Progress, null);
226
+ }
227
+ if (error) {
228
+ return /* @__PURE__ */ React.createElement(Alert, { severity: "warning" }, "No security scans found for repository: ", repoName);
229
+ }
230
+ if (!data) {
231
+ return /* @__PURE__ */ React.createElement(Alert, { severity: "info" }, "No security scans available for ", repoName);
232
+ }
233
+ const filteredFindings = data.findings.filter(
234
+ (finding) => config.severityFilter.includes(finding.severity)
235
+ );
236
+ return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
237
+ TrivyFindingsCard,
238
+ {
239
+ findings: filteredFindings,
240
+ title: title || `Security Scan - ${repoName}`
241
+ }
242
+ ), /* @__PURE__ */ React.createElement(Box, { mt: 2, display: "flex", justifyContent: "space-between", alignItems: "center" }, /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "Scanned: ", new Date(data.timestamp).toLocaleString()), /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 8 } }, data.metadata.githubPr && /* @__PURE__ */ React.createElement(Link, { to: data.metadata.githubPr, target: "_blank" }, "View PR"), data.metadata.circleciUrl && /* @__PURE__ */ React.createElement(Link, { to: data.metadata.circleciUrl, target: "_blank" }, "View Build"))));
243
+ };
244
+
245
+ const queryClient = new QueryClient({
246
+ defaultOptions: {
247
+ queries: {
248
+ gcTime: 1e3 * 60 * 30,
249
+ // 30 minutes
250
+ staleTime: 1e3 * 60 * 5,
251
+ // 5 minutes
252
+ retry: 1,
253
+ refetchOnWindowFocus: false
254
+ }
255
+ }
256
+ });
257
+ const persister = createSyncStoragePersister({
258
+ storage: window.localStorage,
259
+ key: "trivy-query-cache"
260
+ });
261
+ const TrivyQueryProvider = ({ children }) => {
262
+ return /* @__PURE__ */ React.createElement(PersistQueryClientProvider, { client: queryClient, persistOptions: { persister } }, children);
263
+ };
264
+ const SimpleTrivyQueryProvider = ({ children }) => {
265
+ return /* @__PURE__ */ React.createElement(QueryClientProvider, { client: queryClient }, children);
266
+ };
267
+
268
+ export { DEFAULT_TRIVY_CONFIG, EntityTrivyCard, SimpleTrivyQueryProvider, StandaloneTrivyCard, TrivyFindingsCard, TrivyPage, TrivyQueryProvider, getTrivyConfig, trivyApiRef, trivyPlugin, useTrivyLatestScan, useTrivyScan, useTrivyScans };
269
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":["../src/routes.ts","../src/api/TrivyApi.ts","../src/plugin.ts","../src/components/TrivyFindingsCard/TrivyFindingsCard.tsx","../src/api/config.ts","../src/api/hooks.ts","../src/components/EntityTrivyCard/EntityTrivyCard.tsx","../src/components/StandaloneTrivyCard/StandaloneTrivyCard.tsx","../src/api/QueryProvider.tsx"],"sourcesContent":["import { createRouteRef } from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'trivy',\n});\n","import { createApiRef, DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\n\nexport interface TrivyMetadata {\n circleciUrl?: string;\n githubPr?: string;\n gitCommit?: string;\n committer?: string;\n}\n\nexport interface TrivyFinding {\n file: string;\n severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'UNKNOWN';\n title: string;\n description: string;\n lines: string;\n avdId?: string;\n}\n\nexport interface TrivyScanResult {\n repo: string;\n branch: string;\n scanId: string;\n metadata: TrivyMetadata;\n findings: TrivyFinding[];\n timestamp: string;\n}\n\nexport interface TrivyApi {\n getScans(): Promise<TrivyScanResult[]>;\n getScan(repo: string, branch: string, scanId: string): Promise<TrivyScanResult>;\n getLatestScan(repo: string): Promise<TrivyScanResult>;\n}\n\nexport const trivyApiRef = createApiRef<TrivyApi>({\n id: 'plugin.trivy.service',\n});\n\nexport class TrivyClient implements TrivyApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n\n constructor(options: { discoveryApi: DiscoveryApi; fetchApi: FetchApi }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi;\n }\n\n private async fetch<T>(path: string): Promise<T> {\n const baseUrl = await this.discoveryApi.getBaseUrl('trivy');\n const response = await this.fetchApi.fetch(`${baseUrl}${path}`);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ${path}: ${response.statusText}`);\n }\n\n return response.json();\n }\n\n async getScans(): Promise<TrivyScanResult[]> {\n return this.fetch<TrivyScanResult[]>('/scans');\n }\n\n async getScan(repo: string, branch: string, scanId: string): Promise<TrivyScanResult> {\n return this.fetch<TrivyScanResult>(`/scans/${repo}/${branch}/${scanId}`);\n }\n\n async getLatestScan(repo: string): Promise<TrivyScanResult> {\n return this.fetch<TrivyScanResult>(`/scans/${repo}/latest`);\n }\n}\n","import {\n createPlugin,\n createRoutableExtension,\n createApiFactory,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/core-plugin-api';\nimport { rootRouteRef } from './routes';\nimport { trivyApiRef, TrivyClient } from './api/TrivyApi';\n\nexport const trivyPlugin = createPlugin({\n id: 'trivy',\n routes: {\n root: rootRouteRef,\n },\n apis: [\n createApiFactory({\n api: trivyApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n },\n factory: ({ discoveryApi, fetchApi }) =>\n new TrivyClient({ discoveryApi, fetchApi }),\n }),\n ],\n});\n\nexport const TrivyPage = trivyPlugin.provide(\n createRoutableExtension({\n name: 'TrivyPage',\n component: () =>\n import('./components/TrivyPage').then(m => m.TrivyPage),\n mountPoint: rootRouteRef,\n }),\n);\n","import React from 'react';\nimport { InfoCard, Link } from '@backstage/core-components';\nimport { TrivyFinding } from '../../api/TrivyApi';\nimport {\n Box,\n Chip,\n Typography,\n Accordion,\n AccordionSummary,\n AccordionDetails,\n makeStyles,\n} from '@material-ui/core';\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore';\nimport WarningIcon from '@material-ui/icons/Warning';\nimport ErrorIcon from '@material-ui/icons/Error';\nimport InfoIcon from '@material-ui/icons/Info';\n\nconst useStyles = makeStyles(theme => ({\n finding: {\n marginBottom: theme.spacing(1),\n },\n critical: {\n borderLeft: `4px solid ${theme.palette.error.main}`,\n },\n high: {\n borderLeft: `4px solid ${theme.palette.error.light}`,\n },\n medium: {\n borderLeft: `4px solid ${theme.palette.warning.main}`,\n },\n low: {\n borderLeft: `4px solid ${theme.palette.info.main}`,\n },\n unknown: {\n borderLeft: `4px solid ${theme.palette.grey[400]}`,\n },\n}));\n\nconst SeverityIcon = ({ severity }: { severity: string }) => {\n switch (severity) {\n case 'CRITICAL':\n return <ErrorIcon style={{ color: '#d32f2f' }} />;\n case 'HIGH':\n return <ErrorIcon style={{ color: '#f44336' }} />;\n case 'MEDIUM':\n return <WarningIcon style={{ color: '#ff9800' }} />;\n case 'LOW':\n return <InfoIcon style={{ color: '#2196f3' }} />;\n default:\n return <InfoIcon style={{ color: '#9e9e9e' }} />;\n }\n};\n\ninterface TrivyFindingsCardProps {\n findings: TrivyFinding[];\n title?: string;\n}\n\nexport const TrivyFindingsCard = ({ findings, title = 'Security Findings' }: TrivyFindingsCardProps) => {\n const classes = useStyles();\n\n const sortedFindings = [...findings].sort((a, b) => {\n const severityOrder = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, UNKNOWN: 4 };\n return severityOrder[a.severity] - severityOrder[b.severity];\n });\n\n const getSeverityClass = (severity: string) => {\n switch (severity) {\n case 'CRITICAL':\n return classes.critical;\n case 'HIGH':\n return classes.high;\n case 'MEDIUM':\n return classes.medium;\n case 'LOW':\n return classes.low;\n default:\n return classes.unknown;\n }\n };\n\n if (findings.length === 0) {\n return (\n <InfoCard title={title}>\n <Box display=\"flex\" alignItems=\"center\" style={{ gap: 8 }}>\n <Chip label=\"✓ No security issues found\" style={{ backgroundColor: '#4caf50', color: 'white' }} />\n </Box>\n </InfoCard>\n );\n }\n\n return (\n <InfoCard title={title}>\n <Box>\n {sortedFindings.map((finding, index) => (\n <Accordion key={index} className={`${classes.finding} ${getSeverityClass(finding.severity)}`}>\n <AccordionSummary expandIcon={<ExpandMoreIcon />}>\n <Box display=\"flex\" alignItems=\"center\" style={{ gap: 8 }} width=\"100%\">\n <SeverityIcon severity={finding.severity} />\n <Chip label={finding.severity} size=\"small\" />\n <Typography variant=\"subtitle2\" style={{ flex: 1 }}>\n {finding.title}\n </Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {finding.file}\n {finding.lines && `:${finding.lines}`}\n </Typography>\n </Box>\n </AccordionSummary>\n <AccordionDetails>\n <Box>\n <Typography variant=\"body2\" paragraph>\n {finding.description}\n </Typography>\n {finding.avdId && (\n <Box mt={1}>\n <Link to={finding.avdId} target=\"_blank\">\n View documentation →\n </Link>\n </Box>\n )}\n </Box>\n </AccordionDetails>\n </Accordion>\n ))}\n </Box>\n </InfoCard>\n );\n};\n","import { Config } from '@backstage/config';\n\nexport interface TrivyConfig {\n bucketName: string;\n severityFilter: string[];\n cacheTime: number;\n staleTime: number;\n}\n\nexport const DEFAULT_TRIVY_CONFIG: TrivyConfig = {\n bucketName: 'sanity-trivy-logs',\n severityFilter: ['CRITICAL', 'HIGH', 'MEDIUM'],\n cacheTime: 30,\n staleTime: 5,\n};\n\nexport function getTrivyConfig(config: Config): TrivyConfig {\n const trivyConfig = config.getOptionalConfig('trivy');\n\n return {\n bucketName: trivyConfig?.getOptionalString('bucketName') || DEFAULT_TRIVY_CONFIG.bucketName,\n severityFilter: trivyConfig?.getOptionalStringArray('severityFilter') || DEFAULT_TRIVY_CONFIG.severityFilter,\n cacheTime: trivyConfig?.getOptionalNumber('cacheTime') || DEFAULT_TRIVY_CONFIG.cacheTime,\n staleTime: trivyConfig?.getOptionalNumber('staleTime') || DEFAULT_TRIVY_CONFIG.staleTime,\n };\n}\n","import { useApi, configApiRef } from '@backstage/core-plugin-api';\nimport { useQuery, UseQueryOptions } from '@tanstack/react-query';\nimport { trivyApiRef, TrivyScanResult } from './TrivyApi';\nimport { getTrivyConfig } from './config';\n\nexport const useTrivyScans = (options?: Omit<UseQueryOptions<TrivyScanResult[]>, 'queryKey' | 'queryFn'>) => {\n const trivyApi = useApi(trivyApiRef);\n const configApi = useApi(configApiRef);\n const config = getTrivyConfig(configApi);\n\n return useQuery({\n queryKey: ['trivy', 'scans'],\n queryFn: () => trivyApi.getScans(),\n staleTime: config.staleTime * 60 * 1000,\n gcTime: config.cacheTime * 60 * 1000,\n ...options,\n });\n};\n\nexport const useTrivyScan = (\n repo: string,\n branch: string,\n scanId: string,\n options?: Omit<UseQueryOptions<TrivyScanResult>, 'queryKey' | 'queryFn'>,\n) => {\n const trivyApi = useApi(trivyApiRef);\n const configApi = useApi(configApiRef);\n const config = getTrivyConfig(configApi);\n\n return useQuery({\n queryKey: ['trivy', 'scan', repo, branch, scanId],\n queryFn: () => trivyApi.getScan(repo, branch, scanId),\n staleTime: config.staleTime * 60 * 1000,\n gcTime: config.cacheTime * 60 * 1000,\n enabled: !!(repo && branch && scanId),\n ...options,\n });\n};\n\nexport const useTrivyLatestScan = (\n repo: string | undefined,\n options?: Omit<UseQueryOptions<TrivyScanResult>, 'queryKey' | 'queryFn'>,\n) => {\n const trivyApi = useApi(trivyApiRef);\n const configApi = useApi(configApiRef);\n const config = getTrivyConfig(configApi);\n\n return useQuery({\n queryKey: ['trivy', 'latest', repo],\n queryFn: () => trivyApi.getLatestScan(repo!),\n staleTime: config.staleTime * 60 * 1000,\n gcTime: config.cacheTime * 60 * 1000,\n enabled: !!repo,\n ...options,\n });\n};\n","import React from 'react';\nimport { Progress, Link } from '@backstage/core-components';\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { useTrivyLatestScan } from '../../api/hooks';\nimport { getTrivyConfig } from '../../api/config';\nimport Alert from '@material-ui/lab/Alert';\nimport { TrivyFindingsCard } from '../TrivyFindingsCard';\nimport { Box, Typography } from '@material-ui/core';\n\nexport const EntityTrivyCard = () => {\n const { entity } = useEntity();\n const configApi = useApi(configApiRef);\n const config = getTrivyConfig(configApi);\n\n // Extract repo name from entity annotations\n const repoAnnotation =\n entity.metadata.annotations?.['github.com/project-slug'] ||\n entity.metadata.annotations?.['backstage.io/source-location'];\n\n const repoName = repoAnnotation?.split('/').pop()?.replace(/^url:.*\\//, '');\n\n const { data, isLoading, error } = useTrivyLatestScan(repoName);\n\n if (!repoName) {\n return (\n <Alert severity=\"info\">\n No repository configured for this entity. Add a{' '}\n <code>github.com/project-slug</code> annotation to enable security scanning.\n </Alert>\n );\n }\n\n if (isLoading) {\n return <Progress />;\n }\n\n if (error) {\n return (\n <Alert severity=\"warning\">\n No security scans found for repository: {repoName}\n </Alert>\n );\n }\n\n if (!data) {\n return (\n <Alert severity=\"info\">\n No security scans available for {repoName}\n </Alert>\n );\n }\n\n // Filter findings by configured severity levels\n const filteredFindings = data.findings.filter(finding =>\n config.severityFilter.includes(finding.severity),\n );\n\n return (\n <Box>\n <TrivyFindingsCard findings={filteredFindings} title=\"Latest Security Scan\" />\n <Box mt={2} display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\">\n <Typography variant=\"caption\" color=\"textSecondary\">\n Scanned: {new Date(data.timestamp).toLocaleString()} | Showing: {config.severityFilter.join(', ')}\n </Typography>\n <Box display=\"flex\" style={{ gap: 8 }}>\n {data.metadata.githubPr && (\n <Link to={data.metadata.githubPr} target=\"_blank\">\n View PR\n </Link>\n )}\n {data.metadata.circleciUrl && (\n <Link to={data.metadata.circleciUrl} target=\"_blank\">\n View Build\n </Link>\n )}\n </Box>\n </Box>\n </Box>\n );\n};\n","import React from 'react';\nimport { Progress, Link } from '@backstage/core-components';\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\nimport { TrivyFindingsCard } from '../TrivyFindingsCard';\nimport { useTrivyLatestScan } from '../../api/hooks';\nimport { getTrivyConfig } from '../../api/config';\nimport { Box, Typography } from '@material-ui/core';\nimport Alert from '@material-ui/lab/Alert';\n\ninterface StandaloneTrivyCardProps {\n /** Repository identifier (e.g., \"alert-relay\" or \"sanity-io/alert-relay\") */\n repository: string;\n /** Optional title override */\n title?: string;\n}\n\nexport const StandaloneTrivyCard = ({ repository, title }: StandaloneTrivyCardProps) => {\n const configApi = useApi(configApiRef);\n const config = getTrivyConfig(configApi);\n\n // Extract just the repo name if full slug provided\n const repoName = repository.split('/').pop() || repository;\n\n const { data, isLoading, error } = useTrivyLatestScan(repoName);\n\n if (isLoading) {\n return <Progress />;\n }\n\n if (error) {\n return (\n <Alert severity=\"warning\">\n No security scans found for repository: {repoName}\n </Alert>\n );\n }\n\n if (!data) {\n return (\n <Alert severity=\"info\">\n No security scans available for {repoName}\n </Alert>\n );\n }\n\n // Filter findings by configured severity levels\n const filteredFindings = data.findings.filter(finding =>\n config.severityFilter.includes(finding.severity),\n );\n\n return (\n <Box>\n <TrivyFindingsCard\n findings={filteredFindings}\n title={title || `Security Scan - ${repoName}`}\n />\n <Box mt={2} display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\">\n <Typography variant=\"caption\" color=\"textSecondary\">\n Scanned: {new Date(data.timestamp).toLocaleString()}\n </Typography>\n <Box display=\"flex\" style={{ gap: 8 }}>\n {data.metadata.githubPr && (\n <Link to={data.metadata.githubPr} target=\"_blank\">\n View PR\n </Link>\n )}\n {data.metadata.circleciUrl && (\n <Link to={data.metadata.circleciUrl} target=\"_blank\">\n View Build\n </Link>\n )}\n </Box>\n </Box>\n </Box>\n );\n};\n","import React from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';\nimport { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n gcTime: 1000 * 60 * 30, // 30 minutes\n staleTime: 1000 * 60 * 5, // 5 minutes\n retry: 1,\n refetchOnWindowFocus: false,\n },\n },\n});\n\nconst persister = createSyncStoragePersister({\n storage: window.localStorage,\n key: 'trivy-query-cache',\n});\n\nexport const TrivyQueryProvider = ({ children }: { children: React.ReactNode }) => {\n return (\n <PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>\n {children}\n </PersistQueryClientProvider>\n );\n};\n\n// For simpler usage without persistence\nexport const SimpleTrivyQueryProvider = ({ children }: { children: React.ReactNode }) => {\n return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;\n};\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@sanity-labs/backstage-plugin-trivy",
3
+ "version": "0.0.1",
4
+ "main": "dist/index.esm.js",
5
+ "types": "dist/index.d.ts",
6
+ "license": "Apache-2.0",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "backstage": {
11
+ "role": "frontend-plugin"
12
+ },
13
+ "dependencies": {
14
+ "@backstage/config": "^1.1.1",
15
+ "@backstage/core-components": "^0.13.10",
16
+ "@backstage/core-plugin-api": "^1.8.2",
17
+ "@backstage/plugin-catalog-react": "^1.9.3",
18
+ "@backstage/theme": "^0.5.0",
19
+ "@material-ui/core": "^4.12.4",
20
+ "@material-ui/icons": "^4.11.3",
21
+ "@material-ui/lab": "^4.0.0-alpha.61",
22
+ "@tanstack/query-sync-storage-persister": "^5.85.5",
23
+ "@tanstack/react-query": "^5.85.5",
24
+ "@tanstack/react-query-persist-client": "^5.85.5",
25
+ "react": "^18.0.2",
26
+ "react-dom": "^18.0.2",
27
+ "react-router-dom": "^6.3.0",
28
+ "react-use": "^17.2.4"
29
+ },
30
+ "devDependencies": {
31
+ "@backstage/cli": "^0.25.0",
32
+ "@backstage/core-app-api": "^1.11.3",
33
+ "@backstage/dev-utils": "^1.0.28",
34
+ "@backstage/test-utils": "^1.4.7",
35
+ "@testing-library/jest-dom": "^6.0.0",
36
+ "@testing-library/react": "^14.0.0",
37
+ "@testing-library/user-event": "^14.0.0",
38
+ "@types/jest": "^30.0.0",
39
+ "@types/node": "*",
40
+ "@types/react": "^18",
41
+ "@types/react-dom": "^18",
42
+ "@types/webpack-env": "^1.18.8"
43
+ },
44
+ "files": [
45
+ "dist"
46
+ ],
47
+ "scripts": {
48
+ "start": "backstage-cli package start",
49
+ "build": "backstage-cli package build",
50
+ "lint": "backstage-cli package lint",
51
+ "test": "backstage-cli package test",
52
+ "clean": "backstage-cli package clean",
53
+ "tsc": "tsc --project tsconfig.json --declaration --emitDeclarationOnly --outDir dist-types/src"
54
+ }
55
+ }