Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion gcp/website/frontend3/src/go/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Overpass+Mono:wght@400;700&family=Overpass:ital,wght@0,400;0,700;1,400&display=swap">
</noscript>
<meta charset="utf-8">
<title id="title">OSV - Open Source Vulnerabilities</title>
<title id="title">{{ if .Title }}{{ .Title }}{{ else }}OSV - Open Source Vulnerabilities{{ end }}</title>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-ZXG9G6HTBR"></script>
<script>
Expand Down
189 changes: 89 additions & 100 deletions gcp/website/frontend3/src/go/templates/list.html
Original file line number Diff line number Diff line change
@@ -1,166 +1,155 @@
{% extends 'base.html' %}
{% set active_section = 'vulnerabilities' %}
{% set disable_turbo_cache = 'true' %}

{% macro table_header_cell(column_id, column_name, is_sortable, is_sorted, is_descending) %}
<span class="vuln-table-cell mdc-data-table__header-cell vuln-table-header
{% if is_sortable %}mdc-data-table__header-cell--with-sort__DISABLED{% endif %}
{% if is_sorted %}mdc-data-table__header-cell--sorted{% endif %}
{% if is_descending %}mdc-data-table__header-cell--sorted-descending{% endif %}" role="columnheader" scope="col"
aria-sort="{% if is_sorted %}{% if is_descending %}descending{% else %}ascending{% endif %}{% else %}none{% endif %}"
data-column-id="{{ column_id }}">
<div class="mdc-data-table__header-cell-wrapper">
<div class="mdc-data-table__header-cell-label">
{{ column_name }}
</div>
{% if is_sorted %}
<md-icon-button class="mdc-data-table__sort-icon-button" disabled aria-label="Sorted by {{ column_name }}"
aria-describedby="{{ column_id }}-status-label">
<md-icon aria-hidden="false">arrow_upward</md-icon>
</md-icon-button>
<div class="mdc-data-table__sort-status-label" aria-hidden="true" id="{{ column_id }}-status-label">
</div>
{% endif %}
</div>
</span>
{% endmacro %}

