Skip to content

Commit 5bb47e1

Browse files
Merge branch 'llb/app-tokens' into ICU-18005-ui-create-component-ttl-tts-inputs
2 parents ceedfbe + 4823c1b commit 5bb47e1

File tree

13 files changed

+1400
-5
lines changed

13 files changed

+1400
-5
lines changed

addons/api/addon/models/app-token.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,16 @@ export default class AppTokenModel extends GeneratedAppTokenModel {
4141
? this.time_to_stale_seconds * 1000
4242
: null;
4343
}
44+
45+
/**
46+
* Calculate expires in days from now
47+
* @returns {number|null} Days until expiration, or null if no expiration set
48+
*/
49+
get expiresIn() {
50+
if (!this.expire_time) return null;
51+
const now = new Date();
52+
const diffMs = this.expire_time.getTime() - now.getTime();
53+
const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
54+
return diffDays > 0 ? diffDays : 0;
55+
}
4456
}

addons/api/mirage/factories/app-token.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,35 @@ export default factory.extend({
2121
],
2222
status: faker.helpers.arrayElement(statuses),
2323
created_by_user_id: () => generateId('u_'),
24+
25+
// Make expiration times more varied
26+
expire_time: () => {
27+
const now = new Date();
28+
const daysFromNow = faker.number.int({ min: 1, max: 365 });
29+
return faker.date.future({ days: daysFromNow, refDate: now });
30+
},
31+
32+
// Vary the last access time
33+
approximate_last_access_time: () => {
34+
return faker.date.between({
35+
from: faker.date.recent({ days: 30 }),
36+
to: new Date(),
37+
});
38+
},
39+
40+
// Make time to live with more variations
41+
time_to_live_seconds: () => {
42+
const options = [
43+
3600, // 1 hour
44+
86400, // 1 day
45+
604800, // 1 week
46+
2592000, // 30 days
47+
7776000, // 90 days
48+
31536000, // 1 year
49+
];
50+
return faker.helpers.arrayElement(options);
51+
},
52+
2453
permissions: () => [
2554
{
2655
grant: ['ids=*;actions=*'],

addons/api/mirage/generated/factories/app-token.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@ export default Factory.extend({
1515
created_time: () => faker.date.recent(),
1616
updated_time: () => faker.date.recent(),
1717
token: () => faker.string.alphanumeric(50),
18-
approximate_last_access_time: () => faker.date.recent(),
19-
expire_time: () => faker.date.soon(),
20-
time_to_live_seconds: () => faker.number.int(),
2118
time_to_stale_seconds: () => faker.number.int(),
2219
version: () => faker.number.int(),
2320
});

addons/api/mirage/scenarios/default.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
*/
55

66
import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
7+
import {
8+
STATUS_APP_TOKEN_ACTIVE,
9+
STATUS_APP_TOKEN_EXPIRED,
10+
STATUS_APP_TOKEN_REVOKED,
11+
STATUS_APP_TOKEN_STALE,
12+
} from 'api/models/app-token';
13+
import { faker } from '@faker-js/faker';
714

815
export default function (server) {
916
// Scope resources
@@ -171,4 +178,57 @@ export default function (server) {
171178
'withConnectionAndChannels',
172179
'withExistingUserAndTarget',
173180
);
181+
182+
// App Tokens
183+
const statuses = [
184+
STATUS_APP_TOKEN_ACTIVE,
185+
STATUS_APP_TOKEN_EXPIRED,
186+
STATUS_APP_TOKEN_REVOKED,
187+
STATUS_APP_TOKEN_STALE,
188+
];
189+
190+
// Global scope tokens
191+
statuses.forEach((status) => {
192+
server.createList('app-token', 3, {
193+
scope: globalScope,
194+
scopeId: globalScope.id,
195+
status: status,
196+
});
197+
});
198+
199+
// Org scope tokens
200+
const allOrgScopes = server.schema.scopes.where({ type: 'org' }).models;
201+
allOrgScopes.forEach((orgScope) => {
202+
server.create('app-token', {
203+
scope: orgScope,
204+
scopeId: orgScope.id,
205+
status: STATUS_APP_TOKEN_ACTIVE,
206+
});
207+
server.create('app-token', {
208+
scope: orgScope,
209+
scopeId: orgScope.id,
210+
status: STATUS_APP_TOKEN_EXPIRED,
211+
});
212+
server.createList('app-token', 5, {
213+
scope: orgScope,
214+
scopeId: orgScope.id,
215+
status: () => faker.helpers.arrayElement(statuses),
216+
});
217+
});
218+
219+
// Project scope tokens
220+
server.schema.scopes
221+
.where({ type: 'project' })
222+
.models.forEach((projectScope) => {
223+
server.create('app-token', {
224+
scope: projectScope,
225+
scopeId: projectScope.id,
226+
status: STATUS_APP_TOKEN_ACTIVE,
227+
});
228+
server.createList('app-token', 4, {
229+
scope: projectScope,
230+
scopeId: projectScope.id,
231+
status: () => faker.helpers.arrayElement(statuses),
232+
});
233+
});
174234
}

addons/core/translations/resources/en-us.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,3 +1235,23 @@ alias:
12351235
help: You can create aliases for this target that will make it easier to reach. Users must have access to this project to use aliases.
12361236
app-token:
12371237
title_plural: App Tokens
1238+
description: App tokens are long-lived, fine grained access tokens for authorization/authentication.
1239+
form:
1240+
approximate_last_access_time:
1241+
label: Last used at
1242+
expires_in:
1243+
label: Expires in
1244+
status:
1245+
label: Status
1246+
status:
1247+
unknown: Unknown
1248+
active: Active
1249+
revoked: Revoked
1250+
expired: Expired
1251+
stale: Stale
1252+
messages:
1253+
none:
1254+
title: No App Tokens Available
1255+
global-scope: No app tokens have been created at the global scope level.
1256+
org-scope: No app tokens have been created for {scopeName}.
1257+
project-scope: No app tokens have been created for {scopeName}.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import Controller from '@ember/controller';
7+
import { tracked } from '@glimmer/tracking';
8+
import { action } from '@ember/object';
9+
import { service } from '@ember/service';
10+
import { STATUSES_APP_TOKEN } from 'api/models/app-token';
11+
12+
export default class ScopesScopeAppTokensIndexController extends Controller {
13+
// =services
14+
15+
@service intl;
16+
17+
// =attributes
18+
19+
queryParams = [
20+
'search',
21+
'page',
22+
'pageSize',
23+
'sortAttribute',
24+
'sortDirection',
25+
{ statuses: { type: 'array' } },
26+
];
27+
28+
@tracked search;
29+
@tracked page = 1;
30+
@tracked pageSize = 10;
31+
@tracked sortAttribute;
32+
@tracked sortDirection;
33+
@tracked statuses = [];
34+
35+
/**
36+
* Status options for filtering
37+
*/
38+
get statusOptions() {
39+
return STATUSES_APP_TOKEN.map((status) => ({
40+
id: status,
41+
name: this.intl.t(`resources.app-token.status.${status}`),
42+
}));
43+
}
44+
45+
/**
46+
* Returns status badge configuration for app tokens
47+
* @param {string} status
48+
* @returns {object}
49+
*/
50+
@action
51+
getStatusBadge(status) {
52+
const statusConfig = {
53+
active: { color: 'success' },
54+
expired: { color: 'critical' },
55+
revoked: { color: 'critical' },
56+
stale: { color: 'critical' },
57+
unknown: { color: 'neutral' },
58+
};
59+
60+
const config = statusConfig[status] || { color: 'neutral' };
61+
return {
62+
text: this.intl.t(`resources.app-token.status.${status}`),
63+
color: config.color,
64+
};
65+
}
66+
67+
/**
68+
* Formats date for tooltip display
69+
* @param {Date} date
70+
* @returns {string}
71+
*/
72+
@action
73+
formatTooltipDate(date) {
74+
return date.toLocaleDateString('en-US', {
75+
year: 'numeric',
76+
month: 'short',
77+
day: 'numeric',
78+
hour: 'numeric',
79+
minute: '2-digit',
80+
hour12: true,
81+
});
82+
}
83+
84+
/**
85+
* Returns object of filters to be used for displaying selected filters
86+
* @returns {object}
87+
*/
88+
get filters() {
89+
return {
90+
allFilters: {
91+
statuses: this.statusOptions,
92+
},
93+
selectedFilters: {
94+
statuses: this.statuses,
95+
},
96+
};
97+
}
98+
99+
// =actions
100+
101+
/**
102+
* Handles input on each keystroke and the search queryParam
103+
* @param {object} event
104+
*/
105+
@action
106+
handleSearchInput(event) {
107+
const { value } = event.target;
108+
this.search = value;
109+
this.page = 1;
110+
}
111+
112+
/**
113+
* Sets the selected items for the given paramKey and sets the page to 1
114+
* @param {string} paramKey
115+
* @param {[string]} selectedItems
116+
*/
117+
@action
118+
applyFilter(paramKey, selectedItems) {
119+
this[paramKey] = [...selectedItems];
120+
this.page = 1;
121+
}
122+
123+
/**
124+
* Sets sort values and sets page to 1
125+
* @param {string} sortBy
126+
* @param {string} sortOrder
127+
*/
128+
@action
129+
onSort(sortBy, sortOrder) {
130+
this.sortAttribute = sortBy;
131+
this.sortDirection = sortOrder;
132+
this.page = 1;
133+
}
134+
}

ui/admin/app/router.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ Router.map(function () {
187187
this.route('new');
188188
this.route('policy', { path: ':policy_id' }, function () {});
189189
});
190-
this.route('app-tokens');
190+
this.route('app-tokens', function () {});
191191
});
192192
});
193193

0 commit comments

Comments
 (0)