Skip to content

Commit 8c39758

Browse files
committed
Initial tab at a security tab showing advisories
1 parent 9a435a7 commit 8c39758

File tree

6 files changed

+119
-0
lines changed

6 files changed

+119
-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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import Controller from '@ember/controller';
2+
import { service } from '@ember/service';
3+
import { tracked } from '@glimmer/tracking';
4+
5+
import { didCancel, dropTask } from 'ember-concurrency';
6+
7+
import { AjaxError } from '../../utils/ajax';
8+
9+
export default class SearchController extends Controller {
10+
@service releaseTracks;
11+
@service sentry;
12+
13+
@tracked crate;
14+
@tracked data;
15+
16+
constructor() {
17+
super(...arguments);
18+
this.reset();
19+
}
20+
21+
loadMoreTask = dropTask(async () => {
22+
let { crate } = this;
23+
let url = `https://rustsec.org/packages/${crate.id}.json`;
24+
25+
try {
26+
let response = await fetch(url);
27+
if (response.status === 404) {
28+
this.data = [];
29+
} else if (response.ok) {
30+
this.data = await response.json();
31+
} else {
32+
throw new Error(`HTTP error! status: ${response}`);
33+
}
34+
} catch (error) {
35+
// report unexpected errors to Sentry and ignore `ajax()` errors
36+
if (!didCancel(error) && !(error instanceof AjaxError)) {
37+
this.sentry.captureException(error);
38+
}
39+
}
40+
});
41+
42+
reset() {
43+
this.crate = undefined;
44+
this.data = undefined;
45+
}
46+
}

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: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Route from '@ember/routing/route';
2+
import { waitForPromise } from '@ember/test-waiters';
3+
4+
export default class SecurityRoute extends Route {
5+
queryParams = {
6+
sort: { refreshModel: true },
7+
};
8+
9+
model(params) {
10+
// we need a model() implementation that changes, otherwise the setupController() hook
11+
// is not called and we won't reload the results if a new query string is used
12+
return params;
13+
}
14+
15+
setupController(controller) {
16+
super.setupController(...arguments);
17+
let crate = this.modelFor('crate');
18+
// reset when crate changes
19+
if (crate && crate !== controller.crate) {
20+
controller.reset();
21+
}
22+
controller.set('crate', crate);
23+
if (controller.data === undefined) {
24+
waitForPromise(controller.loadMoreTask.perform());
25+
}
26+
}
27+
}

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

0 commit comments

Comments
 (0)