my organization page
This commit is contained in:
parent
a1d52af0ba
commit
786f6953e7
2
jslib
2
jslib
|
@ -1 +1 @@
|
|||
Subproject commit 76ece834d1d18e9cca71bb3c182d2284dae80958
|
||||
Subproject commit b2c700ad285d9336426284f766b434be8c509f0a
|
|
@ -25,6 +25,9 @@ import { GroupsComponent as OrgGroupsComponent } from './organizations/manage/gr
|
|||
import { ManageComponent as OrgManageComponent } from './organizations/manage/manage.component';
|
||||
import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/people.component';
|
||||
|
||||
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
|
||||
import { SettingsComponent as OrgSettingsComponent } from './organizations/settings/settings.component';
|
||||
|
||||
import { ExportComponent as OrgExportComponent } from './organizations/tools/export.component';
|
||||
import { ImportComponent as OrgImportComponent } from './organizations/tools/import.component';
|
||||
import { ToolsComponent as OrgToolsComponent } from './organizations/tools/tools.component';
|
||||
|
@ -176,6 +179,16 @@ const routes: Routes = [
|
|||
{ path: 'people', component: OrgPeopleComponent, data: { titleId: 'people' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
component: OrgSettingsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: { allowedTypes: [OrganizationUserType.Owner, OrganizationUserType.Admin] },
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'account' },
|
||||
{ path: 'account', component: OrgAccountComponent, data: { titleId: 'myOrganization' } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ path: '**', redirectTo: '' },
|
||||
|
|
|
@ -51,6 +51,10 @@ import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/pe
|
|||
import { UserAddEditComponent as OrgUserAddEditComponent } from './organizations/manage/user-add-edit.component';
|
||||
import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/manage/user-groups.component';
|
||||
|
||||
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
|
||||
import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
|
||||
import { SettingsComponent as OrgSettingComponent } from './organizations/settings/settings.component';
|
||||
|
||||
import { ExportComponent as OrgExportComponent } from './organizations/tools/export.component';
|
||||
import { ImportComponent as OrgImportComponent } from './organizations/tools/import.component';
|
||||
import { ToolsComponent as OrgToolsComponent } from './organizations/tools/tools.component';
|
||||
|
@ -164,6 +168,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
|
|||
CreateOrganizationComponent,
|
||||
DeauthorizeSessionsComponent,
|
||||
DeleteAccountComponent,
|
||||
DeleteOrganizationComponent,
|
||||
DomainRulesComponent,
|
||||
ExportComponent,
|
||||
FallbackSrcDirective,
|
||||
|
@ -181,6 +186,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
|
|||
ModalComponent,
|
||||
NavbarComponent,
|
||||
OptionsComponent,
|
||||
OrgAccountComponent,
|
||||
OrgAddEditComponent,
|
||||
OrgAttachmentsComponent,
|
||||
OrgCiphersComponent,
|
||||
|
@ -197,6 +203,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
|
|||
OrgManageCollectionsComponent,
|
||||
OrgManageComponent,
|
||||
OrgPeopleComponent,
|
||||
OrgSettingComponent,
|
||||
OrgToolsComponent,
|
||||
OrgUserAddEditComponent,
|
||||
OrgUserGroupsComponent,
|
||||
|
@ -247,6 +254,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
|
|||
CollectionsComponent,
|
||||
DeauthorizeSessionsComponent,
|
||||
DeleteAccountComponent,
|
||||
DeleteOrganizationComponent,
|
||||
FolderAddEditComponent,
|
||||
ModalComponent,
|
||||
OrgAddEditComponent,
|
||||
|
|
|
@ -8,20 +8,20 @@
|
|||
<small class="text-muted">{{'organization' | i18n}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-tabs">
|
||||
<ul class="nav nav-tabs" *ngIf="organization.isAdmin">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="vault" routerLinkActive="active">
|
||||
<i class="fa fa-lock"></i>
|
||||
{{'vault' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="organization.isAdmin">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="manage" routerLinkActive="active">
|
||||
<i class="fa fa-sliders"></i>
|
||||
{{'manage' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="organization.isAdmin">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="tools" routerLinkActive="active">
|
||||
<i class="fa fa-wrench"></i>
|
||||
{{'tools' | i18n}}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<div class="page-header">
|
||||
<h1>{{'myOrganization' | i18n}}</h1>
|
||||
</div>
|
||||
<div *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted"></i>
|
||||
</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">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="billingEmail">{{'billingEmail' | i18n}}</label>
|
||||
<input id="billingEmail" class="form-control" type="text" name="BillingEmail" [(ngModel)]="org.billingEmail">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="businessName">{{'businessName' | i18n}}</label>
|
||||
<input id="businessName" class="form-control" type="text" name="BusinessName" [(ngModel)]="org.businessName">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<app-avatar data="{{org.name}}" dynamic="true" width="75" height="75" fontSize="35"></app-avatar>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" appBlurClick [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
</form>
|
||||
<div class="secondary-header border-0 mb-0">
|
||||
<h1>{{'taxInformation' | i18n}}</h1>
|
||||
</div>
|
||||
<div class="mb-3" *ngIf="org && (org.businessAddress1 || org.businessTaxNumber)">
|
||||
<div>{{org.businessAddress1}}</div>
|
||||
<div>{{org.businessAddress2}}</div>
|
||||
<div>{{org.businessAddress3}}</div>
|
||||
<div>{{org.businessCountry}}</div>
|
||||
<div>{{org.businessTaxNumber}}</div>
|
||||
</div>
|
||||
<p>{{'taxInformationDesc' | i18n}}</p>
|
||||
<a href="https://bitwarden.com/contact/" target="_blank" rel="noopener" class="btn btn-outline-secondary">
|
||||
{{'contactSupport' | i18n}}
|
||||
</a>
|
||||
<div class="secondary-header text-danger border-0 mb-0">
|
||||
<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()" appBlurClick>{{'deleteOrganization' | i18n}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #deleteOrganizationTemplate></ng-template>
|
|
@ -0,0 +1,81 @@
|
|||
import {
|
||||
Component,
|
||||
ComponentFactoryResolver,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||
|
||||
import { OrganizationUpdateRequest } from 'jslib/models/request/organizationUpdateRequest';
|
||||
import { OrganizationResponse } from 'jslib/models/response/organizationResponse';
|
||||
|
||||
import { ModalComponent } from '../../modal.component';
|
||||
import { DeleteOrganizationComponent } from './delete-organization.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-org-account',
|
||||
templateUrl: 'account.component.html',
|
||||
})
|
||||
export class AccountComponent {
|
||||
@ViewChild('deleteOrganizationTemplate', { read: ViewContainerRef }) deleteModalRef: ViewContainerRef;
|
||||
|
||||
loading = true;
|
||||
org: OrganizationResponse;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
private organizationId: string;
|
||||
private modal: ModalComponent = null;
|
||||
|
||||
constructor(private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private route: ActivatedRoute, private syncService: SyncService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
try {
|
||||
this.org = await this.apiService.getOrganization(this.organizationId);
|
||||
} catch { }
|
||||
});
|
||||
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;
|
||||
this.formPromise = this.apiService.putOrganization(this.organizationId, request).then(() => {
|
||||
return this.syncService.fullSync(true);
|
||||
});
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Updated Organization Settings' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('organizationUpdated'));
|
||||
} catch { }
|
||||
}
|
||||
|
||||
deleteOrganization() {
|
||||
if (this.modal != null) {
|
||||
this.modal.close();
|
||||
}
|
||||
|
||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||
this.modal = this.deleteModalRef.createComponent(factory).instance;
|
||||
const childComponent = this.modal.show<DeleteOrganizationComponent>(
|
||||
DeleteOrganizationComponent, this.deleteModalRef);
|
||||
childComponent.organizationId = this.organizationId;
|
||||
|
||||
this.modal.onClosed.subscribe(async () => {
|
||||
this.modal = null;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{'deleteOrganization' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'deleteOrganizationDesc' | i18n}}</p>
|
||||
<app-callout type="warning">{{'deleteOrganizationWarning' | i18n}}</app-callout>
|
||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
||||
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control" [(ngModel)]="masterPassword" required
|
||||
appAutofocus appInputVerbatim>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button appBlurClick type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
<span>{{'deleteOrganization' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,45 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
|
||||
import { PasswordVerificationRequest } from 'jslib/models/request/passwordVerificationRequest';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delete-organization',
|
||||
templateUrl: 'delete-organization.component.html',
|
||||
})
|
||||
export class DeleteOrganizationComponent {
|
||||
organizationId: string;
|
||||
|
||||
masterPassword: string;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private router: Router) { }
|
||||
|
||||
async submit() {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPassRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
const request = new PasswordVerificationRequest();
|
||||
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
|
||||
try {
|
||||
this.formPromise = this.apiService.postDeleteOrganization(this.organizationId, request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Deleted Organization' });
|
||||
this.toasterService.popAsync('success', this.i18nService.t('organizationDeleted'),
|
||||
this.i18nService.t('organizationDeletedDesc'));
|
||||
this.router.navigate(['/']);
|
||||
} catch { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<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="billing" class="list-group-item" routerLinkActive="active">
|
||||
{{'billingAndLicensing' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-org-settings',
|
||||
templateUrl: 'settings.component.html',
|
||||
})
|
||||
export class SettingsComponent { }
|
|
@ -11,7 +11,7 @@
|
|||
{{'options' | i18n}}
|
||||
</a>
|
||||
<a routerLink="organizations" class="list-group-item" routerLinkActive="active">
|
||||
Organizations
|
||||
{{'organizations' | i18n}}
|
||||
</a>
|
||||
<a routerLink="billing" class="list-group-item" routerLinkActive="active" *ngIf="premium">
|
||||
{{'billingAndLicensing' | i18n}}
|
||||
|
@ -23,7 +23,7 @@
|
|||
{{'twoStepLogin' | i18n}}
|
||||
</a>
|
||||
<a routerLink="domain-rules" class="list-group-item" routerLinkActive="active">
|
||||
Domain Rules
|
||||
{{'domainRules' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2143,5 +2143,32 @@
|
|||
},
|
||||
"deleteRecoverConfirmDesc": {
|
||||
"message": "You have requested to delete your Bitwarden account. Click the button below to confirm."
|
||||
},
|
||||
"myOrganization": {
|
||||
"message": "My Organization"
|
||||
},
|
||||
"deleteOrganization": {
|
||||
"message": "Delete Organization"
|
||||
},
|
||||
"deleteOrganizationDesc": {
|
||||
"message": "Proceed below to delete this organization and all associated data. Individual user accounts will remain, though they will not be associated to this organization anymore. "
|
||||
},
|
||||
"deleteOrganizationWarning": {
|
||||
"message": "Deleting the organization is permanent. It cannot be undone."
|
||||
},
|
||||
"organizationDeleted": {
|
||||
"message": "Organization Deleted"
|
||||
},
|
||||
"organizationDeletedDesc": {
|
||||
"message": "The organization and all associated data has been deleted."
|
||||
},
|
||||
"organizationUpdated": {
|
||||
"message": "Organization updated"
|
||||
},
|
||||
"taxInformation": {
|
||||
"message": "Tax Information"
|
||||
},
|
||||
"taxInformationDesc": {
|
||||
"message": "Please contact support to provide (or update) tax information for your invoices."
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue