@spinnaker/core 2026.1.0 → 2026.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apitoken/ApiTokenService.d.ts +34 -0
- package/dist/apitoken/ApiTokensPage.d.ts +15 -0
- package/dist/apitoken/ApiTokensPageContainer.d.ts +2 -0
- package/dist/apitoken/CreateApiTokenModal.d.ts +20 -0
- package/dist/apitoken/RevokeApiTokenButton.d.ts +7 -0
- package/dist/apitoken/apitoken.module.d.ts +1 -0
- package/dist/apitoken/apitoken.states.d.ts +1 -0
- package/dist/apitoken/index.d.ts +6 -0
- package/dist/application/application.initializers.d.ts +7 -0
- package/dist/application/index.d.ts +2 -0
- package/dist/authentication/AuthenticationService.d.ts +1 -0
- package/dist/config/settings.d.ts +0 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +36 -36
- package/dist/index.js.map +1 -1
- package/dist/notification/selector/types/index.d.ts +0 -1
- package/package.json +12 -7
- package/rollup.config.js +16 -0
- package/src/apitoken/ApiTokenService.spec.ts +199 -0
- package/src/apitoken/ApiTokenService.ts +65 -0
- package/src/apitoken/ApiTokensPage.less +81 -0
- package/src/apitoken/ApiTokensPage.tsx +370 -0
- package/src/apitoken/ApiTokensPageContainer.tsx +85 -0
- package/src/apitoken/CreateApiTokenModal.tsx +228 -0
- package/src/apitoken/RevokeApiTokenButton.tsx +79 -0
- package/src/apitoken/apitoken.module.ts +20 -0
- package/src/apitoken/apitoken.states.ts +43 -0
- package/src/apitoken/index.ts +20 -0
- package/src/application/application.initializers.spec.ts +34 -0
- package/src/application/application.initializers.ts +40 -0
- package/src/application/application.module.ts +2 -0
- package/src/application/index.ts +6 -0
- package/src/authentication/AuthenticationInitializer.spec.ts +16 -0
- package/src/authentication/AuthenticationInitializer.ts +4 -0
- package/src/authentication/AuthenticationService.spec.ts +18 -0
- package/src/authentication/AuthenticationService.ts +3 -0
- package/src/authentication/userMenu/UserMenu.tsx +17 -3
- package/src/config/settings.ts +0 -1
- package/src/core.module.ts +2 -0
- package/src/index.ts +1 -0
- package/src/notification/notification.types.ts +0 -2
- package/src/notification/selector/types/index.ts +0 -1
- package/src/notification/selector/types/microsoftteams/MicrosoftTeamsNotificationType.tsx +1 -1
- package/dist/notification/selector/types/bearychat/BearychatNotificationType.d.ts +0 -5
- package/dist/notification/selector/types/bearychat/beary.notification.d.ts +0 -2
- package/src/notification/selector/types/bearychat/BearychatNotificationType.tsx +0 -19
- package/src/notification/selector/types/bearychat/beary.notification.ts +0 -8
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"url": "https://github.com/spinnaker/spinnaker.git"
|
|
6
6
|
},
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
|
-
"version": "2026.
|
|
8
|
+
"version": "2026.2.0",
|
|
9
9
|
"module": "dist/index.js",
|
|
10
10
|
"typings": "dist/index.d.ts",
|
|
11
11
|
"publishConfig": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@apollo/client": "^3.6.9",
|
|
26
26
|
"@fortawesome/fontawesome-free": "5.5.0",
|
|
27
|
+
"@spinnaker/kayenta": "workspace:^2.6.0",
|
|
27
28
|
"@spinnaker/mocks": "1.0.7",
|
|
28
29
|
"@spinnaker/presentation": "^0.3.1",
|
|
29
30
|
"@spinnaker/styleguide": "2.0.0",
|
|
@@ -33,9 +34,10 @@
|
|
|
33
34
|
"@uirouter/react-hybrid": "1.0.2",
|
|
34
35
|
"@uirouter/rx": "0.6.5",
|
|
35
36
|
"@uirouter/visualizer": "7.2.1",
|
|
36
|
-
"
|
|
37
|
-
"angular
|
|
38
|
-
"angular-
|
|
37
|
+
"ajv": "^6.14.0",
|
|
38
|
+
"angular": "1.8.3",
|
|
39
|
+
"angular-messages": "1.8.3",
|
|
40
|
+
"angular-sanitize": "1.8.3",
|
|
39
41
|
"angular-spinner": "1.0.1",
|
|
40
42
|
"angular-ui-bootstrap": "2.5.0",
|
|
41
43
|
"angular-ui-sortable": "0.17.1",
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
"commonmark": "0.28.1",
|
|
52
54
|
"date-fns": "2.21.1",
|
|
53
55
|
"diff-match-patch": "1.0.4",
|
|
54
|
-
"dompurify": "^
|
|
56
|
+
"dompurify": "^3.0.0",
|
|
55
57
|
"enzyme": "3.11.0",
|
|
56
58
|
"formik": "1.5.1",
|
|
57
59
|
"human-readable-ids": "1.0.4",
|
|
@@ -72,6 +74,7 @@
|
|
|
72
74
|
"react-day-picker": "7.3.2",
|
|
73
75
|
"react-dom": "16.14.0",
|
|
74
76
|
"react-ga": "2.4.1",
|
|
77
|
+
"react-redux": "^5.1.2",
|
|
75
78
|
"react-select": "1.2.1",
|
|
76
79
|
"react-sortable-hoc": "0.6.8",
|
|
77
80
|
"react-spring": "8.0.27",
|
|
@@ -83,6 +86,8 @@
|
|
|
83
86
|
"react-virtualized-select": "3.1.3",
|
|
84
87
|
"react2angular": "3.2.1",
|
|
85
88
|
"recoil": "0.7.2",
|
|
89
|
+
"redux-actions": "^2.6.5",
|
|
90
|
+
"reselect": "^3.0.1",
|
|
86
91
|
"rxjs": "6.6.7",
|
|
87
92
|
"select2": "^3.5.1",
|
|
88
93
|
"select2-bootstrap-css": "1.4.6",
|
|
@@ -102,7 +107,7 @@
|
|
|
102
107
|
"@types/angular-mocks": "1.5.10",
|
|
103
108
|
"@types/angular-ui-bootstrap": "0.13.41",
|
|
104
109
|
"@types/classnames": "2.2.0",
|
|
105
|
-
"@types/clipboard": "1.5.
|
|
110
|
+
"@types/clipboard": "1.5.36",
|
|
106
111
|
"@types/commonmark": "0.27.0",
|
|
107
112
|
"@types/dompurify": "^2.3.3",
|
|
108
113
|
"@types/enzyme": "3.10.3",
|
|
@@ -128,5 +133,5 @@
|
|
|
128
133
|
"shx": "0.3.3",
|
|
129
134
|
"typescript": "5.0.4"
|
|
130
135
|
},
|
|
131
|
-
"gitHead": "
|
|
136
|
+
"gitHead": "a9155eb5c4e40b9db5aa9741af28fc2412f7c7d8"
|
|
132
137
|
}
|
package/rollup.config.js
CHANGED
|
@@ -30,9 +30,25 @@ const fixTSPathRewrite = () => {
|
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
// @rollup/plugin-node-resolve v15 defaults to ['.mjs', '.js', '.json', '.node'] with no .ts/.tsx.
|
|
34
|
+
// This plugin fills the gap: when a bare relative directory import can't be resolved, try
|
|
35
|
+
// appending /index.ts and /index.tsx so that TypeScript barrel files are found.
|
|
36
|
+
const tsDirResolvePlugin = {
|
|
37
|
+
name: 'ts-dir-resolve',
|
|
38
|
+
resolveId(source, importer) {
|
|
39
|
+
if (!importer || !source.startsWith('.')) return null;
|
|
40
|
+
const dir = path.resolve(path.dirname(importer), source);
|
|
41
|
+
for (const candidate of [`${dir}/index.ts`, `${dir}/index.tsx`, `${dir}.ts`, `${dir}.tsx`]) {
|
|
42
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
33
48
|
export default {
|
|
34
49
|
...baseRollupConfig,
|
|
35
50
|
plugins: [
|
|
51
|
+
tsDirResolvePlugin,
|
|
36
52
|
...baseRollupConfig.plugins,
|
|
37
53
|
// `core` has some custom type declarations for `promise` and `svg` that needs to be bundled together in the
|
|
38
54
|
// distribution. These custom type declarations are not automatically copied over by typescript, so needs to be
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Copyright 2026 DoorDash, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import { mockHttpClient } from '../api/mock/jasmine';
|
|
16
|
+
import type { IApiTokenServiceAccount, IApiToken, ICreateApiTokenRequest } from './ApiTokenService';
|
|
17
|
+
import { ApiTokenService } from './ApiTokenService';
|
|
18
|
+
|
|
19
|
+
const TOKEN_FIXTURE: IApiToken = {
|
|
20
|
+
id: 'tok-uuid-1',
|
|
21
|
+
name: 'ci-deploy',
|
|
22
|
+
principalType: 'USER',
|
|
23
|
+
principalId: 'alice@doordash.com',
|
|
24
|
+
createdByUserId: 'alice@doordash.com',
|
|
25
|
+
expiresAt: new Date(Date.now() + 30 * 86400 * 1000).toISOString(),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const SA_TOKEN_FIXTURE: IApiToken = {
|
|
29
|
+
id: 'tok-uuid-2',
|
|
30
|
+
name: 'ci-bot-token',
|
|
31
|
+
principalType: 'SERVICE_ACCOUNT',
|
|
32
|
+
principalId: 'ci-pipeline-bot',
|
|
33
|
+
createdByUserId: 'admin@doordash.com',
|
|
34
|
+
expiresAt: undefined,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const SA_FIXTURE: IApiTokenServiceAccount = {
|
|
38
|
+
name: 'ci-pipeline-bot',
|
|
39
|
+
memberOf: ['deploy-team'],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe('ApiTokenService', () => {
|
|
43
|
+
describe('listTokens()', () => {
|
|
44
|
+
it('GETs /auth/apiTokens and returns the token array', async () => {
|
|
45
|
+
const http = mockHttpClient();
|
|
46
|
+
http.expectGET(/\/auth\/apiTokens$/).respond(200, [TOKEN_FIXTURE]);
|
|
47
|
+
|
|
48
|
+
let result: IApiToken[] | undefined;
|
|
49
|
+
ApiTokenService.listTokens().then((tokens) => {
|
|
50
|
+
result = tokens;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await http.flush();
|
|
54
|
+
expect(result).toEqual([TOKEN_FIXTURE]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns an empty array when there are no tokens', async () => {
|
|
58
|
+
const http = mockHttpClient();
|
|
59
|
+
http.expectGET(/\/auth\/apiTokens$/).respond(200, []);
|
|
60
|
+
|
|
61
|
+
let result: IApiToken[] | undefined;
|
|
62
|
+
ApiTokenService.listTokens().then((tokens) => {
|
|
63
|
+
result = tokens;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await http.flush();
|
|
67
|
+
expect(result).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('includes tokens with null expiresAt (non-expiring SA tokens)', async () => {
|
|
71
|
+
const http = mockHttpClient();
|
|
72
|
+
http.expectGET(/\/auth\/apiTokens$/).respond(200, [SA_TOKEN_FIXTURE]);
|
|
73
|
+
|
|
74
|
+
let result: IApiToken[] | undefined;
|
|
75
|
+
ApiTokenService.listTokens().then((tokens) => {
|
|
76
|
+
result = tokens;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await http.flush();
|
|
80
|
+
expect(result![0].expiresAt).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('createToken()', () => {
|
|
85
|
+
it('POSTs to /auth/apiTokens with the request body', async () => {
|
|
86
|
+
const http = mockHttpClient();
|
|
87
|
+
const created = { ...TOKEN_FIXTURE, token: 'spk_abc123' };
|
|
88
|
+
http.expectPOST(/\/auth\/apiTokens$/).respond(201, created);
|
|
89
|
+
|
|
90
|
+
const request: ICreateApiTokenRequest = {
|
|
91
|
+
name: 'ci-deploy',
|
|
92
|
+
principalType: 'USER',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
let result: (IApiToken & { token: string }) | undefined;
|
|
96
|
+
ApiTokenService.createToken(request).then((token) => {
|
|
97
|
+
result = token;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await http.flush();
|
|
101
|
+
expect(result).toEqual(created);
|
|
102
|
+
expect(result!.token).toBe('spk_abc123');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('includes principalId and expiresAt when provided', async () => {
|
|
106
|
+
const http = mockHttpClient();
|
|
107
|
+
const expiry = new Date(Date.now() + 7 * 86400 * 1000).toISOString();
|
|
108
|
+
const created = { ...SA_TOKEN_FIXTURE, token: 'spk_xyz789', expiresAt: expiry };
|
|
109
|
+
http.expectPOST(/\/auth\/apiTokens$/).respond(201, created);
|
|
110
|
+
|
|
111
|
+
const request: ICreateApiTokenRequest = {
|
|
112
|
+
name: 'ci-bot-token',
|
|
113
|
+
principalType: 'SERVICE_ACCOUNT',
|
|
114
|
+
principalId: 'ci-pipeline-bot',
|
|
115
|
+
expiresAt: expiry,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
let result: (IApiToken & { token: string }) | undefined;
|
|
119
|
+
ApiTokenService.createToken(request).then((token) => {
|
|
120
|
+
result = token;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await http.flush();
|
|
124
|
+
expect(result!.expiresAt).toBe(expiry);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('handles creation of a non-expiring SA token (no expiresAt in request)', async () => {
|
|
128
|
+
const http = mockHttpClient();
|
|
129
|
+
const created = { ...SA_TOKEN_FIXTURE, token: 'spk_never' };
|
|
130
|
+
http.expectPOST(/\/auth\/apiTokens$/).respond(201, created);
|
|
131
|
+
|
|
132
|
+
const request: ICreateApiTokenRequest = {
|
|
133
|
+
name: 'ci-bot-token',
|
|
134
|
+
principalType: 'SERVICE_ACCOUNT',
|
|
135
|
+
principalId: 'ci-pipeline-bot',
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
let result: (IApiToken & { token: string }) | undefined;
|
|
139
|
+
ApiTokenService.createToken(request).then((token) => {
|
|
140
|
+
result = token;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await http.flush();
|
|
144
|
+
expect(result!.expiresAt).toBeUndefined();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('revokeToken()', () => {
|
|
149
|
+
it('DELETEs /auth/apiTokens/{id}', async () => {
|
|
150
|
+
const http = mockHttpClient();
|
|
151
|
+
http.expectDELETE(/\/auth\/apiTokens\/tok-uuid-1$/).respond(204, null);
|
|
152
|
+
|
|
153
|
+
let resolved = false;
|
|
154
|
+
ApiTokenService.revokeToken('tok-uuid-1').then(() => {
|
|
155
|
+
resolved = true;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await http.flush();
|
|
159
|
+
expect(resolved).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('passes the correct token id in the path', async () => {
|
|
163
|
+
const http = mockHttpClient();
|
|
164
|
+
const customId = 'some-other-uuid-789';
|
|
165
|
+
http.expectDELETE(new RegExp(`\\/auth\\/apiTokens\\/${customId}$`)).respond(204, null);
|
|
166
|
+
|
|
167
|
+
ApiTokenService.revokeToken(customId);
|
|
168
|
+
await http.flush();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('listApiTokenServiceAccounts()', () => {
|
|
173
|
+
it('GETs /auth/apiTokens/serviceAccounts', async () => {
|
|
174
|
+
const http = mockHttpClient();
|
|
175
|
+
http.expectGET(/\/auth\/apiTokens\/serviceAccounts$/).respond(200, [SA_FIXTURE]);
|
|
176
|
+
|
|
177
|
+
let result: IApiTokenServiceAccount[] | undefined;
|
|
178
|
+
ApiTokenService.listApiTokenServiceAccounts().then((accounts) => {
|
|
179
|
+
result = accounts;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await http.flush();
|
|
183
|
+
expect(result).toEqual([SA_FIXTURE]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('returns an empty array when no API token service accounts are configured', async () => {
|
|
187
|
+
const http = mockHttpClient();
|
|
188
|
+
http.expectGET(/\/auth\/apiTokens\/serviceAccounts$/).respond(200, []);
|
|
189
|
+
|
|
190
|
+
let result: IApiTokenServiceAccount[] | undefined;
|
|
191
|
+
ApiTokenService.listApiTokenServiceAccounts().then((accounts) => {
|
|
192
|
+
result = accounts;
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await http.flush();
|
|
196
|
+
expect(result).toEqual([]);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Copyright 2026 DoorDash, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import { REST } from '../api/ApiService';
|
|
16
|
+
|
|
17
|
+
export interface IApiToken {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
principalType: 'USER' | 'SERVICE_ACCOUNT';
|
|
21
|
+
principalId: string;
|
|
22
|
+
createdByUserId: string;
|
|
23
|
+
/** Absent for non-expiring service account tokens. */
|
|
24
|
+
expiresAt?: string | null;
|
|
25
|
+
lastUsedAt?: string;
|
|
26
|
+
createdAt?: number;
|
|
27
|
+
lastModified?: number;
|
|
28
|
+
/** Only present immediately after creation. Never stored or returned again. */
|
|
29
|
+
token?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ICreateApiTokenRequest {
|
|
33
|
+
name: string;
|
|
34
|
+
principalType: 'USER' | 'SERVICE_ACCOUNT';
|
|
35
|
+
principalId?: string;
|
|
36
|
+
expiresAt?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface IApiTokenServiceAccount {
|
|
40
|
+
name: string;
|
|
41
|
+
memberOf?: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const ApiTokenService = {
|
|
45
|
+
listTokens(): PromiseLike<IApiToken[]> {
|
|
46
|
+
return REST('/auth/apiTokens').get();
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
createToken(request: ICreateApiTokenRequest): PromiseLike<IApiToken & { token: string }> {
|
|
50
|
+
return REST('/auth/apiTokens').post(request);
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
revokeToken(id: string): PromiseLike<void> {
|
|
54
|
+
return REST('/auth/apiTokens').path(id).delete();
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
listApiTokenServiceAccounts(): PromiseLike<IApiTokenServiceAccount[]> {
|
|
58
|
+
return REST('/auth/apiTokens/serviceAccounts').get();
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/** Admin-only: returns all USER tokens across all principals. */
|
|
62
|
+
listAllUserTokens(): PromiseLike<IApiToken[]> {
|
|
63
|
+
return REST('/auth/apiTokens/admin/users').get();
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Copyright 2026 DoorDash, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
.ApiTokensPage {
|
|
16
|
+
padding-top: 32px;
|
|
17
|
+
max-width: 1100px;
|
|
18
|
+
|
|
19
|
+
.ApiTokensPage-section + .ApiTokensPage-section {
|
|
20
|
+
margin-top: 40px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.ApiTokensPage-section-header {
|
|
24
|
+
margin-bottom: 16px;
|
|
25
|
+
|
|
26
|
+
h3 {
|
|
27
|
+
margin: 0;
|
|
28
|
+
flex: 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ApiTokensPage-admin-badge {
|
|
33
|
+
font-size: 12px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ApiTokensPage-actions-column {
|
|
37
|
+
width: 220px;
|
|
38
|
+
min-width: 220px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.ApiTokensPage-empty {
|
|
42
|
+
padding: 40px 0;
|
|
43
|
+
color: #888;
|
|
44
|
+
|
|
45
|
+
.ApiTokensPage-empty-icon {
|
|
46
|
+
font-size: 36px;
|
|
47
|
+
margin-bottom: 12px;
|
|
48
|
+
display: block;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ApiTokensPage-empty-title {
|
|
52
|
+
font-size: 16px;
|
|
53
|
+
margin-bottom: 4px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.ApiTokensPage-empty-body {
|
|
57
|
+
font-size: 13px;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.ApiTokensPage-status {
|
|
62
|
+
padding-top: 80px;
|
|
63
|
+
|
|
64
|
+
.ApiTokensPage-status-icon {
|
|
65
|
+
font-size: 36px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ApiTokensPage-status-title {
|
|
69
|
+
margin-top: 12px;
|
|
70
|
+
font-size: 16px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.ApiTokensPage-status-subtitle {
|
|
74
|
+
margin-top: 12px;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ApiTokensPage-inline-loader {
|
|
79
|
+
padding: 24px 0;
|
|
80
|
+
}
|
|
81
|
+
}
|