mirror of
https://github.com/bitwarden/browser.git
synced 2025-04-15 20:16:03 +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
apps/web/src/app
organizations
billing
adjust-subscription.component.htmladjust-subscription.component.tschange-plan.component.htmlchange-plan.component.tsdownload-license.component.htmldownload-license.component.tsorganization-billing.module.ts
layouts
manage
organization-routing.module.tssettings
tools/import-export
libs
common/src/abstractions/organization
components/src/tabs/shared
@ -1,9 +1,11 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { LooseComponentsModule } from "../../shared/loose-components.module";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||
|
||||
import { AdjustSubscription } from "./adjust-subscription.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 { OrganizationBillingRoutingModule } from "./organization-billing-routing.module";
|
||||
import { OrganizationBillingTabComponent } from "./organization-billing-tab.component";
|
||||
@ -12,7 +14,10 @@ import { OrganizationSubscriptionComponent } from "./organization-subscription.c
|
||||
@NgModule({
|
||||
imports: [SharedModule, LooseComponentsModule, OrganizationBillingRoutingModule],
|
||||
declarations: [
|
||||
AdjustSubscription,
|
||||
BillingSyncApiKeyComponent,
|
||||
ChangePlanComponent,
|
||||
DownloadLicenseComponent,
|
||||
OrganizationBillingTabComponent,
|
||||
OrganizationSubscriptionComponent,
|
||||
OrgBillingHistoryViewComponent,
|
||||
|
@ -8,17 +8,15 @@
|
||||
></app-organization-switcher>
|
||||
<bit-tab-nav-bar class="-tw-mb-px">
|
||||
<bit-tab-link route="vault">{{ "vault" | i18n }}</bit-tab-link>
|
||||
<bit-tab-link *ngIf="canShowMembersTab(organization)" route="members">{{
|
||||
"members" | i18n
|
||||
}}</bit-tab-link>
|
||||
<bit-tab-link *ngIf="canShowGroupsTab(organization)" route="groups">{{
|
||||
"groups" | i18n
|
||||
}}</bit-tab-link>
|
||||
<bit-tab-link *ngIf="canShowManageTab(organization)" [route]="getManageRoute(organization)">
|
||||
{{ "manage" | i18n }}
|
||||
</bit-tab-link>
|
||||
<bit-tab-link
|
||||
*ngIf="canShowReportsTab(organization)"
|
||||
[route]="getReportRoute(organization)"
|
||||
>{{ getReportTabLabel(organization) | i18n }}</bit-tab-link
|
||||
>
|
||||
{{ getReportTabLabel(organization) | i18n }}
|
||||
</bit-tab-link>
|
||||
<bit-tab-link *ngIf="canShowBillingTab(organization)" route="billing">{{
|
||||
"billing" | i18n
|
||||
}}</bit-tab-link>
|
||||
|
@ -5,6 +5,7 @@ import { map, mergeMap, Observable, Subject, takeUntil } from "rxjs";
|
||||
import {
|
||||
canAccessBillingTab,
|
||||
canAccessGroupsTab,
|
||||
canAccessManageTab,
|
||||
canAccessMembersTab,
|
||||
canAccessReportingTab,
|
||||
canAccessSettingsTab,
|
||||
@ -48,6 +49,10 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
return canAccessSettingsTab(organization);
|
||||
}
|
||||
|
||||
canShowManageTab(organization: Organization): boolean {
|
||||
return canAccessManageTab(organization);
|
||||
}
|
||||
|
||||
canShowMembersTab(organization: Organization): boolean {
|
||||
return canAccessMembersTab(organization);
|
||||
}
|
||||
@ -71,4 +76,20 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
getReportRoute(organization: Organization): string {
|
||||
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,4 +1,3 @@
|
||||
<div class="container page-content">
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "groups" | i18n }}</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
@ -76,4 +75,3 @@
|
||||
</ng-container>
|
||||
<ng-template #addEdit></ng-template>
|
||||
<ng-template #usersTemplate></ng-template>
|
||||
</div>
|
||||
|
@ -5,12 +5,12 @@
|
||||
<div class="card-header">{{ "manage" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a
|
||||
routerLink="people"
|
||||
routerLink="members"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization.canManageUsers"
|
||||
>
|
||||
{{ "people" | i18n }}
|
||||
{{ "members" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="collections"
|
||||
@ -28,38 +28,6 @@
|
||||
>
|
||||
{{ "groups" | i18n }}
|
||||
</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>
|
||||
|
@ -1,4 +1,3 @@
|
||||
<div class="container page-content">
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "members" | i18n }}</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
@ -285,4 +284,3 @@
|
||||
<ng-template #bulkStatusTemplate></ng-template>
|
||||
<ng-template #bulkConfirmTemplate></ng-template>
|
||||
<ng-template #bulkRemoveTemplate></ng-template>
|
||||
</div>
|
||||
|
@ -3,14 +3,18 @@ import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { AuthGuard } from "@bitwarden/angular/guards/auth.guard";
|
||||
import {
|
||||
canAccessOrgAdmin,
|
||||
canAccessGroupsTab,
|
||||
canAccessManageTab,
|
||||
canAccessMembersTab,
|
||||
canAccessOrgAdmin,
|
||||
canManageCollections,
|
||||
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
|
||||
import { OrganizationPermissionsGuard } from "./guards/org-permissions.guard";
|
||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
||||
import { CollectionsComponent } from "./manage/collections.component";
|
||||
import { GroupsComponent } from "./manage/groups.component";
|
||||
import { ManageComponent } from "./manage/manage.component";
|
||||
import { PeopleComponent } from "./manage/people.component";
|
||||
import { VaultModule } from "./vault/vault.module";
|
||||
|
||||
@ -33,12 +37,25 @@ const routes: Routes = [
|
||||
loadChildren: () => import("./settings").then((m) => m.OrganizationSettingsModule),
|
||||
},
|
||||
{
|
||||
path: "members",
|
||||
component: PeopleComponent,
|
||||
path: "manage",
|
||||
component: ManageComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "members",
|
||||
organizationPermissions: canAccessMembersTab,
|
||||
organizationPermissions: canAccessManageTab,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
redirectTo: "members",
|
||||
},
|
||||
{
|
||||
path: "collections",
|
||||
component: CollectionsComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "collections",
|
||||
organizationPermissions: canManageCollections,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -50,6 +67,17 @@ const routes: Routes = [
|
||||
organizationPermissions: canAccessGroupsTab,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "members",
|
||||
component: PeopleComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "members",
|
||||
organizationPermissions: canAccessMembersTab,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "reporting",
|
||||
loadChildren: () =>
|
||||
|
@ -4,10 +4,7 @@ import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||
import { PoliciesModule } from "../policies";
|
||||
|
||||
import { AccountComponent } from "./account.component";
|
||||
import { AdjustSubscription } from "./adjust-subscription.component";
|
||||
import { ChangePlanComponent } from "./change-plan.component";
|
||||
import { DeleteOrganizationComponent } from "./delete-organization.component";
|
||||
import { DownloadLicenseComponent } from "./download-license.component";
|
||||
import { OrganizationSettingsRoutingModule } from "./organization-settings-routing.module";
|
||||
import { SettingsComponent } from "./settings.component";
|
||||
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
@ -17,10 +14,7 @@ import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
declarations: [
|
||||
SettingsComponent,
|
||||
AccountComponent,
|
||||
AdjustSubscription,
|
||||
ChangePlanComponent,
|
||||
DeleteOrganizationComponent,
|
||||
DownloadLicenseComponent,
|
||||
TwoFactorSetupComponent,
|
||||
],
|
||||
})
|
||||
|
@ -293,21 +293,17 @@
|
||||
<label for="file">2. {{ "selectImportFile" | i18n }}</label>
|
||||
<br />
|
||||
<div class="file-selector">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary"
|
||||
onclick="document.getElementById('file').click()"
|
||||
>
|
||||
<button type="button" class="btn btn-outline-primary" (click)="fileInput.click()">
|
||||
{{ "chooseFile" | i18n }}
|
||||
</button>
|
||||
{{ this.fileSelected ? this.fileSelected.name : ("noFileChosen" | i18n) }}
|
||||
</div>
|
||||
<input
|
||||
#fileInput
|
||||
type="file"
|
||||
id="file"
|
||||
class="form-control-file"
|
||||
class="tw-hidden"
|
||||
name="file"
|
||||
style="display: none"
|
||||
[disabled]="importBlockedByPolicy$ | async"
|
||||
(change)="setSelectedFile($event)"
|
||||
/>
|
||||
@ -327,7 +323,7 @@
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-submit"
|
||||
[disabled]="loading || importBlockedByPolicy$ | async"
|
||||
[disabled]="loading || (importBlockedByPolicy$ | async)"
|
||||
[ngClass]="{ manual: importBlockedByPolicy$ | async }"
|
||||
>
|
||||
<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 { I18nService } from "../i18n.service";
|
||||
|
||||
export function canAccessVaultTab(org: Organization): boolean {
|
||||
return org.isManager;
|
||||
}
|
||||
|
||||
export function canAccessSettingsTab(org: Organization): boolean {
|
||||
return org.isOwner;
|
||||
}
|
||||
@ -24,13 +28,27 @@ export function canAccessBillingTab(org: Organization): boolean {
|
||||
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 {
|
||||
return (
|
||||
canAccessMembersTab(org) ||
|
||||
canAccessGroupsTab(org) ||
|
||||
canAccessReportingTab(org) ||
|
||||
canAccessBillingTab(org) ||
|
||||
canAccessSettingsTab(org)
|
||||
canAccessSettingsTab(org) ||
|
||||
canAccessVaultTab(org)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -28,8 +28,23 @@ export class TabListItemDirective implements FocusableOption {
|
||||
@HostBinding("class")
|
||||
get classList(): string[] {
|
||||
return this.baseClassList
|
||||
.concat(this.active ? this.activeClassList : ["!tw-text-main"])
|
||||
.concat(this.disabled ? this.disabledClassList : []);
|
||||
.concat(this.active ? this.activeClassList : [])
|
||||
.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[] {
|
||||
@ -47,9 +62,7 @@ export class TabListItemDirective implements FocusableOption {
|
||||
"tw-border-transparent",
|
||||
"tw-border-solid",
|
||||
"tw-bg-transparent",
|
||||
"tw-text-main",
|
||||
"hover:tw-underline",
|
||||
"hover:tw-text-main",
|
||||
"focus-visible:tw-z-10",
|
||||
"focus-visible:tw-outline-none",
|
||||
"focus-visible:tw-ring-2",
|
||||
@ -58,13 +71,7 @@ export class TabListItemDirective implements FocusableOption {
|
||||
}
|
||||
|
||||
get disabledClassList(): string[] {
|
||||
return [
|
||||
"!tw-bg-secondary-100",
|
||||
"!tw-text-muted",
|
||||
"hover:!tw-text-muted",
|
||||
"!tw-no-underline",
|
||||
"tw-cursor-not-allowed",
|
||||
];
|
||||
return ["!tw-bg-secondary-100", "!tw-no-underline", "tw-cursor-not-allowed"];
|
||||
}
|
||||
|
||||
get activeClassList(): string[] {
|
||||
@ -75,9 +82,7 @@ export class TabListItemDirective implements FocusableOption {
|
||||
"tw-border-b",
|
||||
"tw-border-b-background",
|
||||
"tw-bg-background",
|
||||
"!tw-text-primary-500",
|
||||
"hover:tw-border-t-primary-700",
|
||||
"hover:!tw-text-primary-700",
|
||||
"focus-visible:tw-border-t-primary-700",
|
||||
"focus-visible:!tw-text-primary-700",
|
||||
];
|
||||
|
Loading…
Reference in New Issue
Block a user