Skip to content
Merged
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
25 changes: 25 additions & 0 deletions app/components/trustpub-only-checkbox.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.trustpub-only-checkbox {
display: grid;
grid-template:
'checkbox label' auto
'checkbox note' auto / 16px 1fr;
row-gap: var(--space-3xs);
column-gap: var(--space-xs);
padding: var(--space-s) var(--space-m);
cursor: pointer;
}

.checkbox {
grid-area: checkbox;
}

.label {
grid-area: label;
font-weight: bold;
}

.note {
grid-area: note;
font-size: 85%;
color: var(--main-color-light);
}
41 changes: 41 additions & 0 deletions app/components/trustpub-only-checkbox.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { on } from '@ember/modifier';
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';

import LoadingSpinner from 'crates-io/components/loading-spinner';

export default class TrustpubOnlyCheckbox extends Component {
@service notifications;

@action async toggle(event) {
let { checked } = event.target;
try {
await this.args.crate.setTrustpubOnlyTask.perform(checked);
} catch (error) {
let detail = error.errors?.[0]?.detail;
if (detail && !detail.startsWith('{')) {
this.notifications.error(detail);
} else {
this.notifications.error('Failed to update trusted publishing setting');
}
}
}

<template>
<label class='trustpub-only-checkbox' data-test-trustpub-only-checkbox ...attributes>
<div class='checkbox'>
{{#if @crate.setTrustpubOnlyTask.isRunning}}
<LoadingSpinner data-test-spinner />
{{else}}
<input type='checkbox' checked={{@crate.trustpub_only}} data-test-checkbox {{on 'change' this.toggle}} />
{{/if}}
</div>
<div class='label'>Require trusted publishing for all new versions</div>
<div class='note'>
When enabled, new versions can only be published through configured trusted publishers. Publishing with API
tokens will be rejected.
</div>
</label>
</template>
}
19 changes: 19 additions & 0 deletions app/controllers/crate/settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ export default class CrateSettingsController extends Controller {
username = '';
@tracked addOwnerVisible = false;

/**
* Tracks whether the trustpub_only checkbox was visible when the page loaded.
* This prevents the checkbox from disappearing immediately when unchecked
* if there are no configs - it will only disappear on the next page visit.
*/
trustpubOnlyCheckboxWasVisible = false;

get #hasConfigs() {
return this.githubConfigs?.length > 0 || this.gitlabConfigs?.length > 0;
}

get showTrustpubOnlyCheckbox() {
return this.#hasConfigs || this.crate?.trustpub_only || this.trustpubOnlyCheckboxWasVisible;
}

get showTrustpubOnlyWarning() {
return this.crate?.trustpub_only && !this.#hasConfigs;
}

@action showAddOwnerForm() {
this.addOwnerVisible = true;
this.username = '';
Expand Down
14 changes: 13 additions & 1 deletion app/models/crate.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { waitForPromise } from '@ember/test-waiters';
import { cached } from '@glimmer/tracking';

import { apiAction } from '@mainmatter/ember-api-actions';
import { task } from 'ember-concurrency';
import { keepLatestTask, task } from 'ember-concurrency';

export default class Crate extends Model {
@attr name;
Expand All @@ -29,6 +29,12 @@ export default class Crate extends Model {
@attr documentation;
@attr repository;

/**
* Whether this crate can only be published via Trusted Publishing.
* @type {boolean}
*/
@attr trustpub_only;

/**
* This isn't an attribute in the crate response.
* It's actually the `meta` attribute that belongs to `versions`
Expand Down Expand Up @@ -123,4 +129,10 @@ export default class Crate extends Model {
let fut = reload === true ? versionsRef.reload() : versionsRef.load();
return (await fut) ?? [];
});

setTrustpubOnlyTask = keepLatestTask(async trustpubOnly => {
let data = { crate: { trustpub_only: trustpubOnly } };
let payload = await waitForPromise(apiAction(this, { method: 'PATCH', data }));
this.store.pushPayload(payload);
});
}
4 changes: 4 additions & 0 deletions app/routes/crate/settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@ export default class SettingsIndexRoute extends Route {
controller.set('crate', crate);
controller.set('githubConfigs', githubConfigs);
controller.set('gitlabConfigs', gitlabConfigs);

// Capture whether the trustpub_only checkbox should be visible on initial load
let hasConfigs = githubConfigs?.length > 0 || gitlabConfigs?.length > 0;
controller.set('trustpubOnlyCheckboxWasVisible', hasConfigs || crate.trustpub_only);
}
}
15 changes: 14 additions & 1 deletion app/templates/crate/settings/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
background-color: light-dark(white, #141413);
border-radius: var(--space-3xs);
box-shadow: 0 1px 3px light-dark(hsla(51, 90%, 42%, .35), #232321);
}

.trustpub table {
width: 100%;
border-spacing: 0;

:global(tbody) > :global(tr) > :global(td) {
border-top: 1px solid light-dark(hsla(51, 90%, 42%, .25), #232321);
Expand Down Expand Up @@ -82,7 +87,7 @@
display: none;
}

tbody > tr > td:first-child {
tbody > tr:not(.no-trustpub-config) > td:first-child {
padding-bottom: 0;
}

Expand All @@ -109,6 +114,14 @@
}
}

.trustpub-only-warning {
margin-bottom: var(--space-s);
}

.trustpub-only-checkbox {
border-top: 1px solid light-dark(hsla(51, 90%, 42%, 0.25), #232321);
}

.email-column {
width: 25%;
color: var(--main-color-light);
Expand Down
Loading
Loading