mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-27 04:03:00 +02:00
[EC-646] Org Admin Vault Refresh November Release Prep (#3913)
* [EC-646] Remove links from Manage component
These links are no longer necessary as they are now located in the new OAVR tabs.
* [EC-646] Re-introduce the canAccessManageTab helper
* [EC-646] Re-introduce /manage route in Organization routing module
- Add the parent /manage route
- Add child routes for collections, people, and groups
* [EC-646] Adjust Org admin tabs
Re-introduce the Manage tab and remove Groups and Members tabs.
* [EC-646] Change Members title back to People
* [EC-646] Move missing billing components
Some billing components were in the org settings module and needed to be moved the org billing module
* [EC-646] Fix import file upload button
-Update to use click event handler and tailwind class to hide input. Avoids inline styles/js blocked by CSP
- Fix broken async pipe
* [EC-646] Fix groups and people page overflow
Remove the container and page-content wrapper as the pages are no longer on their own tab
* [EC-646] Change People to Members
Change the text regarding managing members from People to Members to more closely follow changes coming later in the OAVR. Also update the URL to use /manage/members
* [EC-646] Cherry-pick ae39afe
to fix tab text color
* [EC-646] Fix org routing permissions helpers
- Add canAccessVaultTab helper
- Update canAccessOrgAdmin include check for vault tab access
- Simplify canManageCollections
* [EC-646] Fix Manage tab conditional logic
- Add *ngIf condition for rendering Manage tab
- Re-introduce dynamic route for Manage tab
This commit is contained in:
parent
069baeefcd
commit
4b57d28e28
@ -1,9 +1,11 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
import { LooseComponentsModule } from "../../shared/loose-components.module";
|
import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||||
import { SharedModule } from "../../shared/shared.module";
|
|
||||||
|
|
||||||
|
import { AdjustSubscription } from "./adjust-subscription.component";
|
||||||
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
||||||
|
import { ChangePlanComponent } from "./change-plan.component";
|
||||||
|
import { DownloadLicenseComponent } from "./download-license.component";
|
||||||
import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component";
|
import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component";
|
||||||
import { OrganizationBillingRoutingModule } from "./organization-billing-routing.module";
|
import { OrganizationBillingRoutingModule } from "./organization-billing-routing.module";
|
||||||
import { OrganizationBillingTabComponent } from "./organization-billing-tab.component";
|
import { OrganizationBillingTabComponent } from "./organization-billing-tab.component";
|
||||||
@ -12,7 +14,10 @@ import { OrganizationSubscriptionComponent } from "./organization-subscription.c
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SharedModule, LooseComponentsModule, OrganizationBillingRoutingModule],
|
imports: [SharedModule, LooseComponentsModule, OrganizationBillingRoutingModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
AdjustSubscription,
|
||||||
BillingSyncApiKeyComponent,
|
BillingSyncApiKeyComponent,
|
||||||
|
ChangePlanComponent,
|
||||||
|
DownloadLicenseComponent,
|
||||||
OrganizationBillingTabComponent,
|
OrganizationBillingTabComponent,
|
||||||
OrganizationSubscriptionComponent,
|
OrganizationSubscriptionComponent,
|
||||||
OrgBillingHistoryViewComponent,
|
OrgBillingHistoryViewComponent,
|
||||||
|
@ -8,17 +8,15 @@
|
|||||||
></app-organization-switcher>
|
></app-organization-switcher>
|
||||||
<bit-tab-nav-bar class="-tw-mb-px">
|
<bit-tab-nav-bar class="-tw-mb-px">
|
||||||
<bit-tab-link route="vault">{{ "vault" | i18n }}</bit-tab-link>
|
<bit-tab-link route="vault">{{ "vault" | i18n }}</bit-tab-link>
|
||||||
<bit-tab-link *ngIf="canShowMembersTab(organization)" route="members">{{
|
<bit-tab-link *ngIf="canShowManageTab(organization)" [route]="getManageRoute(organization)">
|
||||||
"members" | i18n
|
{{ "manage" | i18n }}
|
||||||
}}</bit-tab-link>
|
</bit-tab-link>
|
||||||
<bit-tab-link *ngIf="canShowGroupsTab(organization)" route="groups">{{
|
|
||||||
"groups" | i18n
|
|
||||||
}}</bit-tab-link>
|
|
||||||
<bit-tab-link
|
<bit-tab-link
|
||||||
*ngIf="canShowReportsTab(organization)"
|
*ngIf="canShowReportsTab(organization)"
|
||||||
[route]="getReportRoute(organization)"
|
[route]="getReportRoute(organization)"
|
||||||
>{{ getReportTabLabel(organization) | i18n }}</bit-tab-link
|
|
||||||
>
|
>
|
||||||
|
{{ getReportTabLabel(organization) | i18n }}
|
||||||
|
</bit-tab-link>
|
||||||
<bit-tab-link *ngIf="canShowBillingTab(organization)" route="billing">{{
|
<bit-tab-link *ngIf="canShowBillingTab(organization)" route="billing">{{
|
||||||
"billing" | i18n
|
"billing" | i18n
|
||||||
}}</bit-tab-link>
|
}}</bit-tab-link>
|
||||||
|
@ -5,6 +5,7 @@ import { map, mergeMap, Observable, Subject, takeUntil } from "rxjs";
|
|||||||
import {
|
import {
|
||||||
canAccessBillingTab,
|
canAccessBillingTab,
|
||||||
canAccessGroupsTab,
|
canAccessGroupsTab,
|
||||||
|
canAccessManageTab,
|
||||||
canAccessMembersTab,
|
canAccessMembersTab,
|
||||||
canAccessReportingTab,
|
canAccessReportingTab,
|
||||||
canAccessSettingsTab,
|
canAccessSettingsTab,
|
||||||
@ -48,6 +49,10 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
|||||||
return canAccessSettingsTab(organization);
|
return canAccessSettingsTab(organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canShowManageTab(organization: Organization): boolean {
|
||||||
|
return canAccessManageTab(organization);
|
||||||
|
}
|
||||||
|
|
||||||
canShowMembersTab(organization: Organization): boolean {
|
canShowMembersTab(organization: Organization): boolean {
|
||||||
return canAccessMembersTab(organization);
|
return canAccessMembersTab(organization);
|
||||||
}
|
}
|
||||||
@ -71,4 +76,20 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
|||||||
getReportRoute(organization: Organization): string {
|
getReportRoute(organization: Organization): string {
|
||||||
return organization.useEvents ? "reporting/events" : "reporting/reports";
|
return organization.useEvents ? "reporting/events" : "reporting/reports";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getManageRoute(organization: Organization): string {
|
||||||
|
let route: string;
|
||||||
|
switch (true) {
|
||||||
|
case organization.canManageUsers:
|
||||||
|
route = "manage/members";
|
||||||
|
break;
|
||||||
|
case organization.canViewAssignedCollections || organization.canViewAllCollections:
|
||||||
|
route = "manage/collections";
|
||||||
|
break;
|
||||||
|
case organization.canManageGroups:
|
||||||
|
route = "manage/groups";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return route;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,79 +1,77 @@
|
|||||||
<div class="container page-content">
|
<div class="page-header d-flex">
|
||||||
<div class="page-header d-flex">
|
<h1>{{ "groups" | i18n }}</h1>
|
||||||
<h1>{{ "groups" | i18n }}</h1>
|
<div class="ml-auto d-flex">
|
||||||
<div class="ml-auto d-flex">
|
<div>
|
||||||
<div>
|
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
|
||||||
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
|
<input
|
||||||
<input
|
type="search"
|
||||||
type="search"
|
class="form-control form-control-sm"
|
||||||
class="form-control form-control-sm"
|
id="search"
|
||||||
id="search"
|
placeholder="{{ 'search' | i18n }}"
|
||||||
placeholder="{{ 'search' | i18n }}"
|
[(ngModel)]="searchText"
|
||||||
[(ngModel)]="searchText"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="add()">
|
|
||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
|
||||||
{{ "newGroup" | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="add()">
|
||||||
|
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||||
|
{{ "newGroup" | i18n }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="loading">
|
|
||||||
<i
|
|
||||||
class="bwi bwi-spinner bwi-spin text-muted"
|
|
||||||
title="{{ 'loading' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container
|
|
||||||
*ngIf="
|
|
||||||
!loading &&
|
|
||||||
(isPaging() ? pagedGroups : (groups | search: searchText:'name':'id')) as searchedGroups
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p *ngIf="!searchedGroups.length">{{ "noGroupsInList" | i18n }}</p>
|
|
||||||
<table
|
|
||||||
class="table table-hover table-list"
|
|
||||||
*ngIf="searchedGroups.length"
|
|
||||||
infiniteScroll
|
|
||||||
[infiniteScrollDistance]="1"
|
|
||||||
[infiniteScrollDisabled]="!isPaging()"
|
|
||||||
(scrolled)="loadMore()"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let g of searchedGroups">
|
|
||||||
<td>
|
|
||||||
<a href="#" appStopClick (click)="edit(g)">{{ g.name }}</a>
|
|
||||||
</td>
|
|
||||||
<td class="table-list-options">
|
|
||||||
<div class="dropdown" appListDropdown>
|
|
||||||
<button
|
|
||||||
class="btn btn-outline-secondary dropdown-toggle"
|
|
||||||
type="button"
|
|
||||||
data-toggle="dropdown"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="false"
|
|
||||||
appA11yTitle="{{ 'options' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
|
||||||
<a class="dropdown-item" href="#" appStopClick (click)="users(g)">
|
|
||||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
|
||||||
{{ "users" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(g)">
|
|
||||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
|
||||||
{{ "delete" | i18n }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</ng-container>
|
|
||||||
<ng-template #addEdit></ng-template>
|
|
||||||
<ng-template #usersTemplate></ng-template>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-spinner bwi-spin text-muted"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container
|
||||||
|
*ngIf="
|
||||||
|
!loading &&
|
||||||
|
(isPaging() ? pagedGroups : (groups | search: searchText:'name':'id')) as searchedGroups
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<p *ngIf="!searchedGroups.length">{{ "noGroupsInList" | i18n }}</p>
|
||||||
|
<table
|
||||||
|
class="table table-hover table-list"
|
||||||
|
*ngIf="searchedGroups.length"
|
||||||
|
infiniteScroll
|
||||||
|
[infiniteScrollDistance]="1"
|
||||||
|
[infiniteScrollDisabled]="!isPaging()"
|
||||||
|
(scrolled)="loadMore()"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let g of searchedGroups">
|
||||||
|
<td>
|
||||||
|
<a href="#" appStopClick (click)="edit(g)">{{ g.name }}</a>
|
||||||
|
</td>
|
||||||
|
<td class="table-list-options">
|
||||||
|
<div class="dropdown" appListDropdown>
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary dropdown-toggle"
|
||||||
|
type="button"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="false"
|
||||||
|
appA11yTitle="{{ 'options' | i18n }}"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="#" appStopClick (click)="users(g)">
|
||||||
|
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||||
|
{{ "users" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(g)">
|
||||||
|
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||||
|
{{ "delete" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #addEdit></ng-template>
|
||||||
|
<ng-template #usersTemplate></ng-template>
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
<div class="card-header">{{ "manage" | i18n }}</div>
|
<div class="card-header">{{ "manage" | i18n }}</div>
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
<a
|
<a
|
||||||
routerLink="people"
|
routerLink="members"
|
||||||
class="list-group-item"
|
class="list-group-item"
|
||||||
routerLinkActive="active"
|
routerLinkActive="active"
|
||||||
*ngIf="organization.canManageUsers"
|
*ngIf="organization.canManageUsers"
|
||||||
>
|
>
|
||||||
{{ "people" | i18n }}
|
{{ "members" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
routerLink="collections"
|
routerLink="collections"
|
||||||
@ -28,38 +28,6 @@
|
|||||||
>
|
>
|
||||||
{{ "groups" | i18n }}
|
{{ "groups" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
<a
|
|
||||||
routerLink="policies"
|
|
||||||
class="list-group-item"
|
|
||||||
routerLinkActive="active"
|
|
||||||
*ngIf="organization.canManagePolicies"
|
|
||||||
>
|
|
||||||
{{ "policies" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
routerLink="sso"
|
|
||||||
class="list-group-item"
|
|
||||||
routerLinkActive="active"
|
|
||||||
*ngIf="organization.canManageSso"
|
|
||||||
>
|
|
||||||
{{ "singleSignOn" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
routerLink="scim"
|
|
||||||
class="list-group-item"
|
|
||||||
routerLinkActive="active"
|
|
||||||
*ngIf="organization.canManageScim"
|
|
||||||
>
|
|
||||||
{{ "scim" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
routerLink="events"
|
|
||||||
class="list-group-item"
|
|
||||||
routerLinkActive="active"
|
|
||||||
*ngIf="organization.canAccessEventLogs"
|
|
||||||
>
|
|
||||||
{{ "eventLogs" | i18n }}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,288 +1,286 @@
|
|||||||
<div class="container page-content">
|
<div class="page-header d-flex">
|
||||||
<div class="page-header d-flex">
|
<h1>{{ "members" | i18n }}</h1>
|
||||||
<h1>{{ "members" | i18n }}</h1>
|
<div class="ml-auto d-flex">
|
||||||
<div class="ml-auto d-flex">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="btn btn-outline-secondary"
|
||||||
class="btn btn-outline-secondary"
|
[ngClass]="{ active: status == null }"
|
||||||
[ngClass]="{ active: status == null }"
|
(click)="filter(null)"
|
||||||
(click)="filter(null)"
|
>
|
||||||
>
|
{{ "all" | i18n }}
|
||||||
{{ "all" | i18n }}
|
<span bitBadge badgeType="info" *ngIf="allCount">{{ allCount }}</span>
|
||||||
<span bitBadge badgeType="info" *ngIf="allCount">{{ allCount }}</span>
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="btn btn-outline-secondary"
|
||||||
class="btn btn-outline-secondary"
|
[ngClass]="{ active: status == userStatusType.Invited }"
|
||||||
[ngClass]="{ active: status == userStatusType.Invited }"
|
(click)="filter(userStatusType.Invited)"
|
||||||
(click)="filter(userStatusType.Invited)"
|
>
|
||||||
>
|
{{ "invited" | i18n }}
|
||||||
{{ "invited" | i18n }}
|
<span bitBadge badgeType="info" *ngIf="invitedCount">{{ invitedCount }}</span>
|
||||||
<span bitBadge badgeType="info" *ngIf="invitedCount">{{ invitedCount }}</span>
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="btn btn-outline-secondary"
|
||||||
class="btn btn-outline-secondary"
|
[ngClass]="{ active: status == userStatusType.Accepted }"
|
||||||
[ngClass]="{ active: status == userStatusType.Accepted }"
|
(click)="filter(userStatusType.Accepted)"
|
||||||
(click)="filter(userStatusType.Accepted)"
|
>
|
||||||
>
|
{{ "accepted" | i18n }}
|
||||||
{{ "accepted" | i18n }}
|
<span bitBadge badgeType="warning" *ngIf="acceptedCount">{{ acceptedCount }}</span>
|
||||||
<span bitBadge badgeType="warning" *ngIf="acceptedCount">{{ acceptedCount }}</span>
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="btn btn-outline-secondary"
|
||||||
class="btn btn-outline-secondary"
|
[ngClass]="{ active: status == userStatusType.Revoked }"
|
||||||
[ngClass]="{ active: status == userStatusType.Revoked }"
|
(click)="filter(userStatusType.Revoked)"
|
||||||
(click)="filter(userStatusType.Revoked)"
|
>
|
||||||
>
|
{{ "revoked" | i18n }}
|
||||||
{{ "revoked" | i18n }}
|
<span bitBadge badgeType="info" *ngIf="revokedCount">{{ revokedCount }}</span>
|
||||||
<span bitBadge badgeType="info" *ngIf="revokedCount">{{ revokedCount }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
type="search"
|
|
||||||
class="form-control form-control-sm"
|
|
||||||
id="search"
|
|
||||||
placeholder="{{ 'search' | i18n }}"
|
|
||||||
[(ngModel)]="searchText"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown ml-3" appListDropdown>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-outline-secondary dropdown-toggle"
|
|
||||||
type="button"
|
|
||||||
id="bulkActionsButton"
|
|
||||||
data-toggle="dropdown"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="false"
|
|
||||||
appA11yTitle="{{ 'options' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-cog" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
|
|
||||||
<button class="dropdown-item" appStopClick (click)="bulkReinvite()">
|
|
||||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
|
||||||
{{ "reinviteSelected" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="dropdown-item text-success"
|
|
||||||
appStopClick
|
|
||||||
(click)="bulkConfirm()"
|
|
||||||
*ngIf="showBulkConfirmUsers"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
|
||||||
{{ "confirmSelected" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button class="dropdown-item" appStopClick (click)="bulkRestore()">
|
|
||||||
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
|
||||||
{{ "restoreAccess" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button class="dropdown-item" appStopClick (click)="bulkRevoke()">
|
|
||||||
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
|
||||||
{{ "revokeAccess" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
|
|
||||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
|
||||||
{{ "remove" | i18n }}
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
|
|
||||||
<i class="bwi bwi-fw bwi-check-square" aria-hidden="true"></i>
|
|
||||||
{{ "selectAll" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
|
|
||||||
<i class="bwi bwi-fw bwi-minus-square" aria-hidden="true"></i>
|
|
||||||
{{ "unselectAll" | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
|
|
||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
|
||||||
{{ "inviteUser" | i18n }}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
class="form-control form-control-sm"
|
||||||
|
id="search"
|
||||||
|
placeholder="{{ 'search' | i18n }}"
|
||||||
|
[(ngModel)]="searchText"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown ml-3" appListDropdown>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-outline-secondary dropdown-toggle"
|
||||||
|
type="button"
|
||||||
|
id="bulkActionsButton"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="false"
|
||||||
|
appA11yTitle="{{ 'options' | i18n }}"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-cog" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
|
||||||
|
<button class="dropdown-item" appStopClick (click)="bulkReinvite()">
|
||||||
|
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||||
|
{{ "reinviteSelected" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="dropdown-item text-success"
|
||||||
|
appStopClick
|
||||||
|
(click)="bulkConfirm()"
|
||||||
|
*ngIf="showBulkConfirmUsers"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||||
|
{{ "confirmSelected" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button class="dropdown-item" appStopClick (click)="bulkRestore()">
|
||||||
|
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
||||||
|
{{ "restoreAccess" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button class="dropdown-item" appStopClick (click)="bulkRevoke()">
|
||||||
|
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
||||||
|
{{ "revokeAccess" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
|
||||||
|
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||||
|
{{ "remove" | i18n }}
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
|
||||||
|
<i class="bwi bwi-fw bwi-check-square" aria-hidden="true"></i>
|
||||||
|
{{ "selectAll" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
|
||||||
|
<i class="bwi bwi-fw bwi-minus-square" aria-hidden="true"></i>
|
||||||
|
{{ "unselectAll" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
|
||||||
|
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||||
|
{{ "inviteUser" | i18n }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="loading">
|
|
||||||
<i
|
|
||||||
class="bwi bwi-spinner bwi-spin text-muted"
|
|
||||||
title="{{ 'loading' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container
|
|
||||||
*ngIf="
|
|
||||||
!loading &&
|
|
||||||
(isPaging() ? pagedUsers : (users | search: searchText:'name':'email':'id')) as searchedUsers
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p *ngIf="!searchedUsers.length">{{ "noUsersInList" | i18n }}</p>
|
|
||||||
<ng-container *ngIf="searchedUsers.length">
|
|
||||||
<app-callout
|
|
||||||
type="info"
|
|
||||||
title="{{ 'confirmUsers' | i18n }}"
|
|
||||||
icon="bwi bwi-check-circle"
|
|
||||||
*ngIf="showConfirmUsers"
|
|
||||||
>
|
|
||||||
{{ "usersNeedConfirmed" | i18n }}
|
|
||||||
</app-callout>
|
|
||||||
<table
|
|
||||||
class="table table-hover table-list"
|
|
||||||
infiniteScroll
|
|
||||||
[infiniteScrollDistance]="1"
|
|
||||||
[infiniteScrollDisabled]="!isPaging()"
|
|
||||||
(scrolled)="loadMore()"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let u of searchedUsers">
|
|
||||||
<td (click)="checkUser(u)" class="table-list-checkbox">
|
|
||||||
<input type="checkbox" [(ngModel)]="u.checked" appStopProp />
|
|
||||||
</td>
|
|
||||||
<td width="30">
|
|
||||||
<bit-avatar [text]="u | userName" [id]="u.userId" size="small"></bit-avatar>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
|
|
||||||
<span bitBadge badgeType="secondary" *ngIf="u.status === userStatusType.Invited">{{
|
|
||||||
"invited" | i18n
|
|
||||||
}}</span>
|
|
||||||
<span bitBadge badgeType="warning" *ngIf="u.status === userStatusType.Accepted">{{
|
|
||||||
"accepted" | i18n
|
|
||||||
}}</span>
|
|
||||||
<span bitBadge badgeType="secondary" *ngIf="u.status === userStatusType.Revoked">{{
|
|
||||||
"revoked" | i18n
|
|
||||||
}}</span>
|
|
||||||
<small class="text-muted d-block" *ngIf="u.name">{{ u.name }}</small>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ng-container *ngIf="u.twoFactorEnabled">
|
|
||||||
<i
|
|
||||||
class="bwi bwi-lock"
|
|
||||||
title="{{ 'userUsingTwoStep' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span class="sr-only">{{ "userUsingTwoStep" | i18n }}</span>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="showEnrolledStatus(u)">
|
|
||||||
<i
|
|
||||||
class="bwi bwi-key"
|
|
||||||
title="{{ 'enrolledPasswordReset' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span class="sr-only">{{ "enrolledPasswordReset" | i18n }}</span>
|
|
||||||
</ng-container>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span *ngIf="u.type === userType.Owner">{{ "owner" | i18n }}</span>
|
|
||||||
<span *ngIf="u.type === userType.Admin">{{ "admin" | i18n }}</span>
|
|
||||||
<span *ngIf="u.type === userType.Manager">{{ "manager" | i18n }}</span>
|
|
||||||
<span *ngIf="u.type === userType.User">{{ "user" | i18n }}</span>
|
|
||||||
<span *ngIf="u.type === userType.Custom">{{ "custom" | i18n }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="table-list-options">
|
|
||||||
<div class="dropdown" appListDropdown>
|
|
||||||
<button
|
|
||||||
class="btn btn-outline-secondary dropdown-toggle"
|
|
||||||
type="button"
|
|
||||||
data-toggle="dropdown"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="false"
|
|
||||||
appA11yTitle="{{ 'options' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
href="#"
|
|
||||||
appStopClick
|
|
||||||
(click)="reinvite(u)"
|
|
||||||
*ngIf="u.status === userStatusType.Invited"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
|
||||||
{{ "resendInvitation" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="dropdown-item text-success"
|
|
||||||
href="#"
|
|
||||||
appStopClick
|
|
||||||
(click)="confirm(u)"
|
|
||||||
*ngIf="u.status === userStatusType.Accepted"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
|
||||||
{{ "confirm" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
href="#"
|
|
||||||
appStopClick
|
|
||||||
(click)="groups(u)"
|
|
||||||
*ngIf="accessGroups"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-fw bwi-sitemap" aria-hidden="true"></i>
|
|
||||||
{{ "groups" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
href="#"
|
|
||||||
appStopClick
|
|
||||||
(click)="events(u)"
|
|
||||||
*ngIf="accessEvents && u.status === userStatusType.Confirmed"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-fw bwi-file-text" aria-hidden="true"></i>
|
|
||||||
{{ "eventLogs" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
href="#"
|
|
||||||
appStopClick
|
|
||||||
(click)="resetPassword(u)"
|
|
||||||
*ngIf="allowResetPassword(u)"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
|
||||||
{{ "resetPassword" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
href="#"
|
|
||||||
appStopClick
|
|
||||||
(click)="restore(u)"
|
|
||||||
*ngIf="u.status === userStatusType.Revoked"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
|
||||||
{{ "restoreAccess" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="dropdown-item"
|
|
||||||
href="#"
|
|
||||||
appStopClick
|
|
||||||
(click)="revoke(u)"
|
|
||||||
*ngIf="u.status !== userStatusType.Revoked"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
|
||||||
{{ "revokeAccess" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
|
|
||||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
|
||||||
{{ "remove" | i18n }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
<ng-template #addEdit></ng-template>
|
|
||||||
<ng-template #groupsTemplate></ng-template>
|
|
||||||
<ng-template #eventsTemplate></ng-template>
|
|
||||||
<ng-template #confirmTemplate></ng-template>
|
|
||||||
<ng-template #resetPasswordTemplate></ng-template>
|
|
||||||
<ng-template #bulkStatusTemplate></ng-template>
|
|
||||||
<ng-template #bulkConfirmTemplate></ng-template>
|
|
||||||
<ng-template #bulkRemoveTemplate></ng-template>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-spinner bwi-spin text-muted"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container
|
||||||
|
*ngIf="
|
||||||
|
!loading &&
|
||||||
|
(isPaging() ? pagedUsers : (users | search: searchText:'name':'email':'id')) as searchedUsers
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<p *ngIf="!searchedUsers.length">{{ "noUsersInList" | i18n }}</p>
|
||||||
|
<ng-container *ngIf="searchedUsers.length">
|
||||||
|
<app-callout
|
||||||
|
type="info"
|
||||||
|
title="{{ 'confirmUsers' | i18n }}"
|
||||||
|
icon="bwi bwi-check-circle"
|
||||||
|
*ngIf="showConfirmUsers"
|
||||||
|
>
|
||||||
|
{{ "usersNeedConfirmed" | i18n }}
|
||||||
|
</app-callout>
|
||||||
|
<table
|
||||||
|
class="table table-hover table-list"
|
||||||
|
infiniteScroll
|
||||||
|
[infiniteScrollDistance]="1"
|
||||||
|
[infiniteScrollDisabled]="!isPaging()"
|
||||||
|
(scrolled)="loadMore()"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let u of searchedUsers">
|
||||||
|
<td (click)="checkUser(u)" class="table-list-checkbox">
|
||||||
|
<input type="checkbox" [(ngModel)]="u.checked" appStopProp />
|
||||||
|
</td>
|
||||||
|
<td width="30">
|
||||||
|
<bit-avatar [text]="u | userName" [id]="u.userId" size="small"></bit-avatar>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
|
||||||
|
<span bitBadge badgeType="secondary" *ngIf="u.status === userStatusType.Invited">{{
|
||||||
|
"invited" | i18n
|
||||||
|
}}</span>
|
||||||
|
<span bitBadge badgeType="warning" *ngIf="u.status === userStatusType.Accepted">{{
|
||||||
|
"accepted" | i18n
|
||||||
|
}}</span>
|
||||||
|
<span bitBadge badgeType="secondary" *ngIf="u.status === userStatusType.Revoked">{{
|
||||||
|
"revoked" | i18n
|
||||||
|
}}</span>
|
||||||
|
<small class="text-muted d-block" *ngIf="u.name">{{ u.name }}</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<ng-container *ngIf="u.twoFactorEnabled">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-lock"
|
||||||
|
title="{{ 'userUsingTwoStep' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "userUsingTwoStep" | i18n }}</span>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="showEnrolledStatus(u)">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-key"
|
||||||
|
title="{{ 'enrolledPasswordReset' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "enrolledPasswordReset" | i18n }}</span>
|
||||||
|
</ng-container>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span *ngIf="u.type === userType.Owner">{{ "owner" | i18n }}</span>
|
||||||
|
<span *ngIf="u.type === userType.Admin">{{ "admin" | i18n }}</span>
|
||||||
|
<span *ngIf="u.type === userType.Manager">{{ "manager" | i18n }}</span>
|
||||||
|
<span *ngIf="u.type === userType.User">{{ "user" | i18n }}</span>
|
||||||
|
<span *ngIf="u.type === userType.Custom">{{ "custom" | i18n }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="table-list-options">
|
||||||
|
<div class="dropdown" appListDropdown>
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary dropdown-toggle"
|
||||||
|
type="button"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="false"
|
||||||
|
appA11yTitle="{{ 'options' | i18n }}"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
appStopClick
|
||||||
|
(click)="reinvite(u)"
|
||||||
|
*ngIf="u.status === userStatusType.Invited"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||||
|
{{ "resendInvitation" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="dropdown-item text-success"
|
||||||
|
href="#"
|
||||||
|
appStopClick
|
||||||
|
(click)="confirm(u)"
|
||||||
|
*ngIf="u.status === userStatusType.Accepted"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||||
|
{{ "confirm" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
appStopClick
|
||||||
|
(click)="groups(u)"
|
||||||
|
*ngIf="accessGroups"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-fw bwi-sitemap" aria-hidden="true"></i>
|
||||||
|
{{ "groups" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
appStopClick
|
||||||
|
(click)="events(u)"
|
||||||
|
*ngIf="accessEvents && u.status === userStatusType.Confirmed"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-fw bwi-file-text" aria-hidden="true"></i>
|
||||||
|
{{ "eventLogs" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
appStopClick
|
||||||
|
(click)="resetPassword(u)"
|
||||||
|
*ngIf="allowResetPassword(u)"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
||||||
|
{{ "resetPassword" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
appStopClick
|
||||||
|
(click)="restore(u)"
|
||||||
|
*ngIf="u.status === userStatusType.Revoked"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
||||||
|
{{ "restoreAccess" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
appStopClick
|
||||||
|
(click)="revoke(u)"
|
||||||
|
*ngIf="u.status !== userStatusType.Revoked"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
||||||
|
{{ "revokeAccess" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
|
||||||
|
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||||
|
{{ "remove" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #addEdit></ng-template>
|
||||||
|
<ng-template #groupsTemplate></ng-template>
|
||||||
|
<ng-template #eventsTemplate></ng-template>
|
||||||
|
<ng-template #confirmTemplate></ng-template>
|
||||||
|
<ng-template #resetPasswordTemplate></ng-template>
|
||||||
|
<ng-template #bulkStatusTemplate></ng-template>
|
||||||
|
<ng-template #bulkConfirmTemplate></ng-template>
|
||||||
|
<ng-template #bulkRemoveTemplate></ng-template>
|
||||||
|
@ -3,14 +3,18 @@ import { RouterModule, Routes } from "@angular/router";
|
|||||||
|
|
||||||
import { AuthGuard } from "@bitwarden/angular/guards/auth.guard";
|
import { AuthGuard } from "@bitwarden/angular/guards/auth.guard";
|
||||||
import {
|
import {
|
||||||
canAccessOrgAdmin,
|
|
||||||
canAccessGroupsTab,
|
canAccessGroupsTab,
|
||||||
|
canAccessManageTab,
|
||||||
canAccessMembersTab,
|
canAccessMembersTab,
|
||||||
|
canAccessOrgAdmin,
|
||||||
|
canManageCollections,
|
||||||
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||||
|
|
||||||
import { OrganizationPermissionsGuard } from "./guards/org-permissions.guard";
|
import { OrganizationPermissionsGuard } from "./guards/org-permissions.guard";
|
||||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
||||||
|
import { CollectionsComponent } from "./manage/collections.component";
|
||||||
import { GroupsComponent } from "./manage/groups.component";
|
import { GroupsComponent } from "./manage/groups.component";
|
||||||
|
import { ManageComponent } from "./manage/manage.component";
|
||||||
import { PeopleComponent } from "./manage/people.component";
|
import { PeopleComponent } from "./manage/people.component";
|
||||||
import { VaultModule } from "./vault/vault.module";
|
import { VaultModule } from "./vault/vault.module";
|
||||||
|
|
||||||
@ -33,22 +37,46 @@ const routes: Routes = [
|
|||||||
loadChildren: () => import("./settings").then((m) => m.OrganizationSettingsModule),
|
loadChildren: () => import("./settings").then((m) => m.OrganizationSettingsModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "members",
|
path: "manage",
|
||||||
component: PeopleComponent,
|
component: ManageComponent,
|
||||||
canActivate: [OrganizationPermissionsGuard],
|
canActivate: [OrganizationPermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
titleId: "members",
|
organizationPermissions: canAccessManageTab,
|
||||||
organizationPermissions: canAccessMembersTab,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "groups",
|
|
||||||
component: GroupsComponent,
|
|
||||||
canActivate: [OrganizationPermissionsGuard],
|
|
||||||
data: {
|
|
||||||
titleId: "groups",
|
|
||||||
organizationPermissions: canAccessGroupsTab,
|
|
||||||
},
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
pathMatch: "full",
|
||||||
|
redirectTo: "members",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "collections",
|
||||||
|
component: CollectionsComponent,
|
||||||
|
canActivate: [OrganizationPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
titleId: "collections",
|
||||||
|
organizationPermissions: canManageCollections,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "groups",
|
||||||
|
component: GroupsComponent,
|
||||||
|
canActivate: [OrganizationPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
titleId: "groups",
|
||||||
|
organizationPermissions: canAccessGroupsTab,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "members",
|
||||||
|
component: PeopleComponent,
|
||||||
|
canActivate: [OrganizationPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
titleId: "members",
|
||||||
|
organizationPermissions: canAccessMembersTab,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "reporting",
|
path: "reporting",
|
||||||
|
@ -4,10 +4,7 @@ import { LooseComponentsModule, SharedModule } from "../../shared";
|
|||||||
import { PoliciesModule } from "../policies";
|
import { PoliciesModule } from "../policies";
|
||||||
|
|
||||||
import { AccountComponent } from "./account.component";
|
import { AccountComponent } from "./account.component";
|
||||||
import { AdjustSubscription } from "./adjust-subscription.component";
|
|
||||||
import { ChangePlanComponent } from "./change-plan.component";
|
|
||||||
import { DeleteOrganizationComponent } from "./delete-organization.component";
|
import { DeleteOrganizationComponent } from "./delete-organization.component";
|
||||||
import { DownloadLicenseComponent } from "./download-license.component";
|
|
||||||
import { OrganizationSettingsRoutingModule } from "./organization-settings-routing.module";
|
import { OrganizationSettingsRoutingModule } from "./organization-settings-routing.module";
|
||||||
import { SettingsComponent } from "./settings.component";
|
import { SettingsComponent } from "./settings.component";
|
||||||
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||||
@ -17,10 +14,7 @@ import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
|||||||
declarations: [
|
declarations: [
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
AccountComponent,
|
AccountComponent,
|
||||||
AdjustSubscription,
|
|
||||||
ChangePlanComponent,
|
|
||||||
DeleteOrganizationComponent,
|
DeleteOrganizationComponent,
|
||||||
DownloadLicenseComponent,
|
|
||||||
TwoFactorSetupComponent,
|
TwoFactorSetupComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@ -293,21 +293,17 @@
|
|||||||
<label for="file">2. {{ "selectImportFile" | i18n }}</label>
|
<label for="file">2. {{ "selectImportFile" | i18n }}</label>
|
||||||
<br />
|
<br />
|
||||||
<div class="file-selector">
|
<div class="file-selector">
|
||||||
<button
|
<button type="button" class="btn btn-outline-primary" (click)="fileInput.click()">
|
||||||
type="button"
|
|
||||||
class="btn btn-outline-primary"
|
|
||||||
onclick="document.getElementById('file').click()"
|
|
||||||
>
|
|
||||||
{{ "chooseFile" | i18n }}
|
{{ "chooseFile" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
{{ this.fileSelected ? this.fileSelected.name : ("noFileChosen" | i18n) }}
|
{{ this.fileSelected ? this.fileSelected.name : ("noFileChosen" | i18n) }}
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
#fileInput
|
||||||
type="file"
|
type="file"
|
||||||
id="file"
|
id="file"
|
||||||
class="form-control-file"
|
class="tw-hidden"
|
||||||
name="file"
|
name="file"
|
||||||
style="display: none"
|
|
||||||
[disabled]="importBlockedByPolicy$ | async"
|
[disabled]="importBlockedByPolicy$ | async"
|
||||||
(change)="setSelectedFile($event)"
|
(change)="setSelectedFile($event)"
|
||||||
/>
|
/>
|
||||||
@ -327,7 +323,7 @@
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-primary btn-submit"
|
class="btn btn-primary btn-submit"
|
||||||
[disabled]="loading || importBlockedByPolicy$ | async"
|
[disabled]="loading || (importBlockedByPolicy$ | async)"
|
||||||
[ngClass]="{ manual: importBlockedByPolicy$ | async }"
|
[ngClass]="{ manual: importBlockedByPolicy$ | async }"
|
||||||
>
|
>
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
|
@ -4,6 +4,10 @@ import { Utils } from "../../misc/utils";
|
|||||||
import { Organization } from "../../models/domain/organization";
|
import { Organization } from "../../models/domain/organization";
|
||||||
import { I18nService } from "../i18n.service";
|
import { I18nService } from "../i18n.service";
|
||||||
|
|
||||||
|
export function canAccessVaultTab(org: Organization): boolean {
|
||||||
|
return org.isManager;
|
||||||
|
}
|
||||||
|
|
||||||
export function canAccessSettingsTab(org: Organization): boolean {
|
export function canAccessSettingsTab(org: Organization): boolean {
|
||||||
return org.isOwner;
|
return org.isOwner;
|
||||||
}
|
}
|
||||||
@ -24,13 +28,27 @@ export function canAccessBillingTab(org: Organization): boolean {
|
|||||||
return org.canManageBilling;
|
return org.canManageBilling;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function canManageCollections(org: Organization): boolean {
|
||||||
|
return (
|
||||||
|
org.canCreateNewCollections ||
|
||||||
|
org.canEditAnyCollection ||
|
||||||
|
org.canDeleteAnyCollection ||
|
||||||
|
org.canViewAssignedCollections
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canAccessManageTab(org: Organization): boolean {
|
||||||
|
return canAccessMembersTab(org) || canAccessGroupsTab(org) || canManageCollections(org);
|
||||||
|
}
|
||||||
|
|
||||||
export function canAccessOrgAdmin(org: Organization): boolean {
|
export function canAccessOrgAdmin(org: Organization): boolean {
|
||||||
return (
|
return (
|
||||||
canAccessMembersTab(org) ||
|
canAccessMembersTab(org) ||
|
||||||
canAccessGroupsTab(org) ||
|
canAccessGroupsTab(org) ||
|
||||||
canAccessReportingTab(org) ||
|
canAccessReportingTab(org) ||
|
||||||
canAccessBillingTab(org) ||
|
canAccessBillingTab(org) ||
|
||||||
canAccessSettingsTab(org)
|
canAccessSettingsTab(org) ||
|
||||||
|
canAccessVaultTab(org)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,8 +28,23 @@ export class TabListItemDirective implements FocusableOption {
|
|||||||
@HostBinding("class")
|
@HostBinding("class")
|
||||||
get classList(): string[] {
|
get classList(): string[] {
|
||||||
return this.baseClassList
|
return this.baseClassList
|
||||||
.concat(this.active ? this.activeClassList : ["!tw-text-main"])
|
.concat(this.active ? this.activeClassList : [])
|
||||||
.concat(this.disabled ? this.disabledClassList : []);
|
.concat(this.disabled ? this.disabledClassList : [])
|
||||||
|
.concat(this.textColorClassList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes used for styling tab item text color.
|
||||||
|
* Separate text color class list required to override bootstrap classes in Web.
|
||||||
|
*/
|
||||||
|
get textColorClassList(): string[] {
|
||||||
|
if (this.disabled) {
|
||||||
|
return ["!tw-text-muted", "hover:!tw-text-muted"];
|
||||||
|
}
|
||||||
|
if (this.active) {
|
||||||
|
return ["!tw-text-primary-500", "hover:!tw-text-primary-700"];
|
||||||
|
}
|
||||||
|
return ["!tw-text-main", "hover:!tw-text-main"];
|
||||||
}
|
}
|
||||||
|
|
||||||
get baseClassList(): string[] {
|
get baseClassList(): string[] {
|
||||||
@ -47,9 +62,7 @@ export class TabListItemDirective implements FocusableOption {
|
|||||||
"tw-border-transparent",
|
"tw-border-transparent",
|
||||||
"tw-border-solid",
|
"tw-border-solid",
|
||||||
"tw-bg-transparent",
|
"tw-bg-transparent",
|
||||||
"tw-text-main",
|
|
||||||
"hover:tw-underline",
|
"hover:tw-underline",
|
||||||
"hover:tw-text-main",
|
|
||||||
"focus-visible:tw-z-10",
|
"focus-visible:tw-z-10",
|
||||||
"focus-visible:tw-outline-none",
|
"focus-visible:tw-outline-none",
|
||||||
"focus-visible:tw-ring-2",
|
"focus-visible:tw-ring-2",
|
||||||
@ -58,13 +71,7 @@ export class TabListItemDirective implements FocusableOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get disabledClassList(): string[] {
|
get disabledClassList(): string[] {
|
||||||
return [
|
return ["!tw-bg-secondary-100", "!tw-no-underline", "tw-cursor-not-allowed"];
|
||||||
"!tw-bg-secondary-100",
|
|
||||||
"!tw-text-muted",
|
|
||||||
"hover:!tw-text-muted",
|
|
||||||
"!tw-no-underline",
|
|
||||||
"tw-cursor-not-allowed",
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeClassList(): string[] {
|
get activeClassList(): string[] {
|
||||||
@ -75,9 +82,7 @@ export class TabListItemDirective implements FocusableOption {
|
|||||||
"tw-border-b",
|
"tw-border-b",
|
||||||
"tw-border-b-background",
|
"tw-border-b-background",
|
||||||
"tw-bg-background",
|
"tw-bg-background",
|
||||||
"!tw-text-primary-500",
|
|
||||||
"hover:tw-border-t-primary-700",
|
"hover:tw-border-t-primary-700",
|
||||||
"hover:!tw-text-primary-700",
|
|
||||||
"focus-visible:tw-border-t-primary-700",
|
"focus-visible:tw-border-t-primary-700",
|
||||||
"focus-visible:!tw-text-primary-700",
|
"focus-visible:!tw-text-primary-700",
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user