Skip to content

Commit 0ad81ca

Browse files
committed
Initial tab at a security tab showing advisories
1 parent 0a6ffe9 commit 0ad81ca

File tree

8 files changed

+438
-0
lines changed

8 files changed

+438
-0
lines changed

app/components/crate-header.gjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ export default class CrateHeader extends Component {
106106
Dependents
107107
</nav.Tab>
108108

109+
<nav.Tab @link={{link_ 'crate.security' @crate}} data-test-security-tab>
110+
Security
111+
</nav.Tab>
112+
109113
{{#if this.isOwner}}
110114
<nav.Tab @link={{link_ 'crate.settings' @crate}} data-test-settings-tab>
111115
Settings

app/controllers/crate/security.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Controller from '@ember/controller';
2+
import { service } from '@ember/service';
3+
import { tracked } from '@glimmer/tracking';
4+
5+
export default class SecurityController extends Controller {
6+
@service releaseTracks;
7+
@service sentry;
8+
9+
@tracked crate;
10+
@tracked data;
11+
12+
constructor() {
13+
super(...arguments);
14+
this.reset();
15+
}
16+
17+
reset() {
18+
this.crate = undefined;
19+
this.data = undefined;
20+
}
21+
}

app/router.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Router.map(function () {
1818
this.route('range', { path: '/range/:range' });
1919

2020
this.route('reverse-dependencies', { path: 'reverse_dependencies' });
21+
this.route('security');
2122

2223
this.route('owners');
2324
this.route('settings', function () {

app/routes/crate/security.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Route from '@ember/routing/route';
2+
3+
import { didCancel } from 'ember-concurrency';
4+
5+
import { AjaxError } from '../../utils/ajax';
6+
7+
async function fetchAdvisories(crateId) {
8+
let url = `https://rustsec.org/packages/${crateId}.json`;
9+
let response = await fetch(url);
10+
if (response.status === 404) {
11+
return [];
12+
} else if (response.ok) {
13+
return await response.json();
14+
} else {
15+
throw new Error(`HTTP error! status: ${response}`);
16+
}
17+
}
18+
19+
export default class SecurityRoute extends Route {
20+
queryParams = {
21+
sort: { refreshModel: true },
22+
};
23+
24+
async model() {
25+
let crate = this.modelFor('crate');
26+
try {
27+
let [advisories, micromarkModule, gfmModule] = await Promise.all([
28+
fetchAdvisories(crate.id),
29+
import('micromark'),
30+
import('micromark-extension-gfm'),
31+
]);
32+
33+
const convertMarkdown = markdown => {
34+
return micromarkModule.micromark(markdown, {
35+
extensions: [gfmModule.gfm()],
36+
htmlExtensions: [gfmModule.gfmHtml()],
37+
});
38+
};
39+
40+
return { crate, advisories, convertMarkdown };
41+
} catch (error) {
42+
// report unexpected errors to Sentry and ignore `ajax()` errors
43+
if (!didCancel(error) && !(error instanceof AjaxError)) {
44+
this.sentry.captureException(error);
45+
}
46+
}
47+
}
48+
49+
setupController(controller, { crate, advisories, convertMarkdown }) {
50+
super.setupController(...arguments);
51+
// reset when crate changes
52+
if (crate && crate !== controller.crate) {
53+
controller.reset();
54+
}
55+
controller.crate = crate;
56+
controller.advisories = advisories;
57+
controller.convertMarkdown = convertMarkdown;
58+
}
59+
}

app/templates/crate/security.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.heading {
2+
font-size: 1.17em;
3+
margin-block-start: 1em;
4+
margin-block-end: 1em;
5+
}
6+
7+
.advisories {
8+
list-style: none;
9+
margin: 0;
10+
padding: 0;
11+
}
12+
13+
.row {
14+
margin-top: var(--space-2xs);
15+
background-color: light-dark(white, #141413);
16+
padding: var(--space-m) var(--space-l);
17+
list-style: none;
18+
}

app/templates/crate/security.gjs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { htmlSafe } from '@ember/template';
2+
3+
import CrateHeader from 'crates-io/components/crate-header';
4+
5+
<template>
6+
<CrateHeader @crate={{@controller.crate}} />
7+
{{#if @controller.model}}
8+
<h2 class='heading'>Advisories</h2>
9+
<ul class='advisories' data-test-list>
10+
{{#each @controller.advisories as |advisory|}}
11+
<li class='row'>
12+
<h3>
13+
<a href='https://rustsec.org/advisories/{{advisory.id}}.html'>{{advisory.id}}</a>:
14+
{{advisory.summary}}
15+
</h3>
16+
<p>{{htmlSafe (@controller.convertMarkdown advisory.details)}}</p>
17+
</li>
18+
{{/each}}
19+
</ul>
20+
{{else}}
21+
<div class='no-results'>
22+
No advisories found for this crate.
23+
</div>
24+
{{/if}}
25+
</template>

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@
133133
"loader.js": "4.7.0",
134134
"match-json": "1.3.7",
135135
"memory-scroll": "2.0.1",
136+
"micromark": "4.0.2",
137+
"micromark-extension-gfm": "^3.0.0",
136138
"msw": "2.12.4",
137139
"playwright-msw": "3.0.1",
138140
"postcss": "8.5.6",

0 commit comments

Comments
 (0)