1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-23 19:53:43 +00:00

Vertical Vault Navigation (#6957)

* WIP admin console layout

* Update icons

* Migrate more things

* Migrate the last pages

* Move header to web

* Fix story not working

* Convert header component to standalone

* Migrate org layout to standalone

* Enable org switcher

* Add AC to product switcher

* Migrate provider portal to vertical nav

* Migrate PM

* Prettier fixes

* Change AC and PP to use secondary variant layout & update logos

* Remove full width setting

* Remove commented code

* Add header to report pages

* Add provider portal banner

* Fix banner for billing pages

* Move vault title to header

* Prevent scrollbar jumping

* Move send button to header

* Replace search input with bit-search

* Remove unused files and css

* Add banner

* Tweak storage option

* Fix duplicate nav item after merge

* Migrate banner state to state provider framework

* [AC-2078] Fix device approvals header

* [PM-5861] Hide AC from product switcher for users that do not have access

* [PM-5860] Fix Vault and Send page headers

* [AC-2075] Fix missing link on reporting nav group

* [AC-2079] Hide Payment Method and Billing History pages for self-hosted instances

* [AC-2090] Hide reports/event log nav items for users that do not have permission

* [AC-2092] Fix missing provider portal option in product switcher on page load

* Add null check for organization in org layout component

* [AC-2094] Fix missing page header for new client orgs page

* [AC-2093] Update New client button styling

* Fix failing test after merge

* [PM-2087] Use disk-local for web layout banner

* [PM-6041] Update banner copy to read "web app"

* [PM-6094] Update banner link to marketing URL

* [PM-6114] add CL container component to VVR pages (#7802)

* create bit-container component

* add container to all page components

* Fix linting errors after merge with main

* Fix product switcher stories

* Fix web-header stories

* mock org state properly in product switcher stories (#7956)

* refactor: move web layout migration banner logic into a service (#7958)

* make CL codeowner of web header files

* move migration banner logic to service; update stories

* [PM-5862] Ensure a sync has run before hiding navigation links

* Remove leftover banner global state

* Re-add dropped selfHosted ngIf

* Add rel noreferrer

* Remove comment

---------

Co-authored-by: Shane Melton <smelton@bitwarden.com>
Co-authored-by: Will Martin <contact@willmartian.com>
This commit is contained in:
Oscar Hinton
2024-02-23 18:22:45 +01:00
committed by GitHub
parent a31e3bf842
commit 38d8fbdb5a
128 changed files with 4228 additions and 4647 deletions

View File

@@ -1,108 +1,109 @@
<div class="page-header">
<h1>{{ "domainRules" | i18n }}</h1>
</div>
<p>{{ "domainRulesDesc" | i18n }}</p>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<h2>{{ "customEqDomains" | i18n }}</h2>
<p *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>
</p>
<ng-container *ngIf="!loading">
<div class="form-group d-flex" *ngFor="let d of custom; let i = index; trackBy: indexTrackBy">
<div class="flex-fill">
<label for="customDomain_{{ i }}" class="sr-only">{{
"customDomainX" | i18n: i + 1
}}</label>
<textarea
class="form-control"
name="CustomDomain[{{ i }}]"
id="customDomain_{{ i }}"
[(ngModel)]="custom[i]"
placeholder="{{ 'ex' | i18n }} google.com, gmail.com"
required
></textarea>
<app-header></app-header>
<bit-container>
<p>{{ "domainRulesDesc" | i18n }}</p>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<h2>{{ "customEqDomains" | i18n }}</h2>
<p *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>
</p>
<ng-container *ngIf="!loading">
<div class="form-group d-flex" *ngFor="let d of custom; let i = index; trackBy: indexTrackBy">
<div class="flex-fill">
<label for="customDomain_{{ i }}" class="sr-only">{{
"customDomainX" | i18n: i + 1
}}</label>
<textarea
class="form-control"
name="CustomDomain[{{ i }}]"
id="customDomain_{{ i }}"
[(ngModel)]="custom[i]"
placeholder="{{ 'ex' | i18n }} google.com, gmail.com"
required
></textarea>
</div>
<button
type="button"
class="btn btn-link text-danger ml-2"
(click)="remove(i)"
appA11yTitle="{{ 'remove' | i18n }}"
>
<i class="bwi bwi-minus-circle bwi-lg" aria-hidden="true"></i>
</button>
</div>
<button
type="button"
class="btn btn-link text-danger ml-2"
(click)="remove(i)"
appA11yTitle="{{ 'remove' | i18n }}"
>
<i class="bwi bwi-minus-circle bwi-lg" aria-hidden="true"></i>
<button type="button" (click)="add()" class="btn btn-outline-secondary btn-sm mb-2">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i> {{ "newCustomDomain" | i18n }}
</button>
</div>
<button type="button" (click)="add()" class="btn btn-outline-secondary btn-sm mb-2">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i> {{ "newCustomDomain" | i18n }}
<small class="text-muted d-block mb-3">{{ "newCustomDomainDesc" | i18n }}</small>
</ng-container>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
<small class="text-muted d-block mb-3">{{ "newCustomDomainDesc" | i18n }}</small>
</ng-container>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
<h2 class="spaced-header">{{ "globalEqDomains" | i18n }}</h2>
<p *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>
</p>
<table class="table table-hover table-list" *ngIf="!loading && global.length > 0">
<tbody>
<tr *ngFor="let d of global">
<td [ngClass]="{ 'table-list-strike': d.excluded }">{{ d.domains }}</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)="toggleExcluded(d)"
*ngIf="!d.excluded"
<h2 class="spaced-header">{{ "globalEqDomains" | i18n }}</h2>
<p *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>
</p>
<table class="table table-hover table-list" *ngIf="!loading && global.length > 0">
<tbody>
<tr *ngFor="let d of global">
<td [ngClass]="{ 'table-list-strike': d.excluded }">{{ d.domains }}</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-fw bwi-close" aria-hidden="true"></i>
{{ "exclude" | i18n }}
</a>
<a
class="dropdown-item"
href="#"
appStopClick
(click)="toggleExcluded(d)"
*ngIf="d.excluded"
>
<i class="bwi bwi-fw bwi-plus" aria-hidden="true"></i>
{{ "include" | i18n }}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="customize(d)">
<i class="bwi bwi-fw bwi-cut" aria-hidden="true"></i>
{{ "customize" | i18n }}
</a>
<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)="toggleExcluded(d)"
*ngIf="!d.excluded"
>
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "exclude" | i18n }}
</a>
<a
class="dropdown-item"
href="#"
appStopClick
(click)="toggleExcluded(d)"
*ngIf="d.excluded"
>
<i class="bwi bwi-fw bwi-plus" aria-hidden="true"></i>
{{ "include" | i18n }}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="customize(d)">
<i class="bwi bwi-fw bwi-cut" aria-hidden="true"></i>
{{ "customize" | i18n }}
</a>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
</form>
</td>
</tr>
</tbody>
</table>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
</form>
</bit-container>

