1
0
mirror of https://github.com/bitwarden/web synced 2026-01-10 04:23:28 +00:00

Apply Prettier (#1347)

This commit is contained in:
Oscar Hinton
2021-12-17 15:57:11 +01:00
committed by GitHub
parent 2b0a9d995e
commit 56477eb39c
414 changed files with 33390 additions and 26857 deletions

View File

@@ -1,81 +1,123 @@
<div class="page-header">
<h1>{{'myOrganization' | i18n}}</h1>
<h1>{{ "myOrganization" | i18n }}</h1>
</div>
<div *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span>
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<form *ngIf="org && !loading" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="name">{{'organizationName' | i18n}}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="org.name"
[disabled]="selfHosted">
</div>
<div class="form-group">
<label for="billingEmail">{{'billingEmail' | i18n}}</label>
<input id="billingEmail" class="form-control" type="text" name="BillingEmail"
[(ngModel)]="org.billingEmail" [disabled]="selfHosted">
</div>
<div class="form-group">
<label for="businessName">{{'businessName' | i18n}}</label>
<input id="businessName" class="form-control" type="text" name="BusinessName"
[(ngModel)]="org.businessName" [disabled]="selfHosted">
</div>
<div class="form-group">
<label for="identifier">{{'identifier' | i18n}}</label>
<input id="identifier" class="form-control" type="text" name="Identifier"
[(ngModel)]="org.identifier">
</div>
</div>
<div class="col-6">
<app-avatar data="{{org.name}}" dynamic="true" size="75" fontSize="35"></app-avatar>
</div>
<form
*ngIf="org && !loading"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="name">{{ "organizationName" | i18n }}</label>
<input
id="name"
class="form-control"
type="text"
name="Name"
[(ngModel)]="org.name"
[disabled]="selfHosted"
/>
</div>
<div class="form-group">
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
<input
id="billingEmail"
class="form-control"
type="text"
name="BillingEmail"
[(ngModel)]="org.billingEmail"
[disabled]="selfHosted"
/>
</div>
<div class="form-group">
<label for="businessName">{{ "businessName" | i18n }}</label>
<input
id="businessName"
class="form-control"
type="text"
name="BusinessName"
[(ngModel)]="org.businessName"
[disabled]="selfHosted"
/>
</div>
<div class="form-group">
<label for="identifier">{{ "identifier" | i18n }}</label>
<input
id="identifier"
class="form-control"
type="text"
name="Identifier"
[(ngModel)]="org.identifier"
/>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span>
</button>
<div class="col-6">
<app-avatar data="{{ org.name }}" dynamic="true" size="75" fontSize="35"></app-avatar>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
</form>
<ng-container *ngIf="canUseApi">
<div class="secondary-header border-0 mb-0">
<h1>{{'apiKey' | i18n}}</h1>
</div>
<p>
{{'apiKeyDesc' | i18n}}
<a href="https://docs.bitwarden.com" target="_blank" rel="noopener">
{{'learnMore' | i18n}}
</a>
</p>
<button type="button" class="btn btn-outline-secondary" (click)="viewApiKey()">{{'viewApiKey' | i18n}}</button>
<button type="button" class="btn btn-outline-secondary" (click)="rotateApiKey()">{{'rotateApiKey' | i18n}}</button>
<div class="secondary-header border-0 mb-0">
<h1>{{ "apiKey" | i18n }}</h1>
</div>
<p>
{{ "apiKeyDesc" | i18n }}
<a href="https://docs.bitwarden.com" target="_blank" rel="noopener">
{{ "learnMore" | i18n }}
</a>
</p>
<button type="button" class="btn btn-outline-secondary" (click)="viewApiKey()">
{{ "viewApiKey" | i18n }}
</button>
<button type="button" class="btn btn-outline-secondary" (click)="rotateApiKey()">
{{ "rotateApiKey" | i18n }}
</button>
</ng-container>
<div class="secondary-header border-0 mb-0">
<h1>{{'taxInformation' | i18n}}</h1>
<h1>{{ "taxInformation" | i18n }}</h1>
</div>
<p>{{'taxInformationDesc' | i18n}}</p>
<p>{{ "taxInformationDesc" | i18n }}</p>
<div *ngIf="!org || loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span>
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<form *ngIf="org && !loading" #formTax (ngSubmit)="submitTaxInfo()" [appApiAction]="taxFormPromise" ngNativeValidate>
<app-tax-info></app-tax-info>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="formTax.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span>
</button>
<form
*ngIf="org && !loading"
#formTax
(ngSubmit)="submitTaxInfo()"
[appApiAction]="taxFormPromise"
ngNativeValidate
>
<app-tax-info></app-tax-info>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="formTax.loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
</form>
<div class="secondary-header text-danger border-0 mb-0">
<h1>{{'dangerZone' | i18n}}</h1>
<h1>{{ "dangerZone" | i18n }}</h1>
</div>
<div class="card border-danger">
<div class="card-body">
<p>{{'dangerZoneDesc' | i18n}}</p>
<button type="button" class="btn btn-outline-danger"
(click)="deleteOrganization()">{{'deleteOrganization' | i18n}}</button>
<button type="button" class="btn btn-outline-danger" (click)="purgeVault()">{{'purgeVault' | i18n}}</button>
</div>
<div class="card-body">
<p>{{ "dangerZoneDesc" | i18n }}</p>
<button type="button" class="btn btn-outline-danger" (click)="deleteOrganization()">
{{ "deleteOrganization" | i18n }}
</button>
<button type="button" class="btn btn-outline-danger" (click)="purgeVault()">
{{ "purgeVault" | i18n }}
</button>
</div>
</div>
<ng-template #deleteOrganizationTemplate></ng-template>
<ng-template #purgeOrganizationTemplate></ng-template>

View File

@@ -1,145 +1,155 @@
import {
Component,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import {
ActivatedRoute,
Router
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { OrganizationKeysRequest } from 'jslib-common/models/request/organizationKeysRequest';
import { OrganizationUpdateRequest } from 'jslib-common/models/request/organizationUpdateRequest';
import { OrganizationKeysRequest } from "jslib-common/models/request/organizationKeysRequest";
import { OrganizationUpdateRequest } from "jslib-common/models/request/organizationUpdateRequest";
import { OrganizationResponse } from 'jslib-common/models/response/organizationResponse';
import { OrganizationResponse } from "jslib-common/models/response/organizationResponse";
import { ApiKeyComponent } from '../../settings/api-key.component';
import { PurgeVaultComponent } from '../../settings/purge-vault.component';
import { TaxInfoComponent } from '../../settings/tax-info.component';
import { ApiKeyComponent } from "../../settings/api-key.component";
import { PurgeVaultComponent } from "../../settings/purge-vault.component";
import { TaxInfoComponent } from "../../settings/tax-info.component";
import { DeleteOrganizationComponent } from './delete-organization.component';
import { DeleteOrganizationComponent } from "./delete-organization.component";
@Component({
selector: 'app-org-account',
templateUrl: 'account.component.html',
selector: "app-org-account",
templateUrl: "account.component.html",
})
export class AccountComponent {
@ViewChild('deleteOrganizationTemplate', { read: ViewContainerRef, static: true }) deleteModalRef: ViewContainerRef;
@ViewChild('purgeOrganizationTemplate', { read: ViewContainerRef, static: true }) purgeModalRef: ViewContainerRef;
@ViewChild('apiKeyTemplate', { read: ViewContainerRef, static: true }) apiKeyModalRef: ViewContainerRef;
@ViewChild('rotateApiKeyTemplate', { read: ViewContainerRef, static: true }) rotateApiKeyModalRef: ViewContainerRef;
@ViewChild(TaxInfoComponent) taxInfo: TaxInfoComponent;
@ViewChild("deleteOrganizationTemplate", { read: ViewContainerRef, static: true })
deleteModalRef: ViewContainerRef;
@ViewChild("purgeOrganizationTemplate", { read: ViewContainerRef, static: true })
purgeModalRef: ViewContainerRef;
@ViewChild("apiKeyTemplate", { read: ViewContainerRef, static: true })
apiKeyModalRef: ViewContainerRef;
@ViewChild("rotateApiKeyTemplate", { read: ViewContainerRef, static: true })
rotateApiKeyModalRef: ViewContainerRef;
@ViewChild(TaxInfoComponent) taxInfo: TaxInfoComponent;
selfHosted = false;
loading = true;
canUseApi = false;
org: OrganizationResponse;
formPromise: Promise<any>;
taxFormPromise: Promise<any>;
selfHosted = false;
loading = true;
canUseApi = false;
org: OrganizationResponse;
formPromise: Promise<any>;
taxFormPromise: Promise<any>;
private organizationId: string;
private organizationId: string;
constructor(private modalService: ModalService,
private apiService: ApiService, private i18nService: I18nService,
private route: ActivatedRoute,
private syncService: SyncService, private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService, private logService: LogService,
private router: Router) { }
constructor(
private modalService: ModalService,
private apiService: ApiService,
private i18nService: I18nService,
private route: ActivatedRoute,
private syncService: SyncService,
private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService,
private logService: LogService,
private router: Router
) {}
async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost();
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
try {
this.org = await this.apiService.getOrganization(this.organizationId);
this.canUseApi = this.org.useApi;
} catch (e) {
this.logService.error(e);
}
async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost();
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
try {
this.org = await this.apiService.getOrganization(this.organizationId);
this.canUseApi = this.org.useApi;
} catch (e) {
this.logService.error(e);
}
});
this.loading = false;
}
async submit() {
try {
const request = new OrganizationUpdateRequest();
request.name = this.org.name;
request.businessName = this.org.businessName;
request.billingEmail = this.org.billingEmail;
request.identifier = this.org.identifier;
// Backfill pub/priv key if necessary
if (!this.org.hasPublicAndPrivateKeys) {
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
}
this.formPromise = this.apiService.putOrganization(this.organizationId, request).then(() => {
return this.syncService.fullSync(true);
});
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("organizationUpdated")
);
} catch (e) {
this.logService.error(e);
}
}
async submitTaxInfo() {
this.taxFormPromise = this.taxInfo.submitTaxInfo();
await this.taxFormPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("taxInfoUpdated"));
}
async deleteOrganization() {
await this.modalService.openViewRef(
DeleteOrganizationComponent,
this.deleteModalRef,
(comp) => {
comp.organizationId = this.organizationId;
comp.onSuccess.subscribe(() => {
this.router.navigate(["/"]);
});
this.loading = false;
}
}
);
}
async submit() {
try {
const request = new OrganizationUpdateRequest();
request.name = this.org.name;
request.businessName = this.org.businessName;
request.billingEmail = this.org.billingEmail;
request.identifier = this.org.identifier;
async purgeVault() {
await this.modalService.openViewRef(PurgeVaultComponent, this.purgeModalRef, (comp) => {
comp.organizationId = this.organizationId;
});
}
// Backfill pub/priv key if necessary
if (!this.org.hasPublicAndPrivateKeys) {
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
}
async viewApiKey() {
await this.modalService.openViewRef(ApiKeyComponent, this.apiKeyModalRef, (comp) => {
comp.keyType = "organization";
comp.entityId = this.organizationId;
comp.postKey = this.apiService.postOrganizationApiKey.bind(this.apiService);
comp.scope = "api.organization";
comp.grantType = "client_credentials";
comp.apiKeyTitle = "apiKey";
comp.apiKeyWarning = "apiKeyWarning";
comp.apiKeyDescription = "apiKeyDesc";
});
}
this.formPromise = this.apiService.putOrganization(this.organizationId, request).then(() => {
return this.syncService.fullSync(true);
});
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('organizationUpdated'));
} catch (e) {
this.logService.error(e);
}
}
async submitTaxInfo() {
this.taxFormPromise = this.taxInfo.submitTaxInfo();
await this.taxFormPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('taxInfoUpdated'));
}
async deleteOrganization() {
await this.modalService.openViewRef(DeleteOrganizationComponent, this.deleteModalRef, comp => {
comp.organizationId = this.organizationId;
comp.onSuccess.subscribe(() => {
this.router.navigate(['/']);
});
});
}
async purgeVault() {
await this.modalService.openViewRef(PurgeVaultComponent, this.purgeModalRef, comp => {
comp.organizationId = this.organizationId;
});
}
async viewApiKey() {
await this.modalService.openViewRef(ApiKeyComponent, this.apiKeyModalRef, comp => {
comp.keyType = 'organization';
comp.entityId = this.organizationId;
comp.postKey = this.apiService.postOrganizationApiKey.bind(this.apiService);
comp.scope = 'api.organization';
comp.grantType = 'client_credentials';
comp.apiKeyTitle = 'apiKey';
comp.apiKeyWarning = 'apiKeyWarning';
comp.apiKeyDescription = 'apiKeyDesc';
});
}
async rotateApiKey() {
await this.modalService.openViewRef(ApiKeyComponent, this.rotateApiKeyModalRef, comp => {
comp.keyType = 'organization';
comp.isRotation = true;
comp.entityId = this.organizationId;
comp.postKey = this.apiService.postOrganizationRotateApiKey.bind(this.apiService);
comp.scope = 'api.organization';
comp.grantType = 'client_credentials';
comp.apiKeyTitle = 'apiKey';
comp.apiKeyWarning = 'apiKeyWarning';
comp.apiKeyDescription = 'apiKeyRotateDesc';
});
}
async rotateApiKey() {
await this.modalService.openViewRef(ApiKeyComponent, this.rotateApiKeyModalRef, (comp) => {
comp.keyType = "organization";
comp.isRotation = true;
comp.entityId = this.organizationId;
comp.postKey = this.apiService.postOrganizationRotateApiKey.bind(this.apiService);
comp.scope = "api.organization";
comp.grantType = "client_credentials";
comp.apiKeyTitle = "apiKey";
comp.apiKeyWarning = "apiKeyWarning";
comp.apiKeyDescription = "apiKeyRotateDesc";
});
}
}

