mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[PM-1019] Environment selection clients (#5480)
* [PM-169][PM-142][PM-191] Add Environments to Web and Desktop (#5294) * [PM-1351] Add property to server-config.response. Change config to be able to fetch without being authed. * [PM-1351] fetch every hour. * [PM-1351] fetch on vault sync. * [PM-1351] browser desktop fetch configs on sync complete. * [PM-1351] Add methods to retrieve feature flags * [PM-1351] Add enum to use as key to get values feature flag values * [PM-1351] Remove debug code * [PM-1351] Get flags when unauthed. Add enums as params. Hourly always fetch. * [PM-1351] add check for authed user using auth service * [PM-169] Web: add drop down to select environment * [PM-169] Fix pop up menu margins. Add DisplayEuEnvironmentFlag. * [PM-169] Change menu name. * [PM-169] Add environment selector ts and html. Add declaration and import on login.module * [PM-169] Add environment selector to desktop. * [PM-169] Ignore lint error. * [PM-169] add takeUntil to subscribes * [PM-191] PR Fixes, code format * [PM-168] Add Environments to extension login/registration (#5434)
This commit is contained in:
@@ -2221,7 +2221,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"region": {
|
||||||
|
"message": "Region"
|
||||||
|
},
|
||||||
"opensInANewWindow": {
|
"opensInANewWindow": {
|
||||||
"message": "Opens in a new window"
|
"message": "Opens in a new window"
|
||||||
|
},
|
||||||
|
"eu": {
|
||||||
|
"message": "EU",
|
||||||
|
"description": "European Union"
|
||||||
|
},
|
||||||
|
"us": {
|
||||||
|
"message": "US",
|
||||||
|
"description": "United States"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2221,6 +2221,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"region": {
|
||||||
|
"message": "Region"
|
||||||
|
},
|
||||||
"opensInANewWindow": {
|
"opensInANewWindow": {
|
||||||
"message": "Opens in a new window"
|
"message": "Opens in a new window"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core";
|
|||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { EnvironmentComponent as BaseEnvironmentComponent } from "@bitwarden/angular/components/environment.component";
|
import { EnvironmentComponent as BaseEnvironmentComponent } from "@bitwarden/angular/components/environment.component";
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
@@ -18,9 +19,10 @@ export class EnvironmentComponent extends BaseEnvironmentComponent implements On
|
|||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
public environmentService: BrowserEnvironmentService,
|
public environmentService: BrowserEnvironmentService,
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
private router: Router
|
private router: Router,
|
||||||
|
modalService: ModalService
|
||||||
) {
|
) {
|
||||||
super(platformUtilsService, environmentService, i18nService);
|
super(platformUtilsService, environmentService, i18nService, modalService);
|
||||||
this.showCustom = true;
|
this.showCustom = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,7 @@
|
|||||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||||
<input id="email" type="email" formControlName="email" appInputVerbatim="false" />
|
<input id="email" type="email" formControlName="email" appInputVerbatim="false" />
|
||||||
</div>
|
</div>
|
||||||
<div class="box-footer no-margin" *ngIf="selfHostedDomain">
|
<environment-selector class="environment-selector-padding"></environment-selector>
|
||||||
{{ "loggingInTo" | i18n : selfHostedDomain }}
|
|
||||||
</div>
|
|
||||||
<div class="remember-email-check">
|
<div class="remember-email-check">
|
||||||
<input
|
<input
|
||||||
id="rememberEmail"
|
id="rememberEmail"
|
||||||
@@ -35,6 +33,3 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" routerLink="/environment" class="settings-icon" (click)="setFormValues()">
|
|
||||||
<i class="bwi bwi-cog-f bwi-lg" aria-hidden="true"></i><span> {{ "settings" | i18n }}</span>
|
|
||||||
</button>
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||||
import { FormBuilder, Validators } from "@angular/forms";
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
import { Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
|
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
||||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
@@ -12,9 +14,12 @@ import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"
|
|||||||
selector: "app-home",
|
selector: "app-home",
|
||||||
templateUrl: "home.component.html",
|
templateUrl: "home.component.html",
|
||||||
})
|
})
|
||||||
export class HomeComponent implements OnInit {
|
export class HomeComponent implements OnInit, OnDestroy {
|
||||||
loginInitiated = false;
|
@ViewChild(EnvironmentSelectorComponent, { static: true })
|
||||||
|
environmentSelector!: EnvironmentSelectorComponent;
|
||||||
|
private destroyed$: Subject<void> = new Subject();
|
||||||
|
|
||||||
|
loginInitiated = false;
|
||||||
formGroup = this.formBuilder.group({
|
formGroup = this.formBuilder.group({
|
||||||
email: ["", [Validators.required, Validators.email]],
|
email: ["", [Validators.required, Validators.email]],
|
||||||
rememberEmail: [false],
|
rememberEmail: [false],
|
||||||
@@ -27,9 +32,9 @@ export class HomeComponent implements OnInit {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private environmentService: EnvironmentService,
|
private environmentService: EnvironmentService,
|
||||||
private route: ActivatedRoute,
|
|
||||||
private loginService: LoginService
|
private loginService: LoginService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
let savedEmail = this.loginService.getEmail();
|
let savedEmail = this.loginService.getEmail();
|
||||||
const rememberEmail = this.loginService.getRememberEmail();
|
const rememberEmail = this.loginService.getRememberEmail();
|
||||||
@@ -48,6 +53,18 @@ export class HomeComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.environmentSelector.onOpenSelfHostedSettings
|
||||||
|
.pipe(takeUntil(this.destroyed$))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.setFormValues();
|
||||||
|
this.router.navigate(["environment"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroyed$.next();
|
||||||
|
this.destroyed$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
|||||||
import { BrowserModule } from "@angular/platform-browser";
|
import { BrowserModule } from "@angular/platform-browser";
|
||||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
|
|
||||||
|
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
||||||
import { BitwardenToastModule } from "@bitwarden/angular/components/toastr.component";
|
import { BitwardenToastModule } from "@bitwarden/angular/components/toastr.component";
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
|
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
|
||||||
@@ -153,6 +154,7 @@ import "./locales";
|
|||||||
AboutComponent,
|
AboutComponent,
|
||||||
HelpAndFeedbackComponent,
|
HelpAndFeedbackComponent,
|
||||||
AutofillComponent,
|
AutofillComponent,
|
||||||
|
EnvironmentSelectorComponent,
|
||||||
],
|
],
|
||||||
providers: [CurrencyPipe, DatePipe],
|
providers: [CurrencyPipe, DatePipe],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
|
|||||||
BIN
apps/browser/src/popup/images/eu-flag.png
Normal file
BIN
apps/browser/src/popup/images/eu-flag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
BIN
apps/browser/src/popup/images/us-flag.png
Normal file
BIN
apps/browser/src/popup/images/us-flag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -45,3 +45,67 @@ html.browser_safari {
|
|||||||
border-color: #2e3440;
|
border-color: #2e3440;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.environment-selector-btn {
|
||||||
|
font-size: $font-size-small;
|
||||||
|
color: $text-muted;
|
||||||
|
line-height: 25px;
|
||||||
|
font-weight: 400;
|
||||||
|
padding-left: 5px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a label:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.environment-selector-dialog {
|
||||||
|
@include themify($themes) {
|
||||||
|
background-color: themed("boxBackgroundColor");
|
||||||
|
}
|
||||||
|
padding: 5px;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12),
|
||||||
|
0 1px 5px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.environment-selector-dialog-item {
|
||||||
|
@include themify($themes) {
|
||||||
|
color: themed("textColor") !important;
|
||||||
|
}
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0px 15px 0px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@include themify($themes) {
|
||||||
|
background-color: themed("listItemBackgroundHoverColor") !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-bottom: -2px;
|
||||||
|
width: 22px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-us {
|
||||||
|
content: url("../images/us-flag.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-eu {
|
||||||
|
content: url("../images/eu-flag.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.environment-selector-padding {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ body.body-full {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.remember-email-check {
|
.remember-email-check {
|
||||||
padding-top: 8px;
|
padding-top: 18px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-bottom: 18px;
|
padding-bottom: 18px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { EnvironmentComponent as BaseEnvironmentComponent } from "@bitwarden/angular/components/environment.component";
|
import { EnvironmentComponent as BaseEnvironmentComponent } from "@bitwarden/angular/components/environment.component";
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
@@ -13,8 +14,9 @@ export class EnvironmentComponent extends BaseEnvironmentComponent {
|
|||||||
constructor(
|
constructor(
|
||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
i18nService: I18nService
|
i18nService: I18nService,
|
||||||
|
modalService: ModalService
|
||||||
) {
|
) {
|
||||||
super(platformUtilsService, environmentService, i18nService);
|
super(platformUtilsService, environmentService, i18nService, modalService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,4 @@
|
|||||||
<div id="login-page">
|
<div id="login-page" class="page-top-padding">
|
||||||
<div class="login-header">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
appStopClick
|
|
||||||
(click)="settings()"
|
|
||||||
class="environment-urls-settings-icon"
|
|
||||||
attr.aria-label="{{ 'settings' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
|
||||||
{{ "settings" | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<form
|
<form
|
||||||
id="login-page"
|
id="login-page"
|
||||||
#form
|
#form
|
||||||
@@ -19,7 +7,7 @@
|
|||||||
[formGroup]="formGroup"
|
[formGroup]="formGroup"
|
||||||
attr.aria-hidden="{{ showingModal }}"
|
attr.aria-hidden="{{ showingModal }}"
|
||||||
>
|
>
|
||||||
<div id="content" class="content">
|
<div id="content" class="content" style="padding-top: 50px">
|
||||||
<img class="logo-image" alt="Bitwarden" />
|
<img class="logo-image" alt="Bitwarden" />
|
||||||
<p class="lead">{{ "loginOrCreateNewAccount" | i18n }}</p>
|
<p class="lead">{{ "loginOrCreateNewAccount" | i18n }}</p>
|
||||||
<!-- start email -->
|
<!-- start email -->
|
||||||
@@ -37,9 +25,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-footer" *ngIf="selfHostedDomain">
|
<environment-selector></environment-selector>
|
||||||
{{ "loggingInTo" | i18n : selfHostedDomain }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox remember-email">
|
<div class="checkbox remember-email">
|
||||||
<label for="rememberEmail">
|
<label for="rememberEmail">
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Component, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
|
import { Component, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
|
||||||
import { FormBuilder } from "@angular/forms";
|
import { FormBuilder } from "@angular/forms";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
import { Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
|
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
||||||
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
|
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
@@ -31,7 +33,10 @@ const BroadcasterSubscriptionId = "LoginComponent";
|
|||||||
export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
||||||
@ViewChild("environment", { read: ViewContainerRef, static: true })
|
@ViewChild("environment", { read: ViewContainerRef, static: true })
|
||||||
environmentModal: ViewContainerRef;
|
environmentModal: ViewContainerRef;
|
||||||
|
@ViewChild(EnvironmentSelectorComponent)
|
||||||
|
environmentSelector!: EnvironmentSelectorComponent;
|
||||||
|
|
||||||
|
protected componentDestroyed$: Subject<void> = new Subject();
|
||||||
webVaultHostname = "";
|
webVaultHostname = "";
|
||||||
|
|
||||||
showingModal = false;
|
showingModal = false;
|
||||||
@@ -116,10 +121,17 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.messagingService.send("getWindowIsFocused");
|
this.messagingService.send("getWindowIsFocused");
|
||||||
|
this.environmentSelector.onOpenSelfHostedSettings
|
||||||
|
.pipe(takeUntil(this.componentDestroyed$))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.settings();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
|
this.componentDestroyed$.next();
|
||||||
|
this.componentDestroyed$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
async settings() {
|
async settings() {
|
||||||
@@ -128,17 +140,16 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
|||||||
this.environmentModal
|
this.environmentModal
|
||||||
);
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
modal.onShown.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => {
|
||||||
modal.onShown.subscribe(() => {
|
|
||||||
this.showingModal = true;
|
this.showingModal = true;
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
|
||||||
modal.onClosed.subscribe(() => {
|
modal.onClosed.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => {
|
||||||
this.showingModal = false;
|
this.showingModal = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||||
childComponent.onSaved.subscribe(async () => {
|
childComponent.onSaved.pipe(takeUntil(this.componentDestroyed$)).subscribe(async () => {
|
||||||
modal.close();
|
modal.close();
|
||||||
await this.checkSelfHosted();
|
await this.checkSelfHosted();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { RouterModule } from "@angular/router";
|
import { RouterModule } from "@angular/router";
|
||||||
|
|
||||||
|
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
||||||
|
|
||||||
import { SharedModule } from "../../app/shared/shared.module";
|
import { SharedModule } from "../../app/shared/shared.module";
|
||||||
|
|
||||||
import { LoginWithDeviceComponent } from "./login-with-device.component";
|
import { LoginWithDeviceComponent } from "./login-with-device.component";
|
||||||
@@ -8,7 +10,7 @@ import { LoginComponent } from "./login.component";
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SharedModule, RouterModule],
|
imports: [SharedModule, RouterModule],
|
||||||
declarations: [LoginComponent, LoginWithDeviceComponent],
|
declarations: [LoginComponent, LoginWithDeviceComponent, EnvironmentSelectorComponent],
|
||||||
exports: [LoginComponent, LoginWithDeviceComponent],
|
exports: [LoginComponent, LoginWithDeviceComponent],
|
||||||
})
|
})
|
||||||
export class LoginModule {}
|
export class LoginModule {}
|
||||||
|
|||||||
BIN
apps/desktop/src/images/eu-flag.png
Normal file
BIN
apps/desktop/src/images/eu-flag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
BIN
apps/desktop/src/images/us-flag.png
Normal file
BIN
apps/desktop/src/images/us-flag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -2251,5 +2251,19 @@
|
|||||||
},
|
},
|
||||||
"windowsBiometricUpdateWarningTitle": {
|
"windowsBiometricUpdateWarningTitle": {
|
||||||
"message": "Recommended Settings Update"
|
"message": "Recommended Settings Update"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"message": "Region"
|
||||||
|
},
|
||||||
|
"eu": {
|
||||||
|
"message": "EU",
|
||||||
|
"description": "European Union"
|
||||||
|
},
|
||||||
|
"us": {
|
||||||
|
"message": "US",
|
||||||
|
"description": "United States"
|
||||||
|
},
|
||||||
|
"selfHosted": {
|
||||||
|
"message": "Self-hosted"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,3 +38,63 @@
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.environment-selector-btn {
|
||||||
|
font-size: $font-size-small;
|
||||||
|
color: $text-muted;
|
||||||
|
line-height: 25px;
|
||||||
|
font-weight: 400;
|
||||||
|
padding-left: 5px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a label:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.environment-selector-dialog {
|
||||||
|
@include themify($themes) {
|
||||||
|
background-color: themed("boxBackgroundColor");
|
||||||
|
}
|
||||||
|
padding: 5px;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12),
|
||||||
|
0 1px 5px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.environment-selector-dialog-item {
|
||||||
|
@include themify($themes) {
|
||||||
|
color: themed("textColor") !important;
|
||||||
|
}
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0px 15px 0px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@include themify($themes) {
|
||||||
|
background-color: themed("listItemBackgroundHoverColor") !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-bottom: -2px;
|
||||||
|
width: 22px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-us {
|
||||||
|
content: url("../images/us-flag.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-eu {
|
||||||
|
content: url("../images/eu-flag.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -421,6 +421,10 @@ app-root > #loading,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-top-padding {
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
.logo-image {
|
.logo-image {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
content: url("../images/logo-" + themed("logoSuffix") + "@2x.png");
|
content: url("../images/logo-" + themed("logoSuffix") + "@2x.png");
|
||||||
|
|||||||
@@ -1,5 +1,34 @@
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
<div class="container my-5 text-muted text-center">
|
<div class="container my-5 text-muted text-center">
|
||||||
|
<div class="tw-mb-1">
|
||||||
|
<bit-menu #environmentOptions>
|
||||||
|
<a bitMenuItem href="https://vault.bitwarden.com" class="pr-4">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-fw bwi-sm bwi-check pb-1"
|
||||||
|
aria-hidden="true"
|
||||||
|
[style.visibility]="isEuServer ? 'hidden' : 'visible'"
|
||||||
|
></i>
|
||||||
|
<img src="../../images/us_flag.png" alt="" class="pb-1 mr-1" />
|
||||||
|
{{ "us" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a bitMenuItem href="https://vault.bitwarden.eu" class="pr-4" *ngIf="euServerFlagEnabled">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-fw bwi-sm bwi-check pb-1"
|
||||||
|
aria-hidden="true"
|
||||||
|
[style.visibility]="isEuServer ? 'visible' : 'hidden'"
|
||||||
|
></i>
|
||||||
|
<img src="../../images/eu_flag.png" alt="" class="pb-1 mr-1" />
|
||||||
|
{{ "eu" | i18n }}
|
||||||
|
</a>
|
||||||
|
</bit-menu>
|
||||||
|
{{ "region" | i18n }}:
|
||||||
|
<a [routerLink]="[]" [bitMenuTriggerFor]="environmentOptions">
|
||||||
|
<b>{{ isEuServer ? ("eu" | i18n) : ("us" | i18n) }}</b
|
||||||
|
><i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
|
||||||
© {{ year }} Bitwarden Inc. <br />
|
© {{ year }} Bitwarden Inc. <br />
|
||||||
{{ "versionNumber" | i18n : version }}
|
{{ "versionNumber" | i18n : version }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
|
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-frontend-layout",
|
selector: "app-frontend-layout",
|
||||||
@@ -9,12 +12,22 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
|
|||||||
export class FrontendLayoutComponent implements OnInit, OnDestroy {
|
export class FrontendLayoutComponent implements OnInit, OnDestroy {
|
||||||
version: string;
|
version: string;
|
||||||
year = "2015";
|
year = "2015";
|
||||||
|
isEuServer = true;
|
||||||
|
euServerFlagEnabled: boolean;
|
||||||
|
|
||||||
constructor(private platformUtilsService: PlatformUtilsService) {}
|
constructor(
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private configService: ConfigServiceAbstraction
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.year = new Date().getFullYear().toString();
|
this.year = new Date().getFullYear().toString();
|
||||||
this.version = await this.platformUtilsService.getApplicationVersion();
|
this.version = await this.platformUtilsService.getApplicationVersion();
|
||||||
|
this.euServerFlagEnabled = await this.configService.getFeatureFlagBool(
|
||||||
|
FeatureFlag.DisplayEuEnvironmentFlag
|
||||||
|
);
|
||||||
|
this.isEuServer = Utils.getDomain(window.location.href).includes(".eu");
|
||||||
|
|
||||||
document.body.classList.add("layout_frontend");
|
document.body.classList.add("layout_frontend");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
apps/web/src/images/eu_flag.png
Normal file
BIN
apps/web/src/images/eu_flag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
BIN
apps/web/src/images/us_flag.png
Normal file
BIN
apps/web/src/images/us_flag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -6789,6 +6789,17 @@
|
|||||||
"enforceOnLoginDesc": {
|
"enforceOnLoginDesc": {
|
||||||
"message": "Require existing members to change their passwords"
|
"message": "Require existing members to change their passwords"
|
||||||
},
|
},
|
||||||
|
"region": {
|
||||||
|
"message": "Region"
|
||||||
|
},
|
||||||
|
"eu": {
|
||||||
|
"message": "EU",
|
||||||
|
"description": "European Union"
|
||||||
|
},
|
||||||
|
"us": {
|
||||||
|
"message": "US",
|
||||||
|
"description": "United States"
|
||||||
|
},
|
||||||
"smProjectDeleteAccessRestricted": {
|
"smProjectDeleteAccessRestricted": {
|
||||||
"message": "You don't have permissions to delete this project",
|
"message": "You don't have permissions to delete this project",
|
||||||
"description": "The individual description shown to the user when the user doesn't have access to delete a project."
|
"description": "The individual description shown to the user when the user doesn't have access to delete a project."
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<label class="environment-selector-btn">
|
||||||
|
{{ "region" | i18n }}:
|
||||||
|
<a
|
||||||
|
(click)="toggle(null)"
|
||||||
|
cdkOverlayOrigin
|
||||||
|
#trigger="cdkOverlayOrigin"
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-controls="cdk-overlay-container"
|
||||||
|
[ngSwitch]="selectedEnvironment"
|
||||||
|
>
|
||||||
|
<label *ngSwitchCase="ServerEnvironmentType.US" class="text-primary">{{ "us" | i18n }}</label>
|
||||||
|
<label *ngSwitchCase="ServerEnvironmentType.EU" class="text-primary">{{ "eu" | i18n }}</label>
|
||||||
|
<label *ngSwitchCase="ServerEnvironmentType.SelfHosted" class="text-primary">{{
|
||||||
|
"selfHosted" | i18n
|
||||||
|
}}</label>
|
||||||
|
<i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<ng-template
|
||||||
|
cdkConnectedOverlay
|
||||||
|
[cdkConnectedOverlayOrigin]="trigger"
|
||||||
|
(backdropClick)="close()"
|
||||||
|
(detach)="close()"
|
||||||
|
[cdkConnectedOverlayOpen]="isOpen"
|
||||||
|
[cdkConnectedOverlayPositions]="overlayPostition"
|
||||||
|
>
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="environment-selector-dialog" [@transformPanel]="'open'" role="dialog">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="environment-selector-dialog-item"
|
||||||
|
(click)="toggle(ServerEnvironmentType.US)"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-fw bwi-sm bwi-check"
|
||||||
|
style="padding-bottom: 1px"
|
||||||
|
aria-hidden="true"
|
||||||
|
[style.visibility]="
|
||||||
|
selectedEnvironment === ServerEnvironmentType.US ? 'visible' : 'hidden'
|
||||||
|
"
|
||||||
|
></i>
|
||||||
|
<img class="img-us" alt="" />
|
||||||
|
<span>{{ "us" | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
<br />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="environment-selector-dialog-item"
|
||||||
|
(click)="toggle(ServerEnvironmentType.EU)"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-fw bwi-sm bwi-check"
|
||||||
|
style="padding-bottom: 1px"
|
||||||
|
aria-hidden="true"
|
||||||
|
[style.visibility]="
|
||||||
|
selectedEnvironment === ServerEnvironmentType.EU ? 'visible' : 'hidden'
|
||||||
|
"
|
||||||
|
></i>
|
||||||
|
<img class="img-eu" alt="" />
|
||||||
|
<span>{{ "eu" | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
<br />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="environment-selector-dialog-item"
|
||||||
|
(click)="toggle(ServerEnvironmentType.SelfHosted)"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-fw bwi-sm bwi-check"
|
||||||
|
style="padding-bottom: 1px"
|
||||||
|
aria-hidden="true"
|
||||||
|
[style.visibility]="
|
||||||
|
selectedEnvironment === ServerEnvironmentType.SelfHosted ? 'visible' : 'hidden'
|
||||||
|
"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-fw bwi-sm bwi-pencil-square"
|
||||||
|
style="padding-bottom: 1px"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span>{{ "selfHosted" | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import { animate, state, style, transition, trigger } from "@angular/animations";
|
||||||
|
import { ConnectedPosition } from "@angular/cdk/overlay";
|
||||||
|
import { Component, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core";
|
||||||
|
import { Router } from "@angular/router";
|
||||||
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction";
|
||||||
|
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "environment-selector",
|
||||||
|
templateUrl: "environment-selector.component.html",
|
||||||
|
animations: [
|
||||||
|
trigger("transformPanel", [
|
||||||
|
state(
|
||||||
|
"void",
|
||||||
|
style({
|
||||||
|
opacity: 0,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
transition(
|
||||||
|
"void => open",
|
||||||
|
animate(
|
||||||
|
"100ms linear",
|
||||||
|
style({
|
||||||
|
opacity: 1,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
transition("* => void", animate("100ms linear", style({ opacity: 0 }))),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
|
||||||
|
@Output() onOpenSelfHostedSettings = new EventEmitter();
|
||||||
|
isOpen = false;
|
||||||
|
showingModal = false;
|
||||||
|
selectedEnvironment: ServerEnvironment;
|
||||||
|
ServerEnvironmentType = ServerEnvironment;
|
||||||
|
euServerFlagEnabled: boolean;
|
||||||
|
overlayPostition: ConnectedPosition[] = [
|
||||||
|
{
|
||||||
|
originX: "start",
|
||||||
|
originY: "bottom",
|
||||||
|
overlayX: "start",
|
||||||
|
overlayY: "top",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
protected componentDestroyed$: Subject<void> = new Subject();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected environmentService: EnvironmentService,
|
||||||
|
protected configService: ConfigServiceAbstraction,
|
||||||
|
protected router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.euServerFlagEnabled = await this.configService.getFeatureFlagBool(
|
||||||
|
FeatureFlag.DisplayEuEnvironmentFlag
|
||||||
|
);
|
||||||
|
this.updateEnvironmentInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.componentDestroyed$.next();
|
||||||
|
this.componentDestroyed$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggle(option: ServerEnvironment) {
|
||||||
|
this.isOpen = !this.isOpen;
|
||||||
|
if (option === ServerEnvironment.EU) {
|
||||||
|
await this.environmentService.setUrls({ base: "https://vault.bitwarden.eu" });
|
||||||
|
} else if (option === ServerEnvironment.US) {
|
||||||
|
await this.environmentService.setUrls({ base: "https://vault.bitwarden.com" });
|
||||||
|
} else if (option === ServerEnvironment.SelfHosted) {
|
||||||
|
this.onOpenSelfHostedSettings.emit();
|
||||||
|
}
|
||||||
|
this.updateEnvironmentInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEnvironmentInfo() {
|
||||||
|
const webvaultUrl = this.environmentService.getWebVaultUrl();
|
||||||
|
if (this.environmentService.isSelfHosted()) {
|
||||||
|
this.selectedEnvironment = ServerEnvironment.SelfHosted;
|
||||||
|
} else if (webvaultUrl != null && webvaultUrl.includes("bitwarden.eu")) {
|
||||||
|
this.selectedEnvironment = ServerEnvironment.EU;
|
||||||
|
} else {
|
||||||
|
this.selectedEnvironment = ServerEnvironment.US;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.isOpen = false;
|
||||||
|
this.updateEnvironmentInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ServerEnvironment {
|
||||||
|
US = "US",
|
||||||
|
EU = "EU",
|
||||||
|
SelfHosted = "Self-hosted",
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s
|
|||||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
|
import { ModalService } from "../services/modal.service";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class EnvironmentComponent {
|
export class EnvironmentComponent {
|
||||||
@Output() onSaved = new EventEmitter();
|
@Output() onSaved = new EventEmitter();
|
||||||
@@ -19,7 +21,8 @@ export class EnvironmentComponent {
|
|||||||
constructor(
|
constructor(
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
protected environmentService: EnvironmentService,
|
protected environmentService: EnvironmentService,
|
||||||
protected i18nService: I18nService
|
protected i18nService: I18nService,
|
||||||
|
private modalService: ModalService
|
||||||
) {
|
) {
|
||||||
const urls = this.environmentService.getUrls();
|
const urls = this.environmentService.getUrls();
|
||||||
|
|
||||||
@@ -59,5 +62,6 @@ export class EnvironmentComponent {
|
|||||||
|
|
||||||
protected saved() {
|
protected saved() {
|
||||||
this.onSaved.emit();
|
this.onSaved.emit();
|
||||||
|
this.modalService.closeAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,6 +218,8 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
|||||||
return ![
|
return ![
|
||||||
"http://vault.bitwarden.com",
|
"http://vault.bitwarden.com",
|
||||||
"https://vault.bitwarden.com",
|
"https://vault.bitwarden.com",
|
||||||
|
"http://vault.bitwarden.eu",
|
||||||
|
"https://vault.bitwarden.eu",
|
||||||
"http://vault.qa.bitwarden.pw",
|
"http://vault.qa.bitwarden.pw",
|
||||||
"https://vault.qa.bitwarden.pw",
|
"https://vault.qa.bitwarden.pw",
|
||||||
].includes(this.getWebVaultUrl());
|
].includes(this.getWebVaultUrl());
|
||||||
|
|||||||
Reference in New Issue
Block a user