View File

@@ -1,143 +1,129 @@
<div class="page-header">
<h1>{{ "preferences" | i18n }}</h1>
</div>
<p>{{ "preferencesDesc" | i18n }}</p>
<form [formGroup]="form" (ngSubmit)="submit()" ngNativeValidate>
<div class="row">
<div class="col-6">
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
<span *ngIf="policy.timeout && policy.action">
{{
"vaultTimeoutPolicyWithActionInEffect"
| i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
}}
</span>
<span *ngIf="policy.timeout && !policy.action">
{{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }}
</span>
<span *ngIf="!policy.timeout && policy.action">
{{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
</span>
</app-callout>
<app-vault-timeout-input
[vaultTimeoutOptions]="vaultTimeoutOptions"
[formControl]="form.controls.vaultTimeout"
ngDefaultControl
>
</app-vault-timeout-input>
</div>
</div>
<ng-container *ngIf="availableVaultTimeoutActions$ | async as availableVaultTimeoutActions">
<div *ngIf="availableVaultTimeoutActions.length > 1" class="form-group">
<label>{{ "vaultTimeoutAction" | i18n }}</label>
<div
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)"
class="form-check form-check-block"
>
<input
class="form-check-input"
type="radio"
name="vaultTimeoutAction"
id="vaultTimeoutActionLock"
value="{{ VaultTimeoutAction.Lock }}"
formControlName="vaultTimeoutAction"
/>
<label class="form-check-label" for="vaultTimeoutActionLock">
{{ "lock" | i18n }}
<small>{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
</label>
</div>
<div
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.LogOut)"
class="form-check mt-2 form-check-block"
>
<input
class="form-check-input"
type="radio"
name="vaultTimeoutAction"
id="vaultTimeoutActionLogOut"
value="{{ VaultTimeoutAction.LogOut }}"
formControlName="vaultTimeoutAction"
/>
<label class="form-check-label" for="vaultTimeoutActionLogOut">
{{ "logOut" | i18n }}
<small>{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
</label>
<app-header></app-header>
<bit-container>
<p>{{ "preferencesDesc" | i18n }}</p>
<form [formGroup]="form" (ngSubmit)="submit()" ngNativeValidate>
<div class="row">
<div class="col-6">
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
<span *ngIf="policy.timeout && policy.action">
{{
"vaultTimeoutPolicyWithActionInEffect"
| i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
}}
</span>
<span *ngIf="policy.timeout && !policy.action">
{{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }}
</span>
<span *ngIf="!policy.timeout && policy.action">
{{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
</span>
</app-callout>
<app-vault-timeout-input
[vaultTimeoutOptions]="vaultTimeoutOptions"
[formControl]="form.controls.vaultTimeout"
ngDefaultControl
>
</app-vault-timeout-input>
</div>
</div>
</ng-container>
<div class="row">
<div class="col-6">
<div class="form-group">
<div class="d-flex">
<label for="locale">{{ "language" | i18n }}</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/localization/"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
<ng-container *ngIf="availableVaultTimeoutActions$ | async as availableVaultTimeoutActions">
<div *ngIf="availableVaultTimeoutActions.length > 1" class="form-group">
<label>{{ "vaultTimeoutAction" | i18n }}</label>
<div
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)"
class="form-check form-check-block"
>
<input
class="form-check-input"
type="radio"
name="vaultTimeoutAction"
id="vaultTimeoutActionLock"
value="{{ VaultTimeoutAction.Lock }}"
formControlName="vaultTimeoutAction"
/>
<label class="form-check-label" for="vaultTimeoutActionLock">
{{ "lock" | i18n }}
<small>{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
</label>
</div>
<div
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.LogOut)"
class="form-check mt-2 form-check-block"
>
<input
class="form-check-input"
type="radio"
name="vaultTimeoutAction"
id="vaultTimeoutActionLogOut"
value="{{ VaultTimeoutAction.LogOut }}"
formControlName="vaultTimeoutAction"
/>
<label class="form-check-label" for="vaultTimeoutActionLogOut">
{{ "logOut" | i18n }}
<small>{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
</label>
</div>
</div>
</ng-container>
<div class="row">
<div class="col-6">
<div class="form-group">
<div class="d-flex">
<label for="locale">{{ "language" | i18n }}</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/localization/"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<select id="locale" name="Locale" formControlName="locale" class="form-control">
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="form-text text-muted">{{ "languageDesc" | i18n }}</small>
</div>
<select id="locale" name="Locale" formControlName="locale" class="form-control">
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="form-text text-muted">{{ "languageDesc" | i18n }}</small>
</div>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="enableFavicons"
name="enableFavicons"
formControlName="enableFavicons"
/>
<label class="form-check-label" for="enableFavicons">
{{ "enableFavicon" | i18n }}
</label>
<a
href="https://bitwarden.com/help/website-icons/"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="enableFavicons"
name="enableFavicons"
formControlName="enableFavicons"
/>
<label class="form-check-label" for="enableFavicons">
{{ "enableFavicon" | i18n }}
</label>
<a
href="https://bitwarden.com/help/website-icons/"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<small class="form-text text-muted">{{ "faviconDesc" | i18n }}</small>
</div>
<small class="form-text text-muted">{{ "faviconDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="enableFullWidth"
name="enableFullWidth"
formControlName="enableFullWidth"
/>
<label class="form-check-label" for="enableFullWidth">
{{ "enableFullWidth" | i18n }}
</label>
</div>
<small class="form-text text-muted">{{ "enableFullWidthDesc" | i18n }}</small>
</div>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="theme">{{ "theme" | i18n }}</label>
<select id="theme" name="theme" formControlName="theme" class="form-control">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="form-text text-muted">{{ "themeDesc" | i18n }}</small>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="theme">{{ "theme" | i18n }}</label>
<select id="theme" name="theme" formControlName="theme" class="form-control">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="form-text text-muted">{{ "themeDesc" | i18n }}</small>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">
{{ "save" | i18n }}
</button>
</form>
<button type="submit" class="btn btn-primary">
{{ "save" | i18n }}
</button>
</form>
</bit-container>

View File

@@ -42,7 +42,6 @@ export class PreferencesComponent implements OnInit {
vaultTimeout: [null as number | null],
vaultTimeoutAction: [VaultTimeoutAction.Lock],
enableFavicons: true,
enableFullWidth: false,
theme: [ThemeType.Light],
locale: [null as string | null],
});
@@ -142,7 +141,6 @@ export class PreferencesComponent implements OnInit {
this.vaultTimeoutSettingsService.vaultTimeoutAction$(),
),
enableFavicons: !(await this.settingsService.getDisableFavicon()),
enableFullWidth: await this.stateService.getEnableFullWidth(),
theme: await this.stateService.getTheme(),
locale: (await this.stateService.getLocale()) ?? null,
};
@@ -167,8 +165,6 @@ export class PreferencesComponent implements OnInit {
values.vaultTimeoutAction,
);
await this.settingsService.setDisableFavicon(!values.enableFavicons);
await this.stateService.setEnableFullWidth(values.enableFullWidth);
this.messagingService.send("setFullWidth");
if (values.theme !== this.startingTheme) {
await this.themingService.updateConfiguredTheme(values.theme);
this.startingTheme = values.theme;

View File

@@ -1,45 +0,0 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<div class="card">
<div class="card-header">{{ "accountSettings" | i18n }}</div>
<div class="list-group list-group-flush">
<a routerLink="account" class="list-group-item" routerLinkActive="active">
{{ "myAccount" | i18n }}
</a>
<a routerLink="security" class="list-group-item" routerLinkActive="active">
{{ "security" | i18n }}
</a>
<a routerLink="preferences" class="list-group-item" routerLinkActive="active">
{{ "preferences" | i18n }}
</a>
<a
routerLink="subscription"
class="list-group-item"
routerLinkActive="active"
*ngIf="!hideSubscription"
>
{{ "subscription" | i18n }}
</a>
<a routerLink="domain-rules" class="list-group-item" routerLinkActive="active">
{{ "domainRules" | i18n }}
</a>
<a routerLink="emergency-access" class="list-group-item" routerLinkActive="active">
{{ "emergencyAccess" | i18n }}
</a>
<a
routerLink="sponsored-families"
class="list-group-item"
routerLinkActive="active"
*ngIf="hasFamilySponsorshipAvailable"
>
{{ "sponsoredFamilies" | i18n }}
</a>
</div>
</div>
</div>
<div class="col-9">
<router-outlet></router-outlet>
</div>
</div>
</div>