Skip to content

Commit

Permalink
feat: adds a query parameter to load a file from an url on startup
Browse files Browse the repository at this point in the history
  • Loading branch information
Morl99 committed Oct 10, 2021
1 parent e32c019 commit 2207165
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 26 deletions.
6 changes: 3 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ The data never leaves your browser, because all processing is handled inside you

If you are using a GitLab Job to generate the Trivy report, you can supply a direct URL to the json file. The app will fetch the report and display the results without the hassle to first download the file. You might need to provide a token for authentication, you can do that by clicking the shield symbol next to the URL field. Make sure to create a personal access token with the scope `read_api`. The token will be persisted in the local storage, so that you can reuse it the next time you want to load a report from the same GitLab instance.

It is a good idea to print the URL of the artifact at the end of the job log, so that it can be grabbed easily. If the name of the report is `trivy-results.json`, the url schema would look like this:
You can pass a query parameter `url` to the app, and it will load a file from this url on startup. It is a good idea to print the URL of the vulnerability explorer at the end of the job log, so that the user can jump directly to the vulnerability report. If the name of the report is `trivy-results.json`, the url schema would look like this:

----
https://$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/jobs/$CI_JOB_ID/artifacts/trivy-results.json
https://dbsystel.github.io/trivy-vulnerability-explorer?url=https://$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/jobs/$CI_JOB_ID/artifacts/trivy-results.json
----

NOTE: While the feature was built having GitLab in mind, it should work for every artifact storage, where the json can be downloaded with a GET http request that needs at most a single HTTP header for authentication.
NOTE: While the feature was built having GitLab in mind, it should work for every artifact storage, where the json can be downloaded with a GET HTTP request that needs at most a single HTTP header for authentication.

== Contribute

Expand Down
30 changes: 23 additions & 7 deletions src/components/DataInput.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<template>
<v-toolbar class="mt-5">
<v-btn-toggle v-model="reportSource" mandatory>
<v-btn> File</v-btn>
<v-btn> URL</v-btn>
<v-btn :value="ReportSource.File"> File</v-btn>
<v-btn :value="ReportSource.Url"> URL</v-btn>
</v-btn-toggle>

<VueFileAgent
ref="fileAgent"
v-if="reportSource === 0"
v-if="reportSource === ReportSource.File"
class="upload-block ma-2"
:multiple="false"
:deletable="true"
Expand All @@ -21,7 +21,11 @@
}"
/>

<ReportUrlFetcher :onNewReport="onNewReport" v-if="reportSource === 1" />
<ReportUrlFetcher
:onNewReport="onNewReport"
v-if="reportSource === ReportSource.Url"
:presetUrl="this.presetUrl"
/>

<v-autocomplete
v-model="selectedTarget"
Expand All @@ -38,7 +42,7 @@
<script lang="ts">
import Vue from "vue"
import VueFileAgent, { FileRecord } from "vue-file-agent"
import { Component, Ref, Watch } from "vue-property-decorator"
import { Component, Prop, Ref, Watch } from "vue-property-decorator"
import {
Version1OrVersion2,
Vulnerability,
Expand All @@ -47,18 +51,30 @@ import {
} from "@/types"
import ReportUrlFetcher from "@/components/ReportUrlFetcher.vue"
enum ReportSource {
File,
Url,
}
Vue.use(VueFileAgent)
@Component({
components: { ReportUrlFetcher },
})
export default class DataInput extends Vue {
private selectedTarget = ""
private reportSource = 0
private reportSource = ReportSource.File
private ReportSource = ReportSource
@Prop() private presetUrl?: string
@Ref() readonly fileAgent!: {
deleteFileRecord: (fileRecordOrRaw: FileRecord) => void
}
mounted(): void {
if (this.presetUrl) {
this.reportSource = ReportSource.Url
}
}
get selectedVulnerabilities(): Vulnerability[] {
if (this.selectedTarget) {
return this.vulnerabilityReport.find(
Expand Down
40 changes: 26 additions & 14 deletions src/components/ReportUrlFetcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@
</v-card>
</v-dialog>
<v-btn
:loading="loading"
:disabled="loading || !url"
:loading="this.state === 'loading'"
:disabled="this.state === 'loading' || !url"
@click="fetchReportFromUrl"
class="mx-2"
:color="this.state === 'error' ? 'error' : 'primary'"
>
Fetch
</v-btn>
Expand All @@ -82,6 +83,7 @@ import { Fragment } from "vue-frag"
const ReportUrlFetcherProps = Vue.extend({
props: {
onNewReport: Function,
presetUrl: String,
},
})
Vue.use(VueFileAgent)
Expand All @@ -90,12 +92,16 @@ Vue.use(VueFileAgent)
})
export default class ReportUrlFetcher extends ReportUrlFetcherProps {
private url = ""
private loading = false
private state = "ready"
private dialog = false
private headerName = ""
private headerValue = ""
mounted(): void {
this.loadAuthorization()
if (this.presetUrl) {
this.url = this.presetUrl
this.fetchReportFromUrl()
}
}
public loadAuthorization(): void {
this.headerName = localStorage.getItem("headerName") || ""
Expand All @@ -109,22 +115,28 @@ export default class ReportUrlFetcher extends ReportUrlFetcherProps {
public async fetchReportFromUrl(): Promise<void> {
if (this.url) {
this.loading = true
this.state = "loading"
const headers: Record<string, string> = {}
if (this.headerName && this.headerValue) {
headers[this.headerName] = this.headerValue
}
const response = await fetch(this.url, { headers })
this.loading = false
const contentType = response.headers.get("content-type")
if (
response.ok &&
contentType &&
contentType.indexOf("application/json") !== -1
) {
const report = await response.json()
this.onNewReport(report)
try {
const response = await fetch(this.url, { headers })
this.state = "ready"
const contentType = response.headers.get("content-type")
if (
response.ok &&
contentType &&
contentType.indexOf("application/json") !== -1
) {
const report = await response.json()
this.onNewReport(report)
return
}
} catch (ignored) {
//no-op
}
this.state = "error"
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const routes: Array<RouteConfig> = [
path: "/",
name: "Home",
component: Home,
props: (route) => ({ presetUrl: route.query.url }),
},
{
path: "/about",
Expand Down
8 changes: 6 additions & 2 deletions src/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
</v-card>
</v-col>
</v-row>
<DataInput @inputChanged="reactivelySetNewVulnerabilities" />
<DataInput
@inputChanged="reactivelySetNewVulnerabilities"
:presetUrl="this.presetUrl"
/>
<DataTable :selectedVulnerabilities="selectedVulnerabilities"></DataTable>
</v-container>
</div>
Expand All @@ -31,7 +34,7 @@ import Vue from "vue"
import DataInput from "@/components/DataInput.vue" // @ is an alias to /src
import { Vulnerability } from "@/types"
import DataTable from "@/components/DataTable.vue"
import { Component } from "vue-property-decorator"
import { Component, Prop } from "vue-property-decorator"
@Component({
components: {
Expand All @@ -40,6 +43,7 @@ import { Component } from "vue-property-decorator"
},
})
export default class Home extends Vue {
@Prop() private presetUrl?: string
private selectedVulnerabilities: Vulnerability[] = []
private reactivelySetNewVulnerabilities(newVulnerabilities: Vulnerability[]) {
Expand Down

0 comments on commit 2207165

Please sign in to comment.