diff --git a/app/components/settings/api-tokens.hbs b/app/components/settings/api-tokens.hbs index b4153d4f5d..095a46533e 100644 --- a/app/components/settings/api-tokens.hbs +++ b/app/components/settings/api-tokens.hbs @@ -71,16 +71,50 @@ {{token.name}} -
- {{#if token.last_used_at}} - Last used {{date-format-distance-to-now token.last_used_at addSuffix=true}} - {{else}} - Never used - {{/if}} -
+ {{#if (or token.endpoint_scopes token.crate_scopes)}} +
+ {{#if token.endpoint_scopes}} +
+ Scopes: + + {{#each (this.listToParts token.endpoint_scopes) as |part|~}} + {{#if (eq part.type "element")}} + {{part.value}} + {{~else~}} + {{part.value}} + {{/if}} + {{~/each}} +
+ {{/if}} + + {{#if token.crate_scopes}} +
+ Crates: + + {{#each (this.listToParts token.crate_scopes) as |part|~}} + {{#if (eq part.type "element")}} + {{part.value}} + {{~else~}} + {{part.value}} + {{/if}} + {{~/each}} +
+ {{/if}} +
+ {{/if}} -
- Created {{date-format-distance-to-now token.created_at addSuffix=true}} +
+
+ {{#if token.last_used_at}} + Last used {{date-format-distance-to-now token.last_used_at addSuffix=true}} + {{else}} + Never used + {{/if}} +
+ +
+ Created {{date-format-distance-to-now token.created_at addSuffix=true}} +
{{#if token.token}} diff --git a/app/components/settings/api-tokens.js b/app/components/settings/api-tokens.js index 0aa28aefdc..fb09965d0c 100644 --- a/app/components/settings/api-tokens.js +++ b/app/components/settings/api-tokens.js @@ -5,6 +5,8 @@ import { tracked } from '@glimmer/tracking'; import { task } from 'ember-concurrency'; +import { patternDescription, scopeDescription } from '../../utils/token-scopes'; + export default class ApiTokens extends Component { @service store; @service notifications; @@ -12,10 +14,18 @@ export default class ApiTokens extends Component { @tracked newToken; + scopeDescription = scopeDescription; + patternDescription = patternDescription; + get sortedTokens() { return this.args.tokens.filter(t => !t.isNew).sort((a, b) => (a.created_at < b.created_at ? 1 : -1)); } + listToParts(list) { + // We hardcode `en-US` here because the rest of the interface text is also currently displayed only in English. + return new Intl.ListFormat('en-US').formatToParts(list); + } + @action startNewToken(event) { if (event.altKey) { this.router.transitionTo('settings.tokens.new'); diff --git a/app/components/settings/api-tokens.module.css b/app/components/settings/api-tokens.module.css index a6e583f29f..e81d0f602c 100644 --- a/app/components/settings/api-tokens.module.css +++ b/app/components/settings/api-tokens.module.css @@ -35,17 +35,21 @@ } .name { - margin: 0 0 12px; + margin: 0 0 var(--space-s); font-weight: 500; } -.dates { +.scopes, +.metadata { + composes: small from '../../styles/shared/typography.module.css'; + + > * + * { + margin-top: var(--space-3xs); + } } -.created-at, -.last-used-at { - composes: small from '../../styles/shared/typography.module.css'; - margin-top: 4px; +.scopes { + margin-bottom: var(--space-xs); } .new-token-form { @@ -168,11 +172,19 @@ display: grid; grid-template: "name actions" auto - "last-user actions" auto - "created-at actions" auto + "scopes actions" auto + "metadata actions" auto "details details" auto / 1fr auto; + .scopes { + grid-area: scopes; + } + + .metadata { + grid-area: metadata; + } + .actions { grid-area: actions; align-self: start; diff --git a/app/controllers/settings/tokens/new.js b/app/controllers/settings/tokens/new.js index fde3af6d7f..4a9f54046b 100644 --- a/app/controllers/settings/tokens/new.js +++ b/app/controllers/settings/tokens/new.js @@ -1,12 +1,13 @@ import Controller from '@ember/controller'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; -import { htmlSafe } from '@ember/template'; import { tracked } from '@glimmer/tracking'; import { task } from 'ember-concurrency'; import { TrackedArray } from 'tracked-built-ins'; +import { patternDescription, scopeDescription } from '../../../utils/token-scopes'; + export default class NewTokenController extends Controller { @service notifications; @service sentry; @@ -19,12 +20,9 @@ export default class NewTokenController extends Controller { @tracked scopesInvalid; @tracked crateScopes; - ENDPOINT_SCOPES = [ - { id: 'change-owners', description: 'Invite new crate owners or remove existing ones' }, - { id: 'publish-new', description: 'Publish new crates' }, - { id: 'publish-update', description: 'Publish new versions of existing crates' }, - { id: 'yank', description: 'Yank and unyank crate versions' }, - ]; + ENDPOINT_SCOPES = ['change-owners', 'publish-new', 'publish-update', 'yank']; + + scopeDescription = scopeDescription; constructor() { super(...arguments); @@ -120,14 +118,10 @@ class CratePattern { get description() { if (!this.pattern) { return 'Please enter a crate name pattern'; - } else if (this.pattern === '*') { - return 'Matches all crates on crates.io'; - } else if (!this.isValid) { - return 'Invalid crate name pattern'; - } else if (this.hasWildcard) { - return htmlSafe(`Matches all crates starting with ${this.pattern.slice(0, -1)}`); + } else if (this.isValid) { + return patternDescription(this.pattern); } else { - return htmlSafe(`Matches only the ${this.pattern} crate`); + return 'Invalid crate name pattern'; } } diff --git a/app/styles/shared/typography.module.css b/app/styles/shared/typography.module.css index 60abd926a8..dee269ce12 100644 --- a/app/styles/shared/typography.module.css +++ b/app/styles/shared/typography.module.css @@ -5,6 +5,10 @@ strong { color: var(--main-color); } + + :global(.tooltip) strong { + color: inherit; + } } .small a, a.small { diff --git a/app/templates/settings/tokens/new.hbs b/app/templates/settings/tokens/new.hbs index cf91d7e48f..a2bde9c271 100644 --- a/app/templates/settings/tokens/new.hbs +++ b/app/templates/settings/tokens/new.hbs @@ -44,16 +44,16 @@