View File

@@ -1,42 +1,65 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div>
<div class="row">
<div class="form-group col-6">
<label for="newSeatCount">{{'subscriptionSeats' | i18n}}</label>
<input id="newSeatCount" class="form-control" type="number" name="NewSeatCount"
[(ngModel)]="newSeatCount" min="0" step="1" required>
<small class="d-block text-muted mb-4">
<strong>{{'total' | i18n}}:</strong> {{newSeatCount || 0}} &times; {{seatPrice | currency:'$'}} =
{{adjustedSeatTotal | currency:'$'}} / {{interval | i18n}}
</small>
</div>
</div>
<div class="row mb-4">
<div class="form-group col-sm">
<div class="form-check">
<input id="limitSubscription" class="form-check-input" type="checkbox" name="LimitSubscription"
[(ngModel)]="limitSubscription" (change)="limitSubscriptionChanged()">
<label for="limitSubscription">{{'limitSubscription' | i18n}}</label>
</div>
<small class="d-block text-muted">{{'limitSubscriptionDesc' | i18n}}</small>
</div>
</div>
<div class="row mb-4" [hidden]="!limitSubscription">
<div class="form-group col-sm">
<label for="maxAutoscaleSeats">{{'maxSeatLimit' | i18n}}</label>
<input id="maxAutoscaleSeats" class="form-control col-6" type="number" name="MaxAutoscaleSeats"
[(ngModel)]="newMaxSeats" [min]="newSeatCount == null ? 1 : newSeatCount" step="1"
[required]="limitSubscription">
<small class="d-block text-muted">
<strong>{{'maxSeatCost' | i18n}}:</strong> {{newMaxSeats || 0}} &times;
{{seatPrice | currency:'$'}} = {{maxSeatTotal | currency:'$'}} / {{interval | i18n}}
</small>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span>
</button>
<div>
<div class="row">
<div class="form-group col-6">
<label for="newSeatCount">{{ "subscriptionSeats" | i18n }}</label>
<input
id="newSeatCount"
class="form-control"
type="number"
name="NewSeatCount"
[(ngModel)]="newSeatCount"
min="0"
step="1"
required
/>
<small class="d-block text-muted mb-4">
<strong>{{ "total" | i18n }}:</strong> {{ newSeatCount || 0 }} &times;
{{ seatPrice | currency: "$" }} = {{ adjustedSeatTotal | currency: "$" }} /
{{ interval | i18n }}
</small>
</div>
</div>
<div class="row mb-4">
<div class="form-group col-sm">
<div class="form-check">
<input
id="limitSubscription"
class="form-check-input"
type="checkbox"
name="LimitSubscription"
[(ngModel)]="limitSubscription"
(change)="limitSubscriptionChanged()"
/>
<label for="limitSubscription">{{ "limitSubscription" | i18n }}</label>
</div>
<small class="d-block text-muted">{{ "limitSubscriptionDesc" | i18n }}</small>
</div>
</div>
<div class="row mb-4" [hidden]="!limitSubscription">
<div class="form-group col-sm">
<label for="maxAutoscaleSeats">{{ "maxSeatLimit" | i18n }}</label>
<input
id="maxAutoscaleSeats"
class="form-control col-6"
type="number"
name="MaxAutoscaleSeats"
[(ngModel)]="newMaxSeats"
[min]="newSeatCount == null ? 1 : newSeatCount"
step="1"
[required]="limitSubscription"
/>
<small class="d-block text-muted">
<strong>{{ "maxSeatCost" | i18n }}:</strong> {{ newMaxSeats || 0 }} &times;
{{ seatPrice | currency: "$" }} = {{ maxSeatTotal | currency: "$" }} /
{{ interval | i18n }}
</small>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
</div>
</form>
<app-payment [showMethods]="false"></app-payment>