{% block content %}
{{ define "content" }}
<div class="list-page">
<div class="mdc-layout-grid">
<div class="mdc-layout-grid__inner">
<div class="mdc-layout-grid__cell--span-12">
<h1 class="title">Vulnerabilities</h1>
<div class="search">
<form action="{{ url_for('frontend_handlers.list_vulnerabilities') }}" data-turbo-frame="vulnerability-table">
<form action="/list" data-turbo-frame="vulnerability-table">
<div class="mdc-layout-grid__inner">
<div class="query-container search-suggestions-container mdc-layout-grid__cell--span-8">
<md-textfield-with-suggestions label="Package or ID search" class="query-field" type="search" name="q"
value="{{ query }}" maxlength="300">
value="{{ .Query }}" maxlength="300">
<md-icon slot="leading-icon" aria-hidden="false">search</md-icon>
</md-textfield-with-suggestions>
</div>
</div>
<submit-radios>
{% if ecosystem_counts %}
{{ if .EcosystemCounts }}
<div class="ecosystem-buttons">
<input type="radio" name="ecosystem" id="ecosystem-radio-all" value="" {% if not selected_ecosystem %}
checked{% endif %}>
<input type="radio" name="ecosystem" id="ecosystem-radio-all" value="" {{ if not .SelectedEcosystem }}checked{{ end }}>
<label class="ecosystem-label ecosystem-label-all" for="ecosystem-radio-all">
<span class="ecosystem-name">All ecosystems</span>
<span class="ecosystem-count">{{ ecosystem_counts.values() | sum }}</span>
<span class="ecosystem-count">{{ .TotalEcosystemCount }}</span>
</label>
{% for ecosystem in ecosystem_counts %}
<input type="radio" name="ecosystem" id="ecosystem-radio-{{ loop.index }}" value="{{ ecosystem }}" {%
if selected_ecosystem==ecosystem %} checked{% endif %}>
<label class="ecosystem-label" for="ecosystem-radio-{{ loop.index }}">
<span class="ecosystem-name">{{ ecosystem }}</span>
<span class="ecosystem-count">{{ ecosystem_counts[ecosystem] }}</span>
{{ range $i, $eco := .EcosystemCounts }}
<input type="radio" name="ecosystem" id="ecosystem-radio-{{ $i }}" value="{{ $eco.Name }}" {{ if eq $.SelectedEcosystem $eco.Name }}checked{{ end }}>
<label class="ecosystem-label" for="ecosystem-radio-{{ $i }}">
<span class="ecosystem-name">{{ $eco.Name }}</span>
<span class="ecosystem-count">{{ $eco.Count }}</span>
</label>
{% endfor %}
{{ end }}
</div>
{% endif %}
{{ end }}
</submit-radios>
<input type="submit">
</form>
</div>
</div>
</div>
</div>
<turbo-frame class="vuln-table-container mdc-data-table" id="vulnerability-table" data-turbo-action="advance">
<turbo-frame class="vuln-table-container mdc-data-table" id="vulnerability-table" data-turbo-action="advance" data-title="{{ if .SelectedEcosystem }}{{ .SelectedEcosystem }} - OSV{{ else }}Vulnerability Database - OSV{{ end }}">
<div role="table" class="vuln-table mdc-data-table__table" aria-label="Vulnerability table">
<div role="rowgroup" class="vuln-table-header">
<div role="row" class="vuln-table-row mdc-data-table__header-row">
{{ table_header_cell('id', 'ID', is_sortable=False, is_sorted=False, is_descending=False) }}
{{ table_header_cell('package', 'Packages', is_sortable=False, is_sorted=False, is_descending=False) }}
{{ table_header_cell('summary', 'Summary', is_sortable=False, is_sorted=False, is_descending=False) }}
{{ table_header_cell('published', 'Published', is_sortable=True, is_sorted=True, is_descending=True) }}
{{ table_header_cell('attributes', 'Attributes', is_sortable=False, is_sorted=False, is_descending=False) }}
<span class="vuln-table-cell mdc-data-table__header-cell vuln-table-header" role="columnheader" scope="col" aria-sort="none" data-column-id="id">
<div class="mdc-data-table__header-cell-wrapper">
<div class="mdc-data-table__header-cell-label">ID</div>
</div>
</span>
<span class="vuln-table-cell mdc-data-table__header-cell vuln-table-header" role="columnheader" scope="col" aria-sort="none" data-column-id="package">
<div class="mdc-data-table__header-cell-wrapper">
<div class="mdc-data-table__header-cell-label">Packages</div>
</div>
</span>
<span class="vuln-table-cell mdc-data-table__header-cell vuln-table-header" role="columnheader" scope="col" aria-sort="none" data-column-id="summary">
<div class="mdc-data-table__header-cell-wrapper">
<div class="mdc-data-table__header-cell-label">Summary</div>
</div>
</span>
<span class="vuln-table-cell mdc-data-table__header-cell vuln-table-header mdc-data-table__header-cell--sorted mdc-data-table__header-cell--sorted-descending" role="columnheader" scope="col" aria-sort="descending" data-column-id="published">
<div class="mdc-data-table__header-cell-wrapper">
<div class="mdc-data-table__header-cell-label">Published</div>
<md-icon-button class="mdc-data-table__sort-icon-button" disabled aria-label="Sorted by Published">
<md-icon aria-hidden="false">arrow_upward</md-icon>
</md-icon-button>
</div>
</span>
<span class="vuln-table-cell mdc-data-table__header-cell vuln-table-header" role="columnheader" scope="col" aria-sort="none" data-column-id="attributes">
<div class="mdc-data-table__header-cell-wrapper">
<div class="mdc-data-table__header-cell-label">Attributes</div>
</div>
</span>
</div>
</div>
<div role="rowgroup" class="vuln-table-rows mdc-data-table__content">
<turbo-frame id="vulnerability-table-page{{ page }}" data-turbo-action="advance" target="_top">
{% for vulnerability in vulnerabilities %}
<turbo-frame id="vulnerability-table-page{{ .Page }}" data-turbo-action="advance" target="_top">
{{ range .Vulnerabilities }}
<div role="row" class="vuln-table-row mdc-data-table__row">
<span role="cell" class="vuln-table-cell mdc-data-table__cell">
<a href="{{ url_for('frontend_handlers.vulnerability', vuln_id=vulnerability.id) }}">{{ vulnerability.id
}}</a>
<a href="/vulnerability/{{ .ID }}">{{ .ID }}</a>
</span>
<span role="cell" class="vuln-table-cell vuln-packages mdc-data-table__cell">
<ul class="packages">
{% for package in vulnerability.packages[:5] %}
<li>{{ package }}</li>
{% endfor %}
{% if vulnerability.packages|length > 5 %}
{% set remainingPkgCount = vulnerability.packages|length - 5 %}
<li class="remaining-packages">... {{ remainingPkgCount }} more</li>
{% elif vulnerability.packages|length == 0 %}
{{ range .DisplayPackages }}
<li>{{ . }}</li>
{{ end }}
{{ if gt .RemainingPackageCount 0 }}
<li class="remaining-packages">... {{ .RemainingPackageCount }} more</li>
{{ else if eq (len .Packages) 0 }}
<li>Not specified</li>
{% endif %}
{{ end }}
</ul>
</span>
<span role="cell" class="vuln-table-cell vuln-summary mdc-data-table__cell">
{% if vulnerability.summary %}
{{ vulnerability.summary | literal_backticks }}
{% else %}
{{ if .Summary }}
{{ .Summary }}
{{ else }}
See record for full details
{% endif %}
{{ end }}
</span>
<span role="cell" class="vuln-table-cell mdc-data-table__cell">
<relative-time datetime="{{ vulnerability.published }}">
{{ vulnerability.published | relative_time }}
<relative-time datetime="{{ .FormattedPublished }}">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just make this a span with a title="original date". We can look at this again in the future if the web component make sense to use.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it looks like we are not even installing the relative-time web component

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was removed here: 25bc7c6

