@spinnaker/core 2026.1.1 → 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
|
@@ -0,0 +1,79 @@
|
|
|
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 React, { useState } from 'react';
|
|
16
|
+
import { Modal } from 'react-bootstrap';
|
|
17
|
+
|
|
18
|
+
import { ApiTokenService } from './ApiTokenService';
|
|
19
|
+
|
|
20
|
+
export interface IRevokeApiTokenButtonProps {
|
|
21
|
+
tokenId: string;
|
|
22
|
+
tokenName: string;
|
|
23
|
+
onRevoked: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function RevokeApiTokenButton({ tokenId, tokenName, onRevoked }: IRevokeApiTokenButtonProps) {
|
|
27
|
+
const [showModal, setShowModal] = useState(false);
|
|
28
|
+
const [revoking, setRevoking] = useState(false);
|
|
29
|
+
const [error, setError] = useState<string | null>(null);
|
|
30
|
+
|
|
31
|
+
const handleRevoke = () => {
|
|
32
|
+
setRevoking(true);
|
|
33
|
+
setError(null);
|
|
34
|
+
ApiTokenService.revokeToken(tokenId)
|
|
35
|
+
.then(() => {
|
|
36
|
+
setShowModal(false);
|
|
37
|
+
onRevoked();
|
|
38
|
+
})
|
|
39
|
+
.catch(() => {
|
|
40
|
+
setRevoking(false);
|
|
41
|
+
setError('Failed to revoke token. Please try again.');
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<button className="btn btn-default btn-xs" onClick={() => setShowModal(true)}>
|
|
48
|
+
Revoke
|
|
49
|
+
</button>
|
|
50
|
+
|
|
51
|
+
<Modal show={showModal} onHide={() => !revoking && setShowModal(false)} bsSize="small">
|
|
52
|
+
<Modal.Header closeButton={!revoking}>
|
|
53
|
+
<Modal.Title>Revoke token</Modal.Title>
|
|
54
|
+
</Modal.Header>
|
|
55
|
+
<Modal.Body>
|
|
56
|
+
{error && (
|
|
57
|
+
<div className="alert alert-danger" role="alert" style={{ marginBottom: 12 }}>
|
|
58
|
+
{error}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
<p>
|
|
62
|
+
Are you sure you want to revoke <strong>{tokenName}</strong>?
|
|
63
|
+
</p>
|
|
64
|
+
<p className="text-muted" style={{ fontSize: 13, marginBottom: 0 }}>
|
|
65
|
+
This cannot be undone. Any automation using this token will stop working immediately.
|
|
66
|
+
</p>
|
|
67
|
+
</Modal.Body>
|
|
68
|
+
<Modal.Footer>
|
|
69
|
+
<button className="btn btn-default" onClick={() => setShowModal(false)} disabled={revoking}>
|
|
70
|
+
Cancel
|
|
71
|
+
</button>
|
|
72
|
+
<button className="btn btn-danger" onClick={handleRevoke} disabled={revoking}>
|
|
73
|
+
{revoking ? 'Revoking…' : 'Revoke token'}
|
|
74
|
+
</button>
|
|
75
|
+
</Modal.Footer>
|
|
76
|
+
</Modal>
|
|
77
|
+
</>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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 { module } from 'angular';
|
|
16
|
+
|
|
17
|
+
import { APITOKEN_STATES } from './apitoken.states';
|
|
18
|
+
|
|
19
|
+
export const APITOKEN_MODULE = 'spinnaker.core.apitoken';
|
|
20
|
+
module(APITOKEN_MODULE, [APITOKEN_STATES]);
|
|
@@ -0,0 +1,43 @@
|
|
|
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 { module } from 'angular';
|
|
16
|
+
|
|
17
|
+
import { ApiTokensPageContainer } from './ApiTokensPageContainer';
|
|
18
|
+
import type { INestedState, StateConfigProvider } from '../navigation/state.provider';
|
|
19
|
+
import { STATE_CONFIG_PROVIDER } from '../navigation/state.provider';
|
|
20
|
+
|
|
21
|
+
export const APITOKEN_STATES = 'spinnaker.core.apitoken.states';
|
|
22
|
+
module(APITOKEN_STATES, [STATE_CONFIG_PROVIDER]).config([
|
|
23
|
+
'stateConfigProvider',
|
|
24
|
+
(stateConfigProvider: StateConfigProvider) => {
|
|
25
|
+
const apiTokens: INestedState = {
|
|
26
|
+
name: 'apiTokens',
|
|
27
|
+
url: '/api-tokens',
|
|
28
|
+
views: {
|
|
29
|
+
'main@': {
|
|
30
|
+
component: ApiTokensPageContainer,
|
|
31
|
+
$type: 'react',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
data: {
|
|
35
|
+
pageTitleMain: {
|
|
36
|
+
label: 'API Tokens',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
stateConfigProvider.addToRootState(apiTokens);
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
export * from './ApiTokenService';
|
|
16
|
+
export * from './ApiTokensPage';
|
|
17
|
+
export * from './ApiTokensPageContainer';
|
|
18
|
+
export * from './CreateApiTokenModal';
|
|
19
|
+
export * from './RevokeApiTokenButton';
|
|
20
|
+
export * from './apitoken.module';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyApplicationInitializers,
|
|
3
|
+
registerApplicationInitializer,
|
|
4
|
+
resetApplicationInitializersForTests,
|
|
5
|
+
} from './application.initializers';
|
|
6
|
+
|
|
7
|
+
describe('application.initializers', () => {
|
|
8
|
+
beforeEach(() => resetApplicationInitializersForTests());
|
|
9
|
+
|
|
10
|
+
it('applies registered initializers once for the current context', () => {
|
|
11
|
+
const applicationState = {} as any;
|
|
12
|
+
const uiRouter = {} as any;
|
|
13
|
+
const initializer = jasmine.createSpy('initializer');
|
|
14
|
+
|
|
15
|
+
registerApplicationInitializer(initializer);
|
|
16
|
+
applyApplicationInitializers(applicationState, uiRouter);
|
|
17
|
+
applyApplicationInitializers(applicationState, uiRouter);
|
|
18
|
+
|
|
19
|
+
expect(initializer).toHaveBeenCalledTimes(1);
|
|
20
|
+
expect(initializer).toHaveBeenCalledWith(applicationState, uiRouter);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('runs late registrations immediately after the context has been applied', () => {
|
|
24
|
+
const applicationState = {} as any;
|
|
25
|
+
const uiRouter = {} as any;
|
|
26
|
+
const initializer = jasmine.createSpy('initializer');
|
|
27
|
+
|
|
28
|
+
applyApplicationInitializers(applicationState, uiRouter);
|
|
29
|
+
registerApplicationInitializer(initializer);
|
|
30
|
+
|
|
31
|
+
expect(initializer).toHaveBeenCalledTimes(1);
|
|
32
|
+
expect(initializer).toHaveBeenCalledWith(applicationState, uiRouter);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { UIRouter } from '@uirouter/core';
|
|
2
|
+
import { module } from 'angular';
|
|
3
|
+
|
|
4
|
+
import type { ApplicationStateProvider } from './application.state.provider';
|
|
5
|
+
|
|
6
|
+
export type ApplicationInitializer = (applicationState: ApplicationStateProvider, uiRouter: UIRouter) => void;
|
|
7
|
+
|
|
8
|
+
const initializers: ApplicationInitializer[] = [];
|
|
9
|
+
let appliedContext: { applicationState: ApplicationStateProvider; uiRouter: UIRouter } | null = null;
|
|
10
|
+
|
|
11
|
+
export function registerApplicationInitializer(initializer: ApplicationInitializer): void {
|
|
12
|
+
initializers.push(initializer);
|
|
13
|
+
if (appliedContext) {
|
|
14
|
+
initializer(appliedContext.applicationState, appliedContext.uiRouter);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function applyApplicationInitializers(applicationState: ApplicationStateProvider, uiRouter: UIRouter): void {
|
|
19
|
+
if (appliedContext?.applicationState === applicationState && appliedContext?.uiRouter === uiRouter) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
appliedContext = { applicationState, uiRouter };
|
|
24
|
+
initializers.forEach((initializer) => initializer(applicationState, uiRouter));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resetApplicationInitializersForTests(): void {
|
|
28
|
+
initializers.length = 0;
|
|
29
|
+
appliedContext = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const APPLICATION_INITIALIZERS_MODULE = 'spinnaker.core.application.initializers';
|
|
33
|
+
|
|
34
|
+
module(APPLICATION_INITIALIZERS_MODULE, []).run([
|
|
35
|
+
'applicationState',
|
|
36
|
+
'$uiRouter',
|
|
37
|
+
(applicationState: ApplicationStateProvider, uiRouter: UIRouter) => {
|
|
38
|
+
applyApplicationInitializers(applicationState, uiRouter);
|
|
39
|
+
},
|
|
40
|
+
]);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { module } from 'angular';
|
|
2
2
|
|
|
3
|
+
import { APPLICATION_INITIALIZERS_MODULE } from './application.initializers';
|
|
3
4
|
import { APPLICATION_STATE_PROVIDER } from './application.state.provider';
|
|
4
5
|
import './applicationSearchResultType';
|
|
5
6
|
import { APPLICATIONS_STATE_PROVIDER } from './applications.state.provider';
|
|
@@ -14,6 +15,7 @@ import './nav/defaultCategories';
|
|
|
14
15
|
export const APPLICATION_MODULE = 'spinnaker.core.application';
|
|
15
16
|
module(APPLICATION_MODULE, [
|
|
16
17
|
APPLICATION_STATE_PROVIDER,
|
|
18
|
+
APPLICATION_INITIALIZERS_MODULE,
|
|
17
19
|
APPLICATIONS_STATE_PROVIDER,
|
|
18
20
|
CORE_APPLICATION_CONFIG_APPLICATIONCONFIG_CONTROLLER,
|
|
19
21
|
CORE_APPLICATION_MODAL_CREATEAPPLICATION_MODAL_CONTROLLER,
|
package/src/application/index.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export * from './ApplicationContext';
|
|
2
2
|
export * from './ApplicationIcon';
|
|
3
|
+
export type { ApplicationInitializer } from './application.initializers';
|
|
4
|
+
export {
|
|
5
|
+
APPLICATION_INITIALIZERS_MODULE,
|
|
6
|
+
applyApplicationInitializers,
|
|
7
|
+
registerApplicationInitializer,
|
|
8
|
+
} from './application.initializers';
|
|
3
9
|
export * from './application.model';
|
|
4
10
|
export * from './application.state.provider';
|
|
5
11
|
export * from './applicationModel.builder';
|
|
@@ -46,6 +46,22 @@ describe('authenticationProvider: application startup', function () {
|
|
|
46
46
|
expect(AuthenticationService.getAuthenticatedUser().authenticated).toBe(true);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
it('threads canMintApiTokens from the auth response onto the authenticated user', function () {
|
|
50
|
+
$httpBackend.whenGET(SETTINGS.authEndpoint).respond(200, { username: 'joe!', canMintApiTokens: true });
|
|
51
|
+
$timeout.flush();
|
|
52
|
+
$httpBackend.flush();
|
|
53
|
+
|
|
54
|
+
expect(AuthenticationService.getAuthenticatedUser().canMintApiTokens).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('defaults canMintApiTokens to false when the auth response omits it', function () {
|
|
58
|
+
$httpBackend.whenGET(SETTINGS.authEndpoint).respond(200, { username: 'joe!' });
|
|
59
|
+
$timeout.flush();
|
|
60
|
+
$httpBackend.flush();
|
|
61
|
+
|
|
62
|
+
expect(AuthenticationService.getAuthenticatedUser().canMintApiTokens).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
49
65
|
it('requests authentication from gate, then opens modal and redirects on 401', function () {
|
|
50
66
|
$httpBackend.whenGET(SETTINGS.authEndpoint).respond(401, null, { 'X-AUTH-REDIRECT-URL': '/authUp' });
|
|
51
67
|
$rootScope.$digest();
|
|
@@ -11,6 +11,7 @@ import { ModalInjector } from '../reactShims/modal.injector';
|
|
|
11
11
|
interface IAuthResponse {
|
|
12
12
|
username: string;
|
|
13
13
|
roles?: string[];
|
|
14
|
+
canMintApiTokens?: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export class AuthenticationInitializer {
|
|
@@ -26,6 +27,7 @@ export class AuthenticationInitializer {
|
|
|
26
27
|
name: response.data.username,
|
|
27
28
|
authenticated: false,
|
|
28
29
|
roles: response.data.roles,
|
|
30
|
+
canMintApiTokens: response.data.canMintApiTokens,
|
|
29
31
|
});
|
|
30
32
|
ModalInjector.modalStackService.dismissAll();
|
|
31
33
|
this.visibilityWatch.unsubscribe();
|
|
@@ -65,6 +67,7 @@ export class AuthenticationInitializer {
|
|
|
65
67
|
name: response.data.username,
|
|
66
68
|
authenticated: false,
|
|
67
69
|
roles: response.data.roles,
|
|
70
|
+
canMintApiTokens: response.data.canMintApiTokens,
|
|
68
71
|
});
|
|
69
72
|
$rootScope.authenticating = false;
|
|
70
73
|
} else {
|
|
@@ -85,6 +88,7 @@ export class AuthenticationInitializer {
|
|
|
85
88
|
name: response.data.username,
|
|
86
89
|
authenticated: false,
|
|
87
90
|
roles: response.data.roles,
|
|
91
|
+
canMintApiTokens: response.data.canMintApiTokens,
|
|
88
92
|
});
|
|
89
93
|
$rootScope.authenticating = false;
|
|
90
94
|
this.userLoggedOut = false;
|
|
@@ -16,6 +16,24 @@ describe('AuthenticationService', function () {
|
|
|
16
16
|
expect(user.authenticated).toBe(true);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
+
it('defaults canMintApiTokens to false', function () {
|
|
20
|
+
let user: IUser = AuthenticationService.getAuthenticatedUser();
|
|
21
|
+
expect(user.canMintApiTokens).toBe(false);
|
|
22
|
+
|
|
23
|
+
AuthenticationService.setAuthenticatedUser({ name: 'kato@example.com', authenticated: false });
|
|
24
|
+
user = AuthenticationService.getAuthenticatedUser();
|
|
25
|
+
expect(user.canMintApiTokens).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('stores canMintApiTokens when provided', function () {
|
|
29
|
+
AuthenticationService.setAuthenticatedUser({
|
|
30
|
+
name: 'kato@example.com',
|
|
31
|
+
authenticated: false,
|
|
32
|
+
canMintApiTokens: true,
|
|
33
|
+
});
|
|
34
|
+
expect(AuthenticationService.getAuthenticatedUser().canMintApiTokens).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
19
37
|
it('disregards falsy values', function () {
|
|
20
38
|
const user: IUser = AuthenticationService.getAuthenticatedUser();
|
|
21
39
|
|
|
@@ -3,12 +3,14 @@ export interface IUser {
|
|
|
3
3
|
authenticated: boolean;
|
|
4
4
|
roles?: string[];
|
|
5
5
|
lastAuthenticated?: number;
|
|
6
|
+
canMintApiTokens?: boolean;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
const defaultUser: IUser = {
|
|
9
10
|
name: '[anonymous]',
|
|
10
11
|
roles: [],
|
|
11
12
|
authenticated: false,
|
|
13
|
+
canMintApiTokens: false,
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
export class AuthenticationService {
|
|
@@ -26,6 +28,7 @@ export class AuthenticationService {
|
|
|
26
28
|
this.user.authenticated = true;
|
|
27
29
|
this.user.lastAuthenticated = new Date().getTime();
|
|
28
30
|
this.user.roles = authenticatedUser.roles;
|
|
31
|
+
this.user.canMintApiTokens = authenticatedUser.canMintApiTokens ?? false;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
this.authEvents.forEach((event: Function) => event());
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UISref } from '@uirouter/react';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
import { Dropdown } from 'react-bootstrap';
|
|
3
4
|
|
|
@@ -10,6 +11,7 @@ import './userMenu.less';
|
|
|
10
11
|
export const UserMenu = () => {
|
|
11
12
|
const authenticatedUser = AuthenticationService.getAuthenticatedUser();
|
|
12
13
|
const showLogOutDropdown = authenticatedUser.authenticated;
|
|
14
|
+
const canMintApiTokens = authenticatedUser.canMintApiTokens ?? false;
|
|
13
15
|
|
|
14
16
|
if (!SETTINGS.authEnabled) {
|
|
15
17
|
return null;
|
|
@@ -23,9 +25,21 @@ export const UserMenu = () => {
|
|
|
23
25
|
<span className="hidden-xs hidden-sm hidden-md">{authenticatedUser.name}</span>
|
|
24
26
|
</Dropdown.Toggle>
|
|
25
27
|
{showLogOutDropdown && (
|
|
26
|
-
<Dropdown.Menu>
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
<Dropdown.Menu pullRight>
|
|
29
|
+
{canMintApiTokens && (
|
|
30
|
+
<>
|
|
31
|
+
<li role="presentation">
|
|
32
|
+
<UISref to="home.apiTokens">
|
|
33
|
+
<a role="menuitem">API Tokens</a>
|
|
34
|
+
</UISref>
|
|
35
|
+
</li>
|
|
36
|
+
<li role="presentation" className="divider" />
|
|
37
|
+
</>
|
|
38
|
+
)}
|
|
39
|
+
<li role="presentation">
|
|
40
|
+
<a role="menuitem" style={{ cursor: 'pointer' }} onClick={() => AuthenticationInitializer.logOut()}>
|
|
41
|
+
Log Out
|
|
42
|
+
</a>
|
|
29
43
|
</li>
|
|
30
44
|
</Dropdown.Menu>
|
|
31
45
|
)}
|
package/src/config/settings.ts
CHANGED
package/src/core.module.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { RECENT_HISTORY_SERVICE } from './history/recentHistory.service';
|
|
|
26
26
|
import './analytics/GoogleAnalyticsInitializer';
|
|
27
27
|
import { UI_ROUTER_REACT_ERROR_BOUNDARY } from './presentation/SpinErrorBoundary';
|
|
28
28
|
import { ANALYTICS_MODULE } from './analytics/angulartics.module';
|
|
29
|
+
import { APITOKEN_MODULE } from './apitoken/apitoken.module';
|
|
29
30
|
import { APPLICATION_BOOTSTRAP_MODULE } from './bootstrap';
|
|
30
31
|
import { APPLICATION_MODULE } from './application/application.module';
|
|
31
32
|
import { ARTIFACT_MODULE } from './artifact/artifact.module';
|
|
@@ -107,6 +108,7 @@ module(CORE_MODULE, [
|
|
|
107
108
|
angularSpinner.name,
|
|
108
109
|
|
|
109
110
|
ANALYTICS_MODULE,
|
|
111
|
+
APITOKEN_MODULE,
|
|
110
112
|
APPLICATION_MODULE,
|
|
111
113
|
APPLICATION_BOOTSTRAP_MODULE,
|
|
112
114
|
ARTIFACT_MODULE,
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { SETTINGS } from '../config';
|
|
|
5
5
|
import type { INotificationTypeConfig } from '../domain';
|
|
6
6
|
import { Registry } from '../registry';
|
|
7
7
|
|
|
8
|
-
import { bearyChatNotification } from './selector/types/bearychat/beary.notification';
|
|
9
8
|
import { cdEventsNotification } from './selector/types/cdevents/cdevents.notification';
|
|
10
9
|
import { emailNotification } from './selector/types/email/email.notification';
|
|
11
10
|
import { githubstatusNotification } from './selector/types/githubstatus/githubstatus.notification';
|
|
@@ -16,7 +15,6 @@ import { slackNotification } from './selector/types/slack/slack.notification';
|
|
|
16
15
|
import { smsNotification } from './selector/types/sms/sms.notification';
|
|
17
16
|
|
|
18
17
|
[
|
|
19
|
-
bearyChatNotification,
|
|
20
18
|
emailNotification,
|
|
21
19
|
githubstatusNotification,
|
|
22
20
|
googlechatNotification,
|
|
@@ -14,7 +14,7 @@ export class MicrosoftTeamsNotificationType extends React.Component<INotificatio
|
|
|
14
14
|
<TextInput
|
|
15
15
|
inputClassName={'form-control input-sm'}
|
|
16
16
|
{...props}
|
|
17
|
-
placeholder="URL starts with https://
|
|
17
|
+
placeholder="URL starts with https://[region].webhook.office.com/webhookb2/"
|
|
18
18
|
/>
|
|
19
19
|
)}
|
|
20
20
|
required={true}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
import type { INotificationTypeCustomConfig } from '../../../../domain';
|
|
4
|
-
import { FormikFormField, TextInput, Validators } from '../../../../presentation';
|
|
5
|
-
|
|
6
|
-
export class BearychatNotificationType extends React.Component<INotificationTypeCustomConfig> {
|
|
7
|
-
public render() {
|
|
8
|
-
const { fieldName } = this.props;
|
|
9
|
-
return (
|
|
10
|
-
<FormikFormField
|
|
11
|
-
label="Email Address"
|
|
12
|
-
name={fieldName ? `${fieldName}.address` : 'address'}
|
|
13
|
-
validate={Validators.emailValue('Please enter a valid email address')}
|
|
14
|
-
input={(props) => <TextInput inputClassName={'form-control input-sm'} {...props} />}
|
|
15
|
-
required={true}
|
|
16
|
-
/>
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { BearychatNotificationType } from './BearychatNotificationType';
|
|
2
|
-
import type { INotificationTypeConfig } from '../../../../domain';
|
|
3
|
-
|
|
4
|
-
export const bearyChatNotification: INotificationTypeConfig = {
|
|
5
|
-
component: BearychatNotificationType,
|
|
6
|
-
key: 'bearychat',
|
|
7
|
-
label: 'Bearychat',
|
|
8
|
-
};
|