View File

@@ -1,69 +1,75 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { OrganizationSubscriptionUpdateRequest } from 'jslib-common/models/request/organizationSubscriptionUpdateRequest';
import { OrganizationSubscriptionUpdateRequest } from "jslib-common/models/request/organizationSubscriptionUpdateRequest";
@Component({
selector: 'app-adjust-subscription',
templateUrl: 'adjust-subscription.component.html',
selector: "app-adjust-subscription",
templateUrl: "adjust-subscription.component.html",
})
export class AdjustSubscription {
@Input() organizationId: string;
@Input() maxAutoscaleSeats: number;
@Input() currentSeatCount: number;
@Input() seatPrice = 0;
@Input() interval = 'year';
@Output() onAdjusted = new EventEmitter();
@Input() organizationId: string;
@Input() maxAutoscaleSeats: number;
@Input() currentSeatCount: number;
@Input() seatPrice = 0;
@Input() interval = "year";
@Output() onAdjusted = new EventEmitter();
formPromise: Promise<any>;
limitSubscription: boolean;
newSeatCount: number;
newMaxSeats: number;
formPromise: Promise<any>;
limitSubscription: boolean;
newSeatCount: number;
newMaxSeats: number;
constructor(private apiService: ApiService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private logService: LogService) { }
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
ngOnInit() {
this.limitSubscription = this.maxAutoscaleSeats != null;
this.newSeatCount = this.currentSeatCount;
this.newMaxSeats = this.maxAutoscaleSeats;
ngOnInit() {
this.limitSubscription = this.maxAutoscaleSeats != null;
this.newSeatCount = this.currentSeatCount;
this.newMaxSeats = this.maxAutoscaleSeats;
}
async submit() {
try {
const seatAdjustment = this.newSeatCount - this.currentSeatCount;
const request = new OrganizationSubscriptionUpdateRequest(seatAdjustment, this.newMaxSeats);
this.formPromise = this.apiService.postOrganizationUpdateSubscription(
this.organizationId,
request
);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("subscriptionUpdated")
);
} catch (e) {
this.logService.error(e);
}
this.onAdjusted.emit();
}
async submit() {
try {
const seatAdjustment = this.newSeatCount - this.currentSeatCount;
const request = new OrganizationSubscriptionUpdateRequest(seatAdjustment, this.newMaxSeats);
this.formPromise = this.apiService.postOrganizationUpdateSubscription(this.organizationId, request);
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('subscriptionUpdated'));
} catch (e) {
this.logService.error(e);
}
this.onAdjusted.emit();
limitSubscriptionChanged() {
if (!this.limitSubscription) {
this.newMaxSeats = null;
}
}
limitSubscriptionChanged() {
if (!this.limitSubscription) {
this.newMaxSeats = null;
}
}
get adjustedSeatTotal(): number {
return this.newSeatCount * this.seatPrice;
}
get adjustedSeatTotal(): number {
return this.newSeatCount * this.seatPrice;
}
get maxSeatTotal(): number {
return this.newMaxSeats * this.seatPrice;
}
get maxSeatTotal(): number {
return this.newMaxSeats * this.seatPrice;
}
}

View File

@@ -1,11 +1,18 @@
<div class="card card-org-plans">
<div class="card-body">
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}" (click)="cancel()"><span
aria-hidden="true">&times;</span></button>
<h2 class="card-body-header">{{'changeBillingPlan' | i18n}}</h2>
<p class="mb-0">{{'changeBillingPlanUpgrade' | i18n}}</p>
<app-organization-plans [showFree]="false" [showCancel]="true" [plan]="defaultUpgradePlan"
[product]="defaultUpgradeProduct" [organizationId]="organizationId" (onCanceled)="cancel()">
</app-organization-plans>
</div>
<div class="card-body">
<button type="button" class="close" appA11yTitle="{{ 'cancel' | i18n }}" (click)="cancel()">
<span aria-hidden="true">&times;</span>
</button>
<h2 class="card-body-header">{{ "changeBillingPlan" | i18n }}</h2>
<p class="mb-0">{{ "changeBillingPlanUpgrade" | i18n }}</p>
<app-organization-plans
[showFree]="false"
[showCancel]="true"
[plan]="defaultUpgradePlan"
[product]="defaultUpgradeProduct"
[organizationId]="organizationId"
(onCanceled)="cancel()"
>
</app-organization-plans>
</div>
</div>

View File

@@ -1,39 +1,34 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { LogService } from 'jslib-common/abstractions/log.service';
import { LogService } from "jslib-common/abstractions/log.service";
import { PlanType } from 'jslib-common/enums/planType';
import { ProductType } from 'jslib-common/enums/productType';
import { PlanType } from "jslib-common/enums/planType";
import { ProductType } from "jslib-common/enums/productType";
@Component({
selector: 'app-change-plan',
templateUrl: 'change-plan.component.html',
selector: "app-change-plan",
templateUrl: "change-plan.component.html",
})
export class ChangePlanComponent {
@Input() organizationId: string;
@Output() onChanged = new EventEmitter();
@Output() onCanceled = new EventEmitter();
@Input() organizationId: string;
@Output() onChanged = new EventEmitter();
@Output() onCanceled = new EventEmitter();
formPromise: Promise<any>;
defaultUpgradePlan: PlanType = PlanType.FamiliesAnnually;
defaultUpgradeProduct: ProductType = ProductType.Families;
formPromise: Promise<any>;
defaultUpgradePlan: PlanType = PlanType.FamiliesAnnually;
defaultUpgradeProduct: ProductType = ProductType.Families;
constructor(private logService: LogService) { }
constructor(private logService: LogService) {}
async submit() {
try {
this.onChanged.emit();
} catch (e) {
this.logService.error(e);
}
async submit() {
try {
this.onChanged.emit();
} catch (e) {
this.logService.error(e);
}
}
cancel() {
this.onCanceled.emit();
}
cancel() {
this.onCanceled.emit();
}
}

View File

@@ -1,25 +1,38 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="deleteOrganizationTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="deleteOrganizationTitle">{{'deleteOrganization' | i18n}}</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{descriptionKey | i18n}}</p>
<app-callout type="warning">{{'deleteOrganizationWarning' | i18n}}</app-callout>
<app-verify-master-password [(ngModel)]="masterPassword" ngDefaultControl name="secret">
</app-verify-master-password>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'deleteOrganization' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</form>
</div>
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h2 class="modal-title" id="deleteOrganizationTitle">{{ "deleteOrganization" | i18n }}</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{ descriptionKey | i18n }}</p>
<app-callout type="warning">{{ "deleteOrganizationWarning" | i18n }}</app-callout>
<app-verify-master-password [(ngModel)]="masterPassword" ngDefaultControl name="secret">
</app-verify-master-password>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "deleteOrganization" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,43 +1,47 @@
import {
Component,
EventEmitter,
Output,
} from '@angular/core';
import { Component, EventEmitter, Output } from "@angular/core";
import { Verification } from 'jslib-common/types/verification';
import { Verification } from "jslib-common/types/verification";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
@Component({
selector: 'app-delete-organization',
templateUrl: 'delete-organization.component.html',
selector: "app-delete-organization",
templateUrl: "delete-organization.component.html",
})
export class DeleteOrganizationComponent {
organizationId: string;
descriptionKey = 'deleteOrganizationDesc';
@Output() onSuccess: EventEmitter<any> = new EventEmitter();
organizationId: string;
descriptionKey = "deleteOrganizationDesc";
@Output() onSuccess: EventEmitter<any> = new EventEmitter();
masterPassword: Verification;
formPromise: Promise<any>;
masterPassword: Verification;
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private userVerificationService: UserVerificationService,
private logService: LogService) { }
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private userVerificationService: UserVerificationService,
private logService: LogService
) {}
async submit() {
try {
this.formPromise = this.userVerificationService.buildRequest(this.masterPassword)
.then(request => this.apiService.deleteOrganization(this.organizationId, request));
await this.formPromise;
this.platformUtilsService.showToast('success', this.i18nService.t('organizationDeleted'),
this.i18nService.t('organizationDeletedDesc'));
this.onSuccess.emit();
} catch (e) {
this.logService.error(e);
}
async submit() {
try {
this.formPromise = this.userVerificationService
.buildRequest(this.masterPassword)
.then((request) => this.apiService.deleteOrganization(this.organizationId, request));
await this.formPromise;
this.platformUtilsService.showToast(
"success",
this.i18nService.t("organizationDeleted"),
this.i18nService.t("organizationDeletedDesc")
);
this.onSuccess.emit();
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,27 +1,39 @@
<form #form class="card" (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="card-body">
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}" (click)="cancel()"><span
aria-hidden="true">&times;</span></button>
<h3 class="card-body-header">{{'downloadLicense' | i18n}}</h3>
<div class="row">
<div class="form-group col-6">
<div class="d-flex">
<label for="installationId">{{'enterInstallationId' | i18n}}</label>
<a class="ml-auto" target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
href="https://help.bitwarden.com/article/licensing-on-premise/#organization-account-sharing">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</div>
<input id="installationId" class="form-control" type="text" name="InstallationId"
[(ngModel)]="installationId" required>
</div>
<div class="card-body">
<button type="button" class="close" appA11yTitle="{{ 'cancel' | i18n }}" (click)="cancel()">
<span aria-hidden="true">&times;</span>
</button>
<h3 class="card-body-header">{{ "downloadLicense" | i18n }}</h3>
<div class="row">
<div class="form-group col-6">
<div class="d-flex">
<label for="installationId">{{ "enterInstallationId" | i18n }}</label>
<a
class="ml-auto"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://help.bitwarden.com/article/licensing-on-premise/#organization-account-sharing"
>
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'submit' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" (click)="cancel()">
{{'cancel' | i18n}}
</button>
<input
id="installationId"
class="form-control"
type="text"
name="InstallationId"
[(ngModel)]="installationId"
required
/>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "submit" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" (click)="cancel()">
{{ "cancel" | i18n }}
</button>
</div>
</form>

View File

@@ -1,46 +1,52 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Component({
selector: 'app-download-license',
templateUrl: 'download-license.component.html',
selector: "app-download-license",
templateUrl: "download-license.component.html",
})
export class DownloadLicenseComponent {
@Input() organizationId: string;
@Output() onDownloaded = new EventEmitter();
@Output() onCanceled = new EventEmitter();
@Input() organizationId: string;
@Output() onDownloaded = new EventEmitter();
@Output() onCanceled = new EventEmitter();
installationId: string;
formPromise: Promise<any>;
installationId: string;
formPromise: Promise<any>;
constructor(private apiService: ApiService, private platformUtilsService: PlatformUtilsService,
private logService: LogService) { }
constructor(
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async submit() {
if (this.installationId == null || this.installationId === '') {
return;
}
try {
this.formPromise = this.apiService.getOrganizationLicense(this.organizationId, this.installationId);
const license = await this.formPromise;
const licenseString = JSON.stringify(license, null, 2);
this.platformUtilsService.saveFile(window, licenseString, null, 'bitwarden_organization_license.json');
this.onDownloaded.emit();
} catch (e) {
this.logService.error(e);
}
async submit() {
if (this.installationId == null || this.installationId === "") {
return;
}
cancel() {
this.onCanceled.emit();
try {
this.formPromise = this.apiService.getOrganizationLicense(
this.organizationId,
this.installationId
);
const license = await this.formPromise;
const licenseString = JSON.stringify(license, null, 2);
this.platformUtilsService.saveFile(
window,
licenseString,
null,
"bitwarden_organization_license.json"
);
this.onDownloaded.emit();
} catch (e) {
this.logService.error(e);
}
}
cancel() {
this.onCanceled.emit();
}
}

View File

@@ -1,32 +1,33 @@
import {
Component,
OnInit,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { UserBillingComponent } from '../../settings/user-billing.component';
import { UserBillingComponent } from "../../settings/user-billing.component";
@Component({
selector: 'app-org-billing',
templateUrl: '../../settings/user-billing.component.html',
selector: "app-org-billing",
templateUrl: "../../settings/user-billing.component.html",
})
export class OrganizationBillingComponent extends UserBillingComponent implements OnInit {
constructor(apiService: ApiService, i18nService: I18nService,
private route: ActivatedRoute, platformUtilsService: PlatformUtilsService,
logService: LogService) {
super(apiService, i18nService, platformUtilsService, logService);
}
constructor(
apiService: ApiService,
i18nService: I18nService,
private route: ActivatedRoute,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(apiService, i18nService, platformUtilsService, logService);
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
await this.load();
this.firstLoaded = true;
});
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await this.load();
this.firstLoaded = true;
});
}
}

View File

@@ -1,171 +1,265 @@
<div class="page-header">
<h1>
{{'subscription' | i18n}}
<small *ngIf="firstLoaded && loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span>
</small>
</h1>
<h1>
{{ "subscription" | i18n }}
<small *ngIf="firstLoaded && loading">
<i
class="fa fa-spinner fa-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</small>
</h1>
</div>
<ng-container *ngIf="!firstLoaded && loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
<span class="sr-only">{{'loading' | i18n}}</span>
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="sub">
<app-callout type="warning" title="{{'canceled' | i18n}}" *ngIf="subscription && subscription.cancelled">
{{'subscriptionCanceled' | i18n}}</app-callout>
<app-callout type="warning" title="{{'pendingCancellation' | i18n}}" *ngIf="subscriptionMarkedForCancel">
<p>{{'subscriptionPendingCanceled' | i18n}}</p>
<button #reinstateBtn type="button" class="btn btn-outline-secondary btn-submit" (click)="reinstate()"
[appApiAction]="reinstatePromise" [disabled]="reinstateBtn.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'reinstateSubscription' | i18n}}</span>
</button>
</app-callout>
<ng-container *ngIf="!selfHosted">
<div class="row">
<div class="col-4">
<dl>
<dt>{{'billingPlan' | i18n}}</dt>
<dd>{{sub.plan.name}}</dd>
<ng-container *ngIf="subscription">
<dt>{{'status' | i18n}}</dt>
<dd>
<span class="text-capitalize">{{isSponsoredSubscription ? 'sponsored' : subscription.status || '-'}}</span>
<span class="badge badge-warning"
*ngIf="subscriptionMarkedForCancel">{{'pendingCancellation' |
i18n}}</span>
</dd>
<dt>{{'nextCharge' | i18n}}</dt>
<dd>{{nextInvoice ? ((nextInvoice.date | date: 'mediumDate') + ', ' + (nextInvoice.amount |
currency:'$'))
: '-'}}
</dd>
</ng-container>
</dl>
</div>
<div class="col-8" *ngIf="subscription">
<strong class="d-block mb-1">{{'details' | i18n}}</strong>
<table class="table">
<tbody>
<tr *ngFor="let i of subscription.items">
<td>
{{i.name}} {{i.quantity > 1 ? '&times;' + i.quantity : ''}} @ {{i.amount |
currency:'$'}}
</td>
<td>
{{(i.quantity * i.amount) | currency:'$'}} /{{i.interval | i18n}}
</td>
</tr>
</tbody>
</table>
</div>
<ng-container *ngIf="userOrg?.providerId != null">
<div class="col-sm">
<dl>
<dt>{{'provider' | i18n}}</dt>
<dd>{{'yourProviderIs' | i18n : userOrg.providerName}}</dd>
</dl>
</div>
</ng-container>
</div>
<ng-container>
<button type="button" class="btn btn-outline-secondary" (click)="changePlan()" *ngIf="showChangePlanButton">
{{'changeBillingPlan' | i18n}}
</button>
<app-change-plan [organizationId]="organizationId" (onChanged)="closeChangePlan(true)"
(onCanceled)="closeChangePlan(false)" *ngIf="showChangePlan"></app-change-plan>
</ng-container>
<h2 class="spaced-header">{{'manageSubscription' | i18n}}</h2>
<p class="mb-4">{{subscriptionDesc}}</p>
<ng-container *ngIf="subscription && canAdjustSeats && !subscription.cancelled && !subscriptionMarkedForCancel">
<div class="mt-3">
<app-adjust-subscription [seatPrice]="seatPrice" [organizationId]="organizationId" [interval]="billingInterval"
[currentSeatCount]="seats" [maxAutoscaleSeats]="maxAutoscaleSeats" (onAdjusted)="subscriptionAdjusted()">
</app-adjust-subscription>
</div>
</ng-container>
<button #removeSponsorshipBtn type="button" class="btn btn-outline-danger btn-submit" (click)="removeSponsorship()"
[appApiAction]="removeSponsorshipPromise" [disabled]="removeSponsorshipBtn.loading"
*ngIf="isSponsoredSubscription">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'removeSponsorship' | i18n}}</span>
</button>
<h2 class="spaced-header">{{'storage' | i18n}}</h2>
<p>{{'subscriptionStorage' | i18n : sub.maxStorageGb || 0 : sub.storageName || '0 MB'}}</p>
<div class="progress">
<div class="progress-bar bg-success" role="progressbar" [ngStyle]="{width: storageProgressWidth + '%' }"
[attr.aria-valuenow]="storagePercentage" aria-valuemin="0" aria-valuemax="100">
{{(storagePercentage / 100) | percent}}</div>
</div>
<ng-container *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
<div class="mt-3">
<div class="d-flex" *ngIf="!showAdjustStorage">
<button type="button" class="btn btn-outline-secondary" (click)="adjustStorage(true)">
{{'addStorage' | i18n}}
</button>
<button type="button" class="btn btn-outline-secondary ml-1" (click)="adjustStorage(false)">
{{'removeStorage' | i18n}}
</button>
</div>
<app-adjust-storage [storageGbPrice]="storageGbPrice" [add]="adjustStorageAdd"
[organizationId]="organizationId" [interval]="billingInterval" (onAdjusted)="closeStorage(true)"
(onCanceled)="closeStorage(false)" *ngIf="showAdjustStorage"></app-adjust-storage>
</div>
</ng-container>
<h2 class="spaced-header">{{'additionalOptions' | i18n}}</h2>
<p class="mb-4">
{{'additionalOptionsDesc' | i18n }}
</p>
<div class="d-flex">
<button type="button" class="btn btn-outline-secondary" (click)="downloadLicense()" *ngIf="canDownloadLicense"
[disabled]="showDownloadLicense">
{{'downloadLicense' | i18n}}
</button>
<button #cancelBtn type="button" class="btn btn-outline-danger btn-submit ml-1" (click)="cancel()"
[appApiAction]="cancelPromise" [disabled]="cancelBtn.loading"
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'cancelSubscription' | i18n}}</span>
</button>
</div>
<div class="mt-3" *ngIf="showDownloadLicense">
<app-download-license [organizationId]="organizationId" (onDownloaded)="closeDownloadLicense()"
(onCanceled)="closeDownloadLicense()"></app-download-license>
</div>
</ng-container>
<ng-container *ngIf="selfHosted">
<app-callout
type="warning"
title="{{ 'canceled' | i18n }}"
*ngIf="subscription && subscription.cancelled"
>
{{ "subscriptionCanceled" | i18n }}</app-callout
>
<app-callout
type="warning"
title="{{ 'pendingCancellation' | i18n }}"
*ngIf="subscriptionMarkedForCancel"
>
<p>{{ "subscriptionPendingCanceled" | i18n }}</p>
<button
#reinstateBtn
type="button"
class="btn btn-outline-secondary btn-submit"
(click)="reinstate()"
[appApiAction]="reinstatePromise"
[disabled]="reinstateBtn.loading"
>
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "reinstateSubscription" | i18n }}</span>
</button>
</app-callout>
<ng-container *ngIf="!selfHosted">
<div class="row">
<div class="col-4">
<dl>
<dt>{{'billingPlan' | i18n}}</dt>
<dd>{{sub.plan.name}}</dd>
<dt>{{'expiration' | i18n}}</dt>
<dd *ngIf="sub.expiration">
{{sub.expiration | date:'mediumDate'}}
<span *ngIf="isExpired" class="text-danger ml-2">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
{{'licenseIsExpired' | i18n}}
</span>
<dt>{{ "billingPlan" | i18n }}</dt>
<dd>{{ sub.plan.name }}</dd>
<ng-container *ngIf="subscription">
<dt>{{ "status" | i18n }}</dt>
<dd>
<span class="text-capitalize">{{
isSponsoredSubscription ? "sponsored" : subscription.status || "-"
}}</span>
<span class="badge badge-warning" *ngIf="subscriptionMarkedForCancel">{{
"pendingCancellation" | i18n
}}</span>
</dd>
<dd *ngIf="!sub.expiration">{{'neverExpires' | i18n}}</dd>
<dt>{{ "nextCharge" | i18n }}</dt>
<dd>
{{
nextInvoice
? (nextInvoice.date | date: "mediumDate") +
", " +
(nextInvoice.amount | currency: "$")
: "-"
}}
</dd>
</ng-container>
</dl>
<div>
<button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
{{'updateLicense' | i18n}}
</button>
<a href="https://vault.bitwarden.com" target="_blank" rel="noopener" class="btn btn-outline-secondary">
{{'manageSubscription' | i18n}}
</a>
</div>
<div class="card mt-3" *ngIf="showUpdateLicense">
<div class="card-body">
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}"
(click)="closeUpdateLicense(false)"><span aria-hidden="true">&times;</span></button>
<h3 class="card-body-header">{{'updateLicense' | i18n}}</h3>
<app-update-license [organizationId]="organizationId" (onUpdated)="closeUpdateLicense(true)"
(onCanceled)="closeUpdateLicense(false)"></app-update-license>
</div>
</div>
<div class="col-8" *ngIf="subscription">
<strong class="d-block mb-1">{{ "details" | i18n }}</strong>
<table class="table">
<tbody>
<tr *ngFor="let i of subscription.items">
<td>
{{ i.name }} {{ i.quantity > 1 ? "&times;" + i.quantity : "" }} @
{{ i.amount | currency: "$" }}
</td>
<td>{{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}</td>
</tr>
</tbody>
</table>
</div>
<ng-container *ngIf="userOrg?.providerId != null">
<div class="col-sm">
<dl>
<dt>{{ "provider" | i18n }}</dt>
<dd>{{ "yourProviderIs" | i18n: userOrg.providerName }}</dd>
</dl>
</div>
</ng-container>
</div>
<ng-container>
<button
type="button"
class="btn btn-outline-secondary"
(click)="changePlan()"
*ngIf="showChangePlanButton"
>
{{ "changeBillingPlan" | i18n }}
</button>
<app-change-plan
[organizationId]="organizationId"
(onChanged)="closeChangePlan(true)"
(onCanceled)="closeChangePlan(false)"
*ngIf="showChangePlan"
></app-change-plan>
</ng-container>
<h2 class="spaced-header">{{ "manageSubscription" | i18n }}</h2>
<p class="mb-4">{{ subscriptionDesc }}</p>
<ng-container
*ngIf="
subscription && canAdjustSeats && !subscription.cancelled && !subscriptionMarkedForCancel
"
>
<div class="mt-3">
<app-adjust-subscription
[seatPrice]="seatPrice"
[organizationId]="organizationId"
[interval]="billingInterval"
[currentSeatCount]="seats"
[maxAutoscaleSeats]="maxAutoscaleSeats"
(onAdjusted)="subscriptionAdjusted()"
>
</app-adjust-subscription>
</div>
</ng-container>
<button
#removeSponsorshipBtn
type="button"
class="btn btn-outline-danger btn-submit"
(click)="removeSponsorship()"
[appApiAction]="removeSponsorshipPromise"
[disabled]="removeSponsorshipBtn.loading"
*ngIf="isSponsoredSubscription"
>
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "removeSponsorship" | i18n }}</span>
</button>
<h2 class="spaced-header">{{ "storage" | i18n }}</h2>
<p>{{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0:sub.storageName || "0 MB" }}</p>
<div class="progress">
<div
class="progress-bar bg-success"
role="progressbar"
[ngStyle]="{ width: storageProgressWidth + '%' }"
[attr.aria-valuenow]="storagePercentage"
aria-valuemin="0"
aria-valuemax="100"
>
{{ storagePercentage / 100 | percent }}
</div>
</div>
<ng-container *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
<div class="mt-3">
<div class="d-flex" *ngIf="!showAdjustStorage">
<button type="button" class="btn btn-outline-secondary" (click)="adjustStorage(true)">
{{ "addStorage" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary ml-1"
(click)="adjustStorage(false)"
>
{{ "removeStorage" | i18n }}
</button>
</div>
<app-adjust-storage
[storageGbPrice]="storageGbPrice"
[add]="adjustStorageAdd"
[organizationId]="organizationId"
[interval]="billingInterval"
(onAdjusted)="closeStorage(true)"
(onCanceled)="closeStorage(false)"
*ngIf="showAdjustStorage"
></app-adjust-storage>
</div>
</ng-container>
<h2 class="spaced-header">{{ "additionalOptions" | i18n }}</h2>
<p class="mb-4">
{{ "additionalOptionsDesc" | i18n }}
</p>
<div class="d-flex">
<button
type="button"
class="btn btn-outline-secondary"
(click)="downloadLicense()"
*ngIf="canDownloadLicense"
[disabled]="showDownloadLicense"
>
{{ "downloadLicense" | i18n }}
</button>
<button
#cancelBtn
type="button"
class="btn btn-outline-danger btn-submit ml-1"
(click)="cancel()"
[appApiAction]="cancelPromise"
[disabled]="cancelBtn.loading"
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel"
>
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "cancelSubscription" | i18n }}</span>
</button>
</div>
<div class="mt-3" *ngIf="showDownloadLicense">
<app-download-license
[organizationId]="organizationId"
(onDownloaded)="closeDownloadLicense()"
(onCanceled)="closeDownloadLicense()"
></app-download-license>
</div>
</ng-container>
<ng-container *ngIf="selfHosted">
<dl>
<dt>{{ "billingPlan" | i18n }}</dt>
<dd>{{ sub.plan.name }}</dd>
<dt>{{ "expiration" | i18n }}</dt>
<dd *ngIf="sub.expiration">
{{ sub.expiration | date: "mediumDate" }}
<span *ngIf="isExpired" class="text-danger ml-2">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
{{ "licenseIsExpired" | i18n }}
</span>
</dd>
<dd *ngIf="!sub.expiration">{{ "neverExpires" | i18n }}</dd>
</dl>
<div>
<button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
{{ "updateLicense" | i18n }}
</button>
<a
href="https://vault.bitwarden.com"
target="_blank"
rel="noopener"
class="btn btn-outline-secondary"
>
{{ "manageSubscription" | i18n }}
</a>
</div>
<div class="card mt-3" *ngIf="showUpdateLicense">
<div class="card-body">
<button
type="button"
class="close"
appA11yTitle="{{ 'cancel' | i18n }}"
(click)="closeUpdateLicense(false)"
>
<span aria-hidden="true">&times;</span>
</button>
<h3 class="card-body-header">{{ "updateLicense" | i18n }}</h3>
<app-update-license
[organizationId]="organizationId"
(onUpdated)="closeUpdateLicense(true)"
(onCanceled)="closeUpdateLicense(false)"
></app-update-license>
</div>
</div>
</ng-container>
</ng-container>

