1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

[PM-8115] Desktop, Extension UI Refresh: Self-hosted Setup Dialog (#11597)

* Reimplement RegistrationSelfHostedEnvConfigDialogComponent

* Update EnvironmentSelectorComponent text based on feature flag.

* Initialize RegistrationSelfHostedEnvConfigDialog with existing values if self hosted

* Cleanup debug

* Add comment

* Remove changes to home and login components

* Remove changes to desktop login component

* Remove changes to browser home component

* Simplify accessing string.

* Add environment selector service.

* Cleanup unused imports in environment-selector

* Launch new env selector dialog from desktop

* Fix lint errors

* Address PR feedback: move dialog component, remove EnvironmentSelectorService, remove unused translation string

* Remove changes to AnonLayout

* PM-8115 - Export Re-usable component from Libs/auth for clean import elsewhere in clients.

* Remove unused accessingString variable

* Add success toast

---------

Co-authored-by: Jared Snider <jsnider@bitwarden.com>
This commit is contained in:
Alec Rippberger
2024-10-28 16:12:57 -05:00
committed by GitHub
parent 95e8e0c9bf
commit 9da80a6cba
8 changed files with 67 additions and 21 deletions

View File

@@ -15,7 +15,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService, FormFieldModule, SelectModule, ToastService } from "@bitwarden/components";
import { RegistrationSelfHostedEnvConfigDialogComponent } from "./registration-self-hosted-env-config-dialog.component";
import { SelfHostedEnvConfigDialogComponent } from "../../self-hosted-env-config-dialog/self-hosted-env-config-dialog.component";
/**
* Component for selecting the environment to register with in the email verification registration flow.
@@ -125,9 +125,7 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy {
}
if (selectedRegion === Region.SelfHosted) {
return from(
RegistrationSelfHostedEnvConfigDialogComponent.open(this.dialogService),
).pipe(
return from(SelfHostedEnvConfigDialogComponent.open(this.dialogService)).pipe(
tap((result: boolean | undefined) =>
this.handleSelfHostedEnvConfigDialogResult(result, prevSelectedRegion),
),

View File

@@ -1,107 +0,0 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog>
<span bitDialogTitle> Self-hosted environment</span>
<ng-container bitDialogContent>
<bit-form-field>
<bit-label>{{ "baseUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_base_url"
bitInput
type="text"
formControlName="baseUrl"
appAutofocus
appInputVerbatim
/>
<bit-hint>{{ "selfHostedBaseUrlHint" | i18n }}</bit-hint>
</bit-form-field>
<button bitLink linkType="primary" type="button" (click)="showCustomEnv = !showCustomEnv">
<i
class="bwi bwi-fw bwi-sm"
[ngClass]="{ 'bwi-angle-right': !showCustomEnv, 'bwi-angle-down': showCustomEnv }"
aria-hidden="true"
></i>
{{ "customEnvironment" | i18n }}
</button>
<ng-container *ngIf="showCustomEnv">
<p bitTypography="body1" class="tw-text-muted tw-mt-3">
{{ "selfHostedCustomEnvHeader" | i18n }}
</p>
<bit-form-field>
<bit-label>{{ "webVaultUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_web_vault_url"
bitInput
type="text"
formControlName="webVaultUrl"
appInputVerbatim
/>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "apiUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_api_url"
bitInput
type="text"
formControlName="apiUrl"
appInputVerbatim
/>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "identityUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_identity_url"
bitInput
type="text"
formControlName="identityUrl"
appInputVerbatim
/>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "notificationsUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_notifications_url"
bitInput
type="text"
formControlName="notificationsUrl"
appInputVerbatim
/>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "iconsUrl" | i18n }}</bit-label>
<input
id="self_hosted_env_settings_form_input_icons_url"
bitInput
type="text"
formControlName="iconsUrl"
appInputVerbatim
/>
</bit-form-field>
</ng-container>
<span
*ngIf="showErrorSummary"
class="tw-block tw-text-danger tw-mt-2"
aria-live="assertive"
role="alert"
>
<i class="bwi bwi-error"></i> {{ "selfHostedEnvFormInvalid" | i18n }}
</span>
</ng-container>
<ng-container bitDialogFooter>
<button type="submit" bitButton bitFormButton buttonType="primary">
{{ "save" | i18n }}
</button>
<button type="button" bitButton bitFormButton buttonType="secondary" (click)="cancel()">
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@@ -1,164 +0,0 @@
import { DialogRef } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import {
AbstractControl,
FormBuilder,
FormControl,
FormGroup,
ReactiveFormsModule,
ValidationErrors,
ValidatorFn,
} from "@angular/forms";
import { Subject, firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
EnvironmentService,
Region,
} from "@bitwarden/common/platform/abstractions/environment.service";
import {
AsyncActionsModule,
ButtonModule,
DialogModule,
DialogService,
FormFieldModule,
LinkModule,
TypographyModule,
} from "@bitwarden/components";
/**
* Validator for self-hosted environment settings form.
* It enforces that at least one URL is provided.
*/
function selfHostedEnvSettingsFormValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const formGroup = control as FormGroup;
const baseUrl = formGroup.get("baseUrl")?.value;
const webVaultUrl = formGroup.get("webVaultUrl")?.value;
const apiUrl = formGroup.get("apiUrl")?.value;
const identityUrl = formGroup.get("identityUrl")?.value;
const iconsUrl = formGroup.get("iconsUrl")?.value;
const notificationsUrl = formGroup.get("notificationsUrl")?.value;
if (baseUrl || webVaultUrl || apiUrl || identityUrl || iconsUrl || notificationsUrl) {
return null; // valid
} else {
return { atLeastOneUrlIsRequired: true }; // invalid
}
};
}
/**
* Dialog for configuring self-hosted environment settings.
*/
@Component({
standalone: true,
selector: "auth-registration-self-hosted-env-config-dialog",
templateUrl: "registration-self-hosted-env-config-dialog.component.html",
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
],
})
export class RegistrationSelfHostedEnvConfigDialogComponent implements OnInit, OnDestroy {
/**
* Opens the dialog.
* @param dialogService - Dialog service.
* @returns Promise that resolves to true if the dialog was closed with a successful result, false otherwise.
*/
static async open(dialogService: DialogService): Promise<boolean> {
const dialogRef = dialogService.open<boolean>(RegistrationSelfHostedEnvConfigDialogComponent, {
disableClose: false,
});
const dialogResult = await firstValueFrom(dialogRef.closed);
return dialogResult;
}
formGroup = this.formBuilder.group(
{
baseUrl: [null],
webVaultUrl: [null],
apiUrl: [null],
identityUrl: [null],
iconsUrl: [null],
notificationsUrl: [null],
},
{ validators: selfHostedEnvSettingsFormValidator() },
);
get baseUrl(): FormControl {
return this.formGroup.get("baseUrl") as FormControl;
}
get webVaultUrl(): FormControl {
return this.formGroup.get("webVaultUrl") as FormControl;
}
get apiUrl(): FormControl {
return this.formGroup.get("apiUrl") as FormControl;
}
get identityUrl(): FormControl {
return this.formGroup.get("identityUrl") as FormControl;
}
get iconsUrl(): FormControl {
return this.formGroup.get("iconsUrl") as FormControl;
}
get notificationsUrl(): FormControl {
return this.formGroup.get("notificationsUrl") as FormControl;
}
showCustomEnv = false;
showErrorSummary = false;
private destroy$ = new Subject<void>();
constructor(
private dialogRef: DialogRef<boolean>,
private formBuilder: FormBuilder,
private environmentService: EnvironmentService,
) {}
ngOnInit() {}
submit = async () => {
this.showErrorSummary = false;
if (this.formGroup.invalid) {
this.showErrorSummary = true;
return;
}
await this.environmentService.setEnvironment(Region.SelfHosted, {
base: this.baseUrl.value,
api: this.apiUrl.value,
identity: this.identityUrl.value,
webVault: this.webVaultUrl.value,
icons: this.iconsUrl.value,
notifications: this.notificationsUrl.value,
});
this.dialogRef.close(true);
};
async cancel() {
this.dialogRef.close(false);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}