mirror of
https://github.com/bitwarden/browser
synced 2026-01-06 18:43:25 +00:00
[PM-23825] setup crowdstrike card (#15728)
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { Observable, switchMap } from "rxjs";
|
||||
import { Observable, Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import {
|
||||
getOrganizationById,
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { IntegrationType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { HeaderModule } from "../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
@@ -30,10 +32,12 @@ import { Integration } from "../shared/components/integrations/models";
|
||||
FilterIntegrationsPipe,
|
||||
],
|
||||
})
|
||||
export class AdminConsoleIntegrationsComponent implements OnInit {
|
||||
export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
|
||||
integrationsList: Integration[] = [];
|
||||
tabIndex: number;
|
||||
organization$: Observable<Organization>;
|
||||
isEventBasedIntegrationsEnabled: boolean = false;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.organization$ = this.route.params.pipe(
|
||||
@@ -53,7 +57,15 @@ export class AdminConsoleIntegrationsComponent implements OnInit {
|
||||
private route: ActivatedRoute,
|
||||
private organizationService: OrganizationService,
|
||||
private accountService: AccountService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.configService
|
||||
.getFeatureFlag$(FeatureFlag.EventBasedOrganizationIntegrations)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((isEnabled) => {
|
||||
this.isEventBasedIntegrationsEnabled = isEnabled;
|
||||
});
|
||||
|
||||
this.integrationsList = [
|
||||
{
|
||||
name: "AD FS",
|
||||
@@ -229,6 +241,22 @@ export class AdminConsoleIntegrationsComponent implements OnInit {
|
||||
type: IntegrationType.DEVICE,
|
||||
},
|
||||
];
|
||||
|
||||
if (this.isEventBasedIntegrationsEnabled) {
|
||||
this.integrationsList.push({
|
||||
name: "Crowdstrike",
|
||||
linkURL: "",
|
||||
image: "../../../../../../../images/integrations/logo-crowdstrike-black.svg",
|
||||
type: IntegrationType.EVENT,
|
||||
description: "crowdstrikeEventIntegrationDesc",
|
||||
isConnected: false,
|
||||
canSetupConnection: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get IntegrationType(): typeof IntegrationType {
|
||||
|
||||
@@ -17,16 +17,40 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-p-5">
|
||||
<h3 class="tw-text-main tw-text-lg tw-font-semibold">{{ name }}</h3>
|
||||
<a
|
||||
class="tw-block tw-mb-0 tw-font-bold hover:tw-no-underline focus:tw-outline-none after:tw-content-[''] after:tw-block after:tw-absolute after:tw-size-full after:tw-left-0 after:tw-top-0"
|
||||
[href]="linkURL"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
</a>
|
||||
<span *ngIf="showNewBadge()" bitBadge class="tw-mt-3" variant="secondary">
|
||||
{{ "new" | i18n }}
|
||||
</span>
|
||||
<h3 class="tw-text-main tw-text-lg tw-font-semibold">
|
||||
{{ name }}
|
||||
@if (showConnectedBadge()) {
|
||||
<span class="tw-ml-3">
|
||||
@if (isConnected) {
|
||||
<span bitBadge variant="success">{{ "on" | i18n }}</span>
|
||||
}
|
||||
@if (!isConnected) {
|
||||
<span bitBadge>{{ "off" | i18n }}</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</h3>
|
||||
<p class="tw-mb-0">{{ description }}</p>
|
||||
|
||||
@if (canSetupConnection) {
|
||||
<button type="button" class="tw-mt-3" bitButton (click)="setupConnection(name)">
|
||||
<span>{{ "connectIntegrationButtonDesc" | i18n: name }}</span>
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (linkURL) {
|
||||
<a
|
||||
class="tw-block tw-mb-0 tw-font-bold hover:tw-no-underline focus:tw-outline-none after:tw-content-[''] after:tw-block after:tw-absolute after:tw-size-full after:tw-left-0 after:tw-top-0"
|
||||
[href]="linkURL"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
</a>
|
||||
}
|
||||
@if (showNewBadge()) {
|
||||
<span bitBadge class="tw-mt-3" variant="secondary">
|
||||
{{ "new" | i18n }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { IntegrationCardComponent } from "./integration-card.component";
|
||||
describe("IntegrationCardComponent", () => {
|
||||
let component: IntegrationCardComponent;
|
||||
let fixture: ComponentFixture<IntegrationCardComponent>;
|
||||
const mockI18nService = mock<I18nService>();
|
||||
|
||||
const systemTheme$ = new BehaviorSubject<ThemeType>(ThemeType.Light);
|
||||
const usersPreferenceTheme$ = new BehaviorSubject<ThemeType>(ThemeType.Light);
|
||||
@@ -41,7 +42,7 @@ describe("IntegrationCardComponent", () => {
|
||||
},
|
||||
{
|
||||
provide: I18nService,
|
||||
useValue: mock<I18nService>(),
|
||||
useValue: mockI18nService,
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
@@ -55,6 +56,7 @@ describe("IntegrationCardComponent", () => {
|
||||
component.image = "test-image.png";
|
||||
component.linkURL = "https://example.com/";
|
||||
|
||||
mockI18nService.t.mockImplementation((key) => key);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -67,7 +69,7 @@ describe("IntegrationCardComponent", () => {
|
||||
it("renders card body", () => {
|
||||
const name = fixture.nativeElement.querySelector("h3");
|
||||
|
||||
expect(name.textContent).toBe("Integration Name");
|
||||
expect(name.textContent).toContain("Integration Name");
|
||||
});
|
||||
|
||||
it("assigns external rel attribute", () => {
|
||||
@@ -182,4 +184,28 @@ describe("IntegrationCardComponent", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("connected badge", () => {
|
||||
it("shows connected badge when isConnected is true", () => {
|
||||
component.isConnected = true;
|
||||
|
||||
expect(component.showConnectedBadge()).toBe(true);
|
||||
});
|
||||
|
||||
it("does not show connected badge when isConnected is false", () => {
|
||||
component.isConnected = false;
|
||||
fixture.detectChanges();
|
||||
const name = fixture.nativeElement.querySelector("h3 > span > span > span");
|
||||
|
||||
expect(name.textContent).toContain("off");
|
||||
// when isConnected is true/false, the badge should be shown as on/off
|
||||
// when isConnected is undefined, the badge should not be shown
|
||||
expect(component.showConnectedBadge()).toBe(true);
|
||||
});
|
||||
|
||||
it("does not show connected badge when isConnected is undefined", () => {
|
||||
component.isConnected = undefined;
|
||||
expect(component.showConnectedBadge()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,9 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
|
||||
* @example "2024-12-31"
|
||||
*/
|
||||
@Input() newBadgeExpiration?: string;
|
||||
@Input() description?: string;
|
||||
@Input() isConnected?: boolean;
|
||||
@Input() canSetupConnection?: boolean;
|
||||
|
||||
constructor(
|
||||
private themeStateService: ThemeStateService,
|
||||
@@ -93,4 +96,14 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
return expirationDate > new Date();
|
||||
}
|
||||
|
||||
showConnectedBadge(): boolean {
|
||||
return this.isConnected !== undefined;
|
||||
}
|
||||
|
||||
setupConnection(app: string) {
|
||||
// This method can be used to handle the connection logic for the integration
|
||||
// For example, it could open a modal or redirect to a setup page
|
||||
this.isConnected = !this.isConnected; // Toggle connection state for demonstration
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
[imageDarkMode]="integration.imageDarkMode"
|
||||
[externalURL]="integration.type === IntegrationType.SDK"
|
||||
[newBadgeExpiration]="integration.newBadgeExpiration"
|
||||
[description]="integration.description | i18n"
|
||||
[isConnected]="integration.isConnected"
|
||||
[canSetupConnection]="integration.canSetupConnection"
|
||||
></app-integration-card>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -17,4 +17,7 @@ export type Integration = {
|
||||
* @example "2024-12-31"
|
||||
*/
|
||||
newBadgeExpiration?: string;
|
||||
description?: string;
|
||||
isConnected?: boolean;
|
||||
canSetupConnection?: boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user