{{ .RelativePublished }}
</relative-time>
</span>
<span role="cell" class="vuln-table-cell vuln-attributes mdc-data-table__cell">
<ul class="tags">
<li>
{%- if vulnerability.is_fixed -%}
{{ if .IsFixed }}
<span class="tag fix-available">Fix available</span>
{%- else -%}
{{ else }}
<span class="tag fix-unavailable">No fix available</span>
{%- endif -%}
{{ end }}
</li>
{%- if vulnerability.severity_score and vulnerability.severity_rating -%}
{{ with .PrimarySeverity }}
<li>
<span class="tag severity-{{ vulnerability.severity_rating | lower }}">Severity - {{
vulnerability.severity_score }} ({{ vulnerability.severity_rating }})</span>
<span class="tag severity-{{ .Level }}">Severity - {{ .Rating }}</span>
</li>
{%- endif -%}
{{ end }}
</ul>
</span>
</div>
{%- endfor -%}
{%- if vulnerabilities | length == 0 -%}
{{ end }}
{{ if eq (len .Vulnerabilities) 0 }}
<span class="no-results">No results (check our <a href="https://google.github.io/osv.dev/faq/">FAQ</a> if this is unexpected)</span>
{%- endif -%}
{%- if page < total_pages -%} <turbo-frame id="vulnerability-table-page{{ page + 1 }}"
data-turbo-action="advance" target="_top" class="next-page-frame">
{{ end }}
{{ if lt .Page .TotalPages }}
<turbo-frame id="vulnerability-table-page{{ add .Page 1 }}" data-turbo-action="advance" target="_top" class="next-page-frame">
<div class="next-page-container">
<a class="next-page-button link-button" data-turbo-frame="_self" href="{{ url_for(request.endpoint) }}?page={{ page + 1 }}
{%- if query %}&q={{ query }}{% endif %}
{%- if selected_ecosystem %}&ecosystem={{ selected_ecosystem }}{% endif %}">
<a class="next-page-button link-button" data-turbo-frame="_self" href="/list?page={{ add .Page 1 }}{{ if $.Query }}&q={{ $.EncodedQuery }}{{ end }}{{ if $.SelectedEcosystem }}&ecosystem={{ $.EncodedEcosystem }}{{ end }}">
<span>Load more...</span>
{%- if total_pages - page <= 3 -%} <span style="margin-left: 5px">({{ total_pages - page }} page{% if
total_pages - page > 1 %}s{% endif %} left)</span>
{%- endif -%}
{{ $pagesLeft := sub $.TotalPages $.Page }}
{{ if le $pagesLeft 3 }}
<span style="margin-left: 5px">({{ $pagesLeft }} page{{ if gt $pagesLeft 1 }}s{{ end }} left)</span>
{{ end }}
</a>
<md-circular-progress class="next-page-indicator" indeterminate density="-4"
aria-label="Page loading progress"></md-circular-progress>
<md-circular-progress class="next-page-indicator" indeterminate density="-4" aria-label="Page loading progress"></md-circular-progress>
</div>
</turbo-frame>
{{ end }}
</turbo-frame>
{%- endif -%}
</div>
</div>
<turbo-stream action="update" target="title">
<template>
{{ if .SelectedEcosystem }}
{{ .SelectedEcosystem }} - OSV
{{ else }}
Vulnerability Database - OSV
{{ end }}
</template>
</turbo-stream>
</turbo-frame>
</div>
</div>
<turbo-stream action="update" target="title">
<template>
{% if selected_ecosystem %}
{{ selected_ecosystem }} - OSV
{% else %}
Vulnerability Database - OSV
{% endif %}
</template>
</turbo-stream>
</turbo-frame>
</div>
{% endblock %}
{{ end }}
16 changes: 16 additions & 0 deletions go/internal/models/vulnerability.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ type VulnSourceRef struct {
ModifiedRaw time.Time
}

// Package represents an affected package or Git repository reference.
type Package struct {
Package *osvschema.Package
Repo string
}

// ListedVulnerability represents a vulnerability entry used for rendering the website list page.
type ListedVulnerability struct {
ID string
Published time.Time
Packages []Package
Summary string
IsFixed bool
Severities []*osvschema.Severity
}

// WriteRequest bundles everything needed to perform a permanent update to an OSV record.
type WriteRequest struct {
ID string
Expand Down
Loading
Loading