View File

@@ -1,260 +1,294 @@
import {
Component,
OnInit,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { Organization } from 'jslib-common/models/domain/organization';
import { OrganizationSubscriptionResponse } from 'jslib-common/models/response/organizationSubscriptionResponse';
import { Organization } from "jslib-common/models/domain/organization";
import { OrganizationSubscriptionResponse } from "jslib-common/models/response/organizationSubscriptionResponse";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PlanType } from 'jslib-common/enums/planType';
import { PlanType } from "jslib-common/enums/planType";
@Component({
selector: 'app-org-subscription',
templateUrl: 'organization-subscription.component.html',
selector: "app-org-subscription",
templateUrl: "organization-subscription.component.html",
})
export class OrganizationSubscriptionComponent implements OnInit {
loading = false;
firstLoaded = false;
organizationId: string;
adjustSeatsAdd = true;
showAdjustSeats = false;
showAdjustSeatAutoscale = false;
adjustStorageAdd = true;
showAdjustStorage = false;
showUpdateLicense = false;
showDownloadLicense = false;
showChangePlan = false;
sub: OrganizationSubscriptionResponse;
selfHosted = false;
loading = false;
firstLoaded = false;
organizationId: string;
adjustSeatsAdd = true;
showAdjustSeats = false;
showAdjustSeatAutoscale = false;
adjustStorageAdd = true;
showAdjustStorage = false;
showUpdateLicense = false;
showDownloadLicense = false;
showChangePlan = false;
sub: OrganizationSubscriptionResponse;
selfHosted = false;
userOrg: Organization;
userOrg: Organization;
removeSponsorshipPromise: Promise<any>;
cancelPromise: Promise<any>;
reinstatePromise: Promise<any>;
removeSponsorshipPromise: Promise<any>;
cancelPromise: Promise<any>;
reinstatePromise: Promise<any>;
constructor(private apiService: ApiService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private messagingService: MessagingService, private route: ActivatedRoute,
private organizationService: OrganizationService, private logService: LogService) {
this.selfHosted = platformUtilsService.isSelfHost();
constructor(
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private messagingService: MessagingService,
private route: ActivatedRoute,
private organizationService: OrganizationService,
private logService: LogService
) {
this.selfHosted = platformUtilsService.isSelfHost();
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await this.load();
this.firstLoaded = true;
});
}
async load() {
if (this.loading) {
return;
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
await this.load();
this.firstLoaded = true;
});
this.loading = true;
this.userOrg = await this.organizationService.get(this.organizationId);
this.sub = await this.apiService.getOrganizationSubscription(this.organizationId);
this.loading = false;
}
async reinstate() {
if (this.loading) {
return;
}
async load() {
if (this.loading) {
return;
}
this.loading = true;
this.userOrg = await this.organizationService.get(this.organizationId);
this.sub = await this.apiService.getOrganizationSubscription(this.organizationId);
this.loading = false;
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("reinstateConfirmation"),
this.i18nService.t("reinstateSubscription"),
this.i18nService.t("yes"),
this.i18nService.t("cancel")
);
if (!confirmed) {
return;
}
async reinstate() {
if (this.loading) {
return;
}
try {
this.reinstatePromise = this.apiService.postOrganizationReinstate(this.organizationId);
await this.reinstatePromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("reinstated"));
this.load();
} catch (e) {
this.logService.error(e);
}
}
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('reinstateConfirmation'),
this.i18nService.t('reinstateSubscription'), this.i18nService.t('yes'), this.i18nService.t('cancel'));
if (!confirmed) {
return;
}
try {
this.reinstatePromise = this.apiService.postOrganizationReinstate(this.organizationId);
await this.reinstatePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('reinstated'));
this.load();
} catch (e) {
this.logService.error(e);
}
async cancel() {
if (this.loading) {
return;
}
async cancel() {
if (this.loading) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('cancelConfirmation'),
this.i18nService.t('cancelSubscription'), this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return;
}
try {
this.cancelPromise = this.apiService.postOrganizationCancel(this.organizationId);
await this.cancelPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('canceledSubscription'));
this.load();
} catch (e) {
this.logService.error(e);
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("cancelConfirmation"),
this.i18nService.t("cancelSubscription"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return;
}
async changePlan() {
this.showChangePlan = !this.showChangePlan;
try {
this.cancelPromise = this.apiService.postOrganizationCancel(this.organizationId);
await this.cancelPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("canceledSubscription")
);
this.load();
} catch (e) {
this.logService.error(e);
}
}
async changePlan() {
this.showChangePlan = !this.showChangePlan;
}
closeChangePlan(changed: boolean) {
this.showChangePlan = false;
}
downloadLicense() {
this.showDownloadLicense = !this.showDownloadLicense;
}
closeDownloadLicense() {
this.showDownloadLicense = false;
}
updateLicense() {
if (this.loading) {
return;
}
this.showUpdateLicense = true;
}
closeUpdateLicense(updated: boolean) {
this.showUpdateLicense = false;
if (updated) {
this.load();
this.messagingService.send("updatedOrgLicense");
}
}
subscriptionAdjusted() {
this.load();
}
adjustStorage(add: boolean) {
this.adjustStorageAdd = add;
this.showAdjustStorage = true;
}
closeStorage(load: boolean) {
this.showAdjustStorage = false;
if (load) {
this.load();
}
}
async removeSponsorship() {
const isConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("removeSponsorshipConfirmation"),
this.i18nService.t("removeSponsorship"),
this.i18nService.t("remove"),
this.i18nService.t("cancel"),
"warning"
);
if (!isConfirmed) {
return;
}
closeChangePlan(changed: boolean) {
this.showChangePlan = false;
try {
this.removeSponsorshipPromise = this.apiService.deleteRemoveSponsorship(this.organizationId);
await this.removeSponsorshipPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("removeSponsorshipSuccess")
);
await this.load();
} catch (e) {
this.logService.error(e);
}
}
downloadLicense() {
this.showDownloadLicense = !this.showDownloadLicense;
get isExpired() {
return (
this.sub != null && this.sub.expiration != null && new Date(this.sub.expiration) < new Date()
);
}
get subscriptionMarkedForCancel() {
return (
this.subscription != null && !this.subscription.cancelled && this.subscription.cancelAtEndDate
);
}
get subscription() {
return this.sub != null ? this.sub.subscription : null;
}
get nextInvoice() {
return this.sub != null ? this.sub.upcomingInvoice : null;
}
get storagePercentage() {
return this.sub != null && this.sub.maxStorageGb
? +(100 * (this.sub.storageGb / this.sub.maxStorageGb)).toFixed(2)
: 0;
}
get storageProgressWidth() {
return this.storagePercentage < 5 ? 5 : 0;
}
get billingInterval() {
const monthly = !this.sub.plan.isAnnual;
return monthly ? "month" : "year";
}
get storageGbPrice() {
return this.sub.plan.additionalStoragePricePerGb;
}
get seatPrice() {
return this.sub.plan.seatPrice;
}
get seats() {
return this.sub.seats;
}
get maxAutoscaleSeats() {
return this.sub.maxAutoscaleSeats;
}
get canAdjustSeats() {
return this.sub.plan.hasAdditionalSeatsOption;
}
get isSponsoredSubscription(): boolean {
return this.sub.subscription?.items.some((i) => i.sponsoredSubscriptionItem);
}
get canDownloadLicense() {
return (
(this.sub.planType !== PlanType.Free && this.subscription == null) ||
(this.subscription != null && !this.subscription.cancelled)
);
}
get subscriptionDesc() {
if (this.sub.planType === PlanType.Free) {
return this.i18nService.t("subscriptionFreePlan", this.sub.seats.toString());
} else if (
this.sub.planType === PlanType.FamiliesAnnually ||
this.sub.planType === PlanType.FamiliesAnnually2019
) {
if (this.isSponsoredSubscription) {
return this.i18nService.t("subscriptionSponsoredFamiliesPlan", this.sub.seats.toString());
} else {
return this.i18nService.t("subscriptionFamiliesPlan", this.sub.seats.toString());
}
} else if (this.sub.maxAutoscaleSeats === this.sub.seats && this.sub.seats != null) {
return this.i18nService.t("subscriptionMaxReached", this.sub.seats.toString());
} else if (this.sub.maxAutoscaleSeats == null) {
return this.i18nService.t("subscriptionUserSeatsUnlimitedAutoscale");
} else {
return this.i18nService.t(
"subscriptionUserSeatsLimitedAutoscale",
this.sub.maxAutoscaleSeats.toString()
);
}
}
closeDownloadLicense() {
this.showDownloadLicense = false;
}
updateLicense() {
if (this.loading) {
return;
}
this.showUpdateLicense = true;
}
closeUpdateLicense(updated: boolean) {
this.showUpdateLicense = false;
if (updated) {
this.load();
this.messagingService.send('updatedOrgLicense');
}
}
subscriptionAdjusted() {
this.load();
}
adjustStorage(add: boolean) {
this.adjustStorageAdd = add;
this.showAdjustStorage = true;
}
closeStorage(load: boolean) {
this.showAdjustStorage = false;
if (load) {
this.load();
}
}
async removeSponsorship() {
const isConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('removeSponsorshipConfirmation'),
this.i18nService.t('removeSponsorship'),
this.i18nService.t('remove'), this.i18nService.t('cancel'), 'warning');
if (!isConfirmed) {
return;
}
try {
this.removeSponsorshipPromise = this.apiService.deleteRemoveSponsorship(this.organizationId);
await this.removeSponsorshipPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('removeSponsorshipSuccess'));
await this.load();
} catch (e) {
this.logService.error(e);
}
}
get isExpired() {
return this.sub != null && this.sub.expiration != null &&
new Date(this.sub.expiration) < new Date();
}
get subscriptionMarkedForCancel() {
return this.subscription != null && !this.subscription.cancelled && this.subscription.cancelAtEndDate;
}
get subscription() {
return this.sub != null ? this.sub.subscription : null;
}
get nextInvoice() {
return this.sub != null ? this.sub.upcomingInvoice : null;
}
get storagePercentage() {
return this.sub != null && this.sub.maxStorageGb ?
+(100 * (this.sub.storageGb / this.sub.maxStorageGb)).toFixed(2) : 0;
}
get storageProgressWidth() {
return this.storagePercentage < 5 ? 5 : 0;
}
get billingInterval() {
const monthly = !this.sub.plan.isAnnual;
return monthly ? 'month' : 'year';
}
get storageGbPrice() {
return this.sub.plan.additionalStoragePricePerGb;
}
get seatPrice() {
return this.sub.plan.seatPrice;
}
get seats() {
return this.sub.seats;
}
get maxAutoscaleSeats() {
return this.sub.maxAutoscaleSeats;
}
get canAdjustSeats() {
return this.sub.plan.hasAdditionalSeatsOption;
}
get isSponsoredSubscription(): boolean {
return this.sub.subscription?.items.some(i => i.sponsoredSubscriptionItem);
}
get canDownloadLicense() {
return (this.sub.planType !== PlanType.Free && this.subscription == null) ||
(this.subscription != null && !this.subscription.cancelled);
}
get subscriptionDesc() {
if (this.sub.planType === PlanType.Free) {
return this.i18nService.t('subscriptionFreePlan', this.sub.seats.toString());
} else if (this.sub.planType === PlanType.FamiliesAnnually || this.sub.planType === PlanType.FamiliesAnnually2019) {
if (this.isSponsoredSubscription) {
return this.i18nService.t('subscriptionSponsoredFamiliesPlan', this.sub.seats.toString());
} else {
return this.i18nService.t('subscriptionFamiliesPlan', this.sub.seats.toString());
}
} else if (this.sub.maxAutoscaleSeats === this.sub.seats && this.sub.seats != null) {
return this.i18nService.t('subscriptionMaxReached', this.sub.seats.toString());
} else if (this.sub.maxAutoscaleSeats == null) {
return this.i18nService.t('subscriptionUserSeatsUnlimitedAutoscale');
} else {
return this.i18nService.t('subscriptionUserSeatsLimitedAutoscale', this.sub.maxAutoscaleSeats.toString());
}
}
get showChangePlanButton() {
return this.subscription == null && this.sub.planType === PlanType.Free && !this.showChangePlan;
}
get showChangePlanButton() {
return this.subscription == null && this.sub.planType === PlanType.Free && !this.showChangePlan;
}
}

