mirror of
https://github.com/bitwarden/web
synced 2026-01-10 04:23:28 +00:00
Apply Prettier (#1347)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}} × {{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}} ×
|
||||
{{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 }} ×
|
||||
{{ 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 }} ×
|
||||
{{ 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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">×</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">×</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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">×</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">×</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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">×</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">×</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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ? '×' + 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">×</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 ? "×" + 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">×</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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user