View File

@@ -1,26 +1,36 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<div class="card">
<div class="card-header">{{'settings' | i18n}}</div>
<div class="list-group list-group-flush">
<a routerLink="account" class="list-group-item" routerLinkActive="active">
{{'myOrganization' | i18n}}
</a>
<a routerLink="subscription" class="list-group-item" routerLinkActive="active">
{{'subscription' | i18n}}
</a>
<a routerLink="billing" class="list-group-item" routerLinkActive="active" *ngIf="!selfHosted">
{{'billing' | i18n}}
</a>
<a routerLink="two-factor" class="list-group-item" routerLinkActive="active" *ngIf="access2fa">
{{'twoStepLogin' | i18n}}
</a>
</div>
</div>
</div>
<div class="col-9">
<router-outlet></router-outlet>
<div class="row">
<div class="col-3">
<div class="card">
<div class="card-header">{{ "settings" | i18n }}</div>
<div class="list-group list-group-flush">
<a routerLink="account" class="list-group-item" routerLinkActive="active">
{{ "myOrganization" | i18n }}
</a>
<a routerLink="subscription" class="list-group-item" routerLinkActive="active">
{{ "subscription" | i18n }}
</a>
<a
routerLink="billing"
class="list-group-item"
routerLinkActive="active"
*ngIf="!selfHosted"
>
{{ "billing" | i18n }}
</a>
<a
routerLink="two-factor"
class="list-group-item"
routerLinkActive="active"
*ngIf="access2fa"
>
{{ "twoStepLogin" | i18n }}
</a>
</div>
</div>
</div>
<div class="col-9">
<router-outlet></router-outlet>
</div>
</div>
</div>

View File

@@ -1,25 +1,28 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Component({
selector: 'app-org-settings',
templateUrl: 'settings.component.html',
selector: "app-org-settings",
templateUrl: "settings.component.html",
})
export class SettingsComponent {
access2fa = false;
selfHosted: boolean;
access2fa = false;
selfHosted: boolean;
constructor(private route: ActivatedRoute, private organizationService: OrganizationService,
private platformUtilsService: PlatformUtilsService) { }
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
private platformUtilsService: PlatformUtilsService
) {}
ngOnInit() {
this.route.parent.params.subscribe(async params => {
this.selfHosted = await this.platformUtilsService.isSelfHost();
const organization = await this.organizationService.get(params.organizationId);
this.access2fa = organization.use2fa;
});
}
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
this.selfHosted = await this.platformUtilsService.isSelfHost();
const organization = await this.organizationService.get(params.organizationId);
this.access2fa = organization.use2fa;
});
}
}

View File

@@ -1,56 +1,61 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { TwoFactorDuoComponent } from '../../settings/two-factor-duo.component';
import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from '../../settings/two-factor-setup.component';
import { TwoFactorDuoComponent } from "../../settings/two-factor-duo.component";
import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from "../../settings/two-factor-setup.component";
@Component({
selector: 'app-two-factor-setup',
templateUrl: '../../settings/two-factor-setup.component.html',
selector: "app-two-factor-setup",
templateUrl: "../../settings/two-factor-setup.component.html",
})
export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
constructor(apiService: ApiService,
modalService: ModalService, messagingService: MessagingService,
policyService: PolicyService, private route: ActivatedRoute, stateService: StateService) {
super(apiService, modalService, messagingService, policyService, stateService);
}
constructor(
apiService: ApiService,
modalService: ModalService,
messagingService: MessagingService,
policyService: PolicyService,
private route: ActivatedRoute,
stateService: StateService
) {
super(apiService, modalService, messagingService, policyService, stateService);
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
await super.ngOnInit();
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await super.ngOnInit();
});
}
async manage(type: TwoFactorProviderType) {
switch (type) {
case TwoFactorProviderType.OrganizationDuo:
const duoComp = await this.openModal(this.duoModalRef, TwoFactorDuoComponent);
duoComp.type = TwoFactorProviderType.OrganizationDuo;
duoComp.organizationId = this.organizationId;
duoComp.onUpdated.subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.OrganizationDuo);
});
break;
default:
break;
}
}
async manage(type: TwoFactorProviderType) {
switch (type) {
case TwoFactorProviderType.OrganizationDuo:
const duoComp = await this.openModal(this.duoModalRef, TwoFactorDuoComponent);
duoComp.type = TwoFactorProviderType.OrganizationDuo;
duoComp.organizationId = this.organizationId;
duoComp.onUpdated.subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.OrganizationDuo);
});
break;
default:
break;
}
}
protected getTwoFactorProviders() {
return this.apiService.getTwoFactorOrganizationProviders(this.organizationId);
}
protected getTwoFactorProviders() {
return this.apiService.getTwoFactorOrganizationProviders(this.organizationId);
}
protected filterProvider(type: TwoFactorProviderType) {
return type !== TwoFactorProviderType.OrganizationDuo;
}
protected filterProvider(type: TwoFactorProviderType) {
return type !== TwoFactorProviderType.OrganizationDuo;
}
}