1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-23 11:43:46 +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,476 +1,479 @@
<div class="page-header">
<h1>{{ "generator" | i18n }}</h1>
</div>
<app-callout type="info" *ngIf="enforcedPasswordPolicyOptions?.inEffect() && type === 'password'">
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="card card-generated bg-light my-4">
<div class="card-body">
<bit-color-password
[password]="type === 'password' ? password : username"
[appCopyText]="type === 'password' ? password : username"
></bit-color-password>
<app-header></app-header>
<bit-container>
<app-callout type="info" *ngIf="enforcedPasswordPolicyOptions?.inEffect() && type === 'password'">
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="card card-generated bg-light my-4">
<div class="card-body">
<bit-color-password
[password]="type === 'password' ? password : username"
[appCopyText]="type === 'password' ? password : username"
></bit-color-password>
</div>
</div>
</div>
<div class="form-group" role="radiogroup" aria-labelledby="typeHeading">
<label id="typeHeading" class="d-block">{{ "whatWouldYouLikeToGenerate" | i18n }}</label>
<div class="form-check form-check-inline" *ngFor="let o of typeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="type"
name="Type"
id="type_{{ o.value }}"
[value]="o.value"
(change)="typeChanged()"
[checked]="type === o.value"
/>
<label class="form-check-label" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<ng-container *ngIf="type === 'password'">
<div aria-labelledby="passwordTypeHeading" class="form-group" role="radiogroup">
<label id="passwordTypeHeading" class="d-block">{{ "passwordType" | i18n }}</label>
<div class="form-check form-check-inline" *ngFor="let o of passTypeOptions">
<div class="form-group" role="radiogroup" aria-labelledby="typeHeading">
<label id="typeHeading" class="d-block">{{ "whatWouldYouLikeToGenerate" | i18n }}</label>
<div class="form-check form-check-inline" *ngFor="let o of typeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="passwordOptions.type"
name="PasswordType"
id="passwordType_{{ o.value }}"
[(ngModel)]="type"
name="Type"
id="type_{{ o.value }}"
[value]="o.value"
(change)="savePasswordOptions()"
[checked]="passwordOptions.type === o.value"
(change)="typeChanged()"
[checked]="type === o.value"
/>
<label class="form-check-label" for="passwordType_{{ o.value }}">
<label class="form-check-label" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<ng-container *ngIf="passwordOptions.type === 'passphrase'">
<div class="row">
<div class="form-group col-4">
<label for="num-words">{{ "numWords" | i18n }}</label>
<ng-container *ngIf="type === 'password'">
<div aria-labelledby="passwordTypeHeading" class="form-group" role="radiogroup">
<label id="passwordTypeHeading" class="d-block">{{ "passwordType" | i18n }}</label>
<div class="form-check form-check-inline" *ngFor="let o of passTypeOptions">
<input
id="num-words"
class="form-control"
type="number"
min="3"
max="20"
[(ngModel)]="passwordOptions.numWords"
(blur)="savePasswordOptions()"
/>
</div>
<div class="form-group col-4">
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
class="form-control"
type="text"
maxlength="1"
[(ngModel)]="passwordOptions.wordSeparator"
(blur)="savePasswordOptions()"
/>
</div>
</div>
<label class="d-block">{{ "options" | i18n }}</label>
<div class="form-group">
<div class="form-check">
<input
id="capitalize"
class="form-check-input"
type="checkbox"
type="radio"
[(ngModel)]="passwordOptions.type"
name="PasswordType"
id="passwordType_{{ o.value }}"
[value]="o.value"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.capitalize"
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
[checked]="passwordOptions.type === o.value"
/>
<label for="capitalize" class="form-check-label">{{ "capitalize" | i18n }}</label>
</div>
<div class="form-check">
<input
id="include-number"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.includeNumber"
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
/>
<label for="include-number" class="form-check-label">{{ "includeNumber" | i18n }}</label>
</div>
</div>
</ng-container>
<ng-container *ngIf="passwordOptions.type === 'password'">
<div class="row">
<div class="form-group col-4">
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
class="form-control"
type="number"
[min]="passwordOptions.minLength"
max="128"
[(ngModel)]="passwordOptions.length"
(blur)="savePasswordOptions()"
(change)="lengthChanged()"
/>
</div>
<div class="form-group col-4">
<label for="min-length">{{ "passwordMinLength" | i18n }}</label>
<input
id="min-length"
class="form-control"
type="text"
readonly="true"
[value]="passwordOptions.length"
/>
<span
class="sr-only"
attr.aria-label="{{ 'passwordMinLength' | i18n }}"
role="status"
aria-live="polite"
>
{{ passwordOptionsMinLengthForReader$ | async }}
</span>
</div>
<div class="form-group col-4">
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
class="form-control"
type="number"
min="0"
max="9"
[(ngModel)]="passwordOptions.minNumber"
(input)="onPasswordOptionsMinNumberInput($event)"
(change)="minNumberChanged()"
/>
</div>
<div class="form-group col-4">
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
class="form-control"
type="number"
min="0"
max="9"
[(ngModel)]="passwordOptions.minSpecial"
(input)="onPasswordOptionsMinSpecialInput($event)"
(change)="minSpecialChanged()"
/>
</div>
</div>
<label class="d-block">{{ "options" | i18n }}</label>
<div class="form-group">
<div class="form-check">
<input
id="uppercase"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.uppercase"
[disabled]="enforcedPasswordPolicyOptions?.useUppercase"
attr.aria-label="{{ 'uppercase' | i18n }}"
/>
<label for="uppercase" class="form-check-label">A-Z</label>
</div>
<div class="form-check">
<input
id="lowercase"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.lowercase"
[disabled]="enforcedPasswordPolicyOptions?.useLowercase"
attr.aria-label="{{ 'lowercase' | i18n }}"
/>
<label for="lowercase" class="form-check-label">a-z</label>
</div>
<div class="form-check">
<input
id="numbers"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[ngModel]="passwordOptions.number"
(ngModelChange)="setPasswordOptionsNumber($event)"
[disabled]="enforcedPasswordPolicyOptions?.useNumbers"
attr.aria-label="{{ 'numbers' | i18n }}"
/>
<label for="numbers" class="form-check-label">0-9</label>
</div>
<div class="form-check">
<input
id="special"
class="form-check-input"
type="checkbox"
[ngModel]="passwordOptions.special"
(ngModelChange)="setPasswordOptionsSpecial($event)"
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
attr.aria-label="{{ 'specialCharacters' | i18n }}"
/>
<label for="special" class="form-check-label">!@#$%^&amp;*</label>
</div>
<div class="form-check">
<input
id="ambiguous"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="avoidAmbiguous"
/>
<label for="ambiguous" class="form-check-label">{{ "ambiguous" | i18n }}</label>
</div>
</div>
</ng-container>
<div class="d-flex">
<div>
<button type="button" class="btn btn-primary" (click)="regenerate()">
{{ "regeneratePassword" | i18n }}
</button>
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
{{ "copyPassword" | i18n }}
</button>
</div>
<div class="ml-auto">
<button
type="button"
class="btn btn-outline-secondary"
(click)="history()"
appA11yTitle="{{ 'passwordHistory' | i18n }}"
>
<i class="bwi bwi-clock bwi-lg" aria-hidden="true"></i>
</button>
</div>
</div>
</ng-container>
<ng-container *ngIf="type === 'username'">
<div aria-labelledby="usernameTypeHeading" class="form-group" role="radiogroup">
<div class="d-block">
<label id="usernameTypeHeading">{{ "usernameType" | i18n }}</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/generator/#username-types"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="form-check" *ngFor="let o of usernameTypeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="usernameOptions.type"
name="UsernameType"
id="usernameType_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.type === o.value"
/>
<label class="form-check-label" for="usernameType_{{ o.value }}">
{{ o.name }}
<div class="small text-muted">{{ o.desc }}</div>
</label>
</div>
</div>
<ng-container *ngIf="usernameOptions.type === 'forwarded'">
<div class="form-group" role="listbox">
<label class="d-block">{{ "service" | i18n }}</label>
<select
id="ForwardTypeDropdown"
name="ForwardType"
[(ngModel)]="usernameOptions.forwardedService"
(change)="saveUsernameOptions()"
class="form-control w-auto"
>
<option *ngFor="let o of forwardOptions" [ngValue]="o.value" role="option">
<label class="form-check-label" for="passwordType_{{ o.value }}">
{{ o.name }}
</option>
</select>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'simplelogin'">
<div class="form-group col-4">
<label for="simplelogin-apikey">{{ "apiKey" | i18n }}</label>
<input
id="simplelogin-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedSimpleLoginApiKey"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4" *ngIf="isSelfHosted">
<label for="simplelogin-baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="simplelogin-baseUrl"
class="form-control"
type="text"
name="SimpleLoginDomain"
[(ngModel)]="usernameOptions.forwardedSimpleLoginBaseUrl"
(blur)="saveUsernameOptions()"
/>
</label>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'duckduckgo'">
<div class="form-group col-4">
<label for="duckduckgo-apikey">{{ "apiKey" | i18n }}</label>
<input
id="duckduckgo-apikey"
class="form-control"
type="password"
name="DuckDuckGoApiKey"
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
(blur)="saveUsernameOptions()"
/>
<ng-container *ngIf="passwordOptions.type === 'passphrase'">
<div class="row">
<div class="form-group col-4">
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="num-words"
class="form-control"
type="number"
min="3"
max="20"
[(ngModel)]="passwordOptions.numWords"
(blur)="savePasswordOptions()"
/>
</div>
<div class="form-group col-4">
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
class="form-control"
type="text"
maxlength="1"
[(ngModel)]="passwordOptions.wordSeparator"
(blur)="savePasswordOptions()"
/>
</div>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'anonaddy'">
<div class="form-group col-4">
<label for="anonaddy-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="anonaddy-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedAnonAddyApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4">
<label for="anonaddy-domain">{{ "aliasDomain" | i18n }}</label>
<input
id="anonaddy-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.forwardedAnonAddyDomain"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4" *ngIf="isSelfHosted">
<label for="anonaddy-baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="anonaddy-baseUrl"
class="form-control"
type="text"
name="AnonAddyDomain"
[(ngModel)]="usernameOptions.forwardedAnonAddyBaseUrl"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'firefoxrelay'">
<div class="form-group col-4">
<label for="firefox-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="firefox-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedFirefoxApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'fastmail'">
<div class="form-group col-4">
<label for="fastmail-apiToken">{{ "apiAccessToken" | i18n }}</label>
<input
id="fastmail-apiToken"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedFastmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'forwardemail'">
<div class="form-group col-4">
<label for="forwardemail-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="forwardemail-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedForwardEmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4">
<label for="forwardemail-domain">{{ "aliasDomain" | i18n }}</label>
<input
id="forwardemail-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.forwardedForwardEmailDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
</ng-container>
<div class="row" *ngIf="usernameOptions.type === 'subaddress'">
<div class="form-group col-4">
<label for="subaddress-email">{{ "emailAddress" | i18n }}</label>
<input
id="subaddress-email"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.subaddressEmail"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.type === 'catchall'">
<div class="form-group col-4">
<label for="catchall-domain">{{ "domainName" | i18n }}</label>
<input
id="catchall-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.catchallDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<ng-container *ngIf="usernameOptions.type === 'word'">
<label class="d-block">{{ "options" | i18n }}</label>
<div class="row">
<label class="d-block">{{ "options" | i18n }}</label>
<div class="form-group">
<div class="form-check">
<input
id="capitalizeUsername"
id="capitalize"
class="form-check-input"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordCapitalize"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.capitalize"
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
/>
<label for="capitalizeUsername" class="form-check-label">{{ "capitalize" | i18n }}</label>
<label for="capitalize" class="form-check-label">{{ "capitalize" | i18n }}</label>
</div>
<div class="form-check">
<input
id="includeNumberUsername"
id="include-number"
class="form-check-input"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordIncludeNumber"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.includeNumber"
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
/>
<label for="includeNumberUsername" class="form-check-label">{{
"includeNumber" | i18n
}}</label>
<label for="include-number" class="form-check-label">{{ "includeNumber" | i18n }}</label>
</div>
</div>
</ng-container>
<ng-container *ngIf="passwordOptions.type === 'password'">
<div class="row">
<div class="form-group col-4">
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
class="form-control"
type="number"
[min]="passwordOptions.minLength"
max="128"
[(ngModel)]="passwordOptions.length"
(blur)="savePasswordOptions()"
(change)="lengthChanged()"
/>
</div>
<div class="form-group col-4">
<label for="min-length">{{ "passwordMinLength" | i18n }}</label>
<input
id="min-length"
class="form-control"
type="text"
readonly="true"
[value]="passwordOptions.length"
/>
<span
class="sr-only"
attr.aria-label="{{ 'passwordMinLength' | i18n }}"
role="status"
aria-live="polite"
>
{{ passwordOptionsMinLengthForReader$ | async }}
</span>
</div>
<div class="form-group col-4">
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
class="form-control"
type="number"
min="0"
max="9"
[(ngModel)]="passwordOptions.minNumber"
(input)="onPasswordOptionsMinNumberInput($event)"
(change)="minNumberChanged()"
/>
</div>
<div class="form-group col-4">
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
class="form-control"
type="number"
min="0"
max="9"
[(ngModel)]="passwordOptions.minSpecial"
(input)="onPasswordOptionsMinSpecialInput($event)"
(change)="minSpecialChanged()"
/>
</div>
</div>
<label class="d-block">{{ "options" | i18n }}</label>
<div class="form-group">
<div class="form-check">
<input
id="uppercase"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.uppercase"
[disabled]="enforcedPasswordPolicyOptions?.useUppercase"
attr.aria-label="{{ 'uppercase' | i18n }}"
/>
<label for="uppercase" class="form-check-label">A-Z</label>
</div>
<div class="form-check">
<input
id="lowercase"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.lowercase"
[disabled]="enforcedPasswordPolicyOptions?.useLowercase"
attr.aria-label="{{ 'lowercase' | i18n }}"
/>
<label for="lowercase" class="form-check-label">a-z</label>
</div>
<div class="form-check">
<input
id="numbers"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[ngModel]="passwordOptions.number"
(ngModelChange)="setPasswordOptionsNumber($event)"
[disabled]="enforcedPasswordPolicyOptions?.useNumbers"
attr.aria-label="{{ 'numbers' | i18n }}"
/>
<label for="numbers" class="form-check-label">0-9</label>
</div>
<div class="form-check">
<input
id="special"
class="form-check-input"
type="checkbox"
[ngModel]="passwordOptions.special"
(ngModelChange)="setPasswordOptionsSpecial($event)"
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
attr.aria-label="{{ 'specialCharacters' | i18n }}"
/>
<label for="special" class="form-check-label">!@#$%^&amp;*</label>
</div>
<div class="form-check">
<input
id="ambiguous"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="avoidAmbiguous"
/>
<label for="ambiguous" class="form-check-label">{{ "ambiguous" | i18n }}</label>
</div>
</div>
</ng-container>
<div class="d-flex">
<div>
<button type="button" class="btn btn-primary" (click)="regenerate()">
{{ "regeneratePassword" | i18n }}
</button>
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
{{ "copyPassword" | i18n }}
</button>
</div>
<div class="ml-auto">
<button
type="button"
class="btn btn-outline-secondary"
(click)="history()"
appA11yTitle="{{ 'passwordHistory' | i18n }}"
>
<i class="bwi bwi-clock bwi-lg" aria-hidden="true"></i>
</button>
</div>
</div>
</ng-container>
<div #form [appApiAction]="usernameGeneratingPromise">
<button
type="button"
class="btn btn-submit btn-primary"
(click)="$any(form).loading ? false : regenerate()"
[attr.aria-disabled]="$any(form).loading ? 'true' : null"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "regenerateUsername" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
{{ "copyUsername" | i18n }}
</button>
</div>
</ng-container>
<ng-template #historyTemplate></ng-template>
<ng-container *ngIf="type === 'username'">
<div aria-labelledby="usernameTypeHeading" class="form-group" role="radiogroup">
<div class="d-block">
<label id="usernameTypeHeading">{{ "usernameType" | i18n }}</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/generator/#username-types"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="form-check" *ngFor="let o of usernameTypeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="usernameOptions.type"
name="UsernameType"
id="usernameType_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.type === o.value"
/>
<label class="form-check-label" for="usernameType_{{ o.value }}">
{{ o.name }}
<div class="small text-muted">{{ o.desc }}</div>
</label>
</div>
</div>
<ng-container *ngIf="usernameOptions.type === 'forwarded'">
<div class="form-group" role="listbox">
<label class="d-block">{{ "service" | i18n }}</label>
<select
id="ForwardTypeDropdown"
name="ForwardType"
[(ngModel)]="usernameOptions.forwardedService"
(change)="saveUsernameOptions()"
class="form-control w-auto"
>
<option *ngFor="let o of forwardOptions" [ngValue]="o.value" role="option">
{{ o.name }}
</option>
</select>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'simplelogin'">
<div class="form-group col-4">
<label for="simplelogin-apikey">{{ "apiKey" | i18n }}</label>
<input
id="simplelogin-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedSimpleLoginApiKey"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4" *ngIf="isSelfHosted">
<label for="simplelogin-baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="simplelogin-baseUrl"
class="form-control"
type="text"
name="SimpleLoginDomain"
[(ngModel)]="usernameOptions.forwardedSimpleLoginBaseUrl"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'duckduckgo'">
<div class="form-group col-4">
<label for="duckduckgo-apikey">{{ "apiKey" | i18n }}</label>
<input
id="duckduckgo-apikey"
class="form-control"
type="password"
name="DuckDuckGoApiKey"
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'anonaddy'">
<div class="form-group col-4">
<label for="anonaddy-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="anonaddy-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedAnonAddyApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4">
<label for="anonaddy-domain">{{ "aliasDomain" | i18n }}</label>
<input
id="anonaddy-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.forwardedAnonAddyDomain"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4" *ngIf="isSelfHosted">
<label for="anonaddy-baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="anonaddy-baseUrl"
class="form-control"
type="text"
name="AnonAddyDomain"
[(ngModel)]="usernameOptions.forwardedAnonAddyBaseUrl"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'firefoxrelay'">
<div class="form-group col-4">
<label for="firefox-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="firefox-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedFirefoxApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'fastmail'">
<div class="form-group col-4">
<label for="fastmail-apiToken">{{ "apiAccessToken" | i18n }}</label>
<input
id="fastmail-apiToken"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedFastmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'forwardemail'">
<div class="form-group col-4">
<label for="forwardemail-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="forwardemail-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedForwardEmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4">
<label for="forwardemail-domain">{{ "aliasDomain" | i18n }}</label>
<input
id="forwardemail-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.forwardedForwardEmailDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
</ng-container>
<div class="row" *ngIf="usernameOptions.type === 'subaddress'">
<div class="form-group col-4">
<label for="subaddress-email">{{ "emailAddress" | i18n }}</label>
<input
id="subaddress-email"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.subaddressEmail"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.type === 'catchall'">
<div class="form-group col-4">
<label for="catchall-domain">{{ "domainName" | i18n }}</label>
<input
id="catchall-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.catchallDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<ng-container *ngIf="usernameOptions.type === 'word'">
<label class="d-block">{{ "options" | i18n }}</label>
<div class="row">
<div class="form-group">
<div class="form-check">
<input
id="capitalizeUsername"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordCapitalize"
/>
<label for="capitalizeUsername" class="form-check-label">{{
"capitalize" | i18n
}}</label>
</div>
<div class="form-check">
<input
id="includeNumberUsername"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordIncludeNumber"
/>
<label for="includeNumberUsername" class="form-check-label">{{
"includeNumber" | i18n
}}</label>
</div>
</div>
</div>
</ng-container>
<div #form [appApiAction]="usernameGeneratingPromise">
<button
type="button"
class="btn btn-submit btn-primary"
(click)="$any(form).loading ? false : regenerate()"
[attr.aria-disabled]="$any(form).loading ? 'true' : null"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "regenerateUsername" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
{{ "copyUsername" | i18n }}
</button>
</div>
</ng-container>
<ng-template #historyTemplate></ng-template>
</bit-container>

View File

@@ -1,24 +0,0 @@
import { Component } from "@angular/core";
import { ImportCollectionServiceAbstraction } from "@bitwarden/importer/core";
import { ImportComponent } from "@bitwarden/importer/ui";
import { SharedModule } from "../../shared";
import { CollectionAdminService } from "../../vault/core/collection-admin.service";
import { ImportCollectionAdminService } from "./import-collection-admin.service";
import { ImportWebComponent } from "./import-web.component";
@Component({
templateUrl: "import-web.component.html",
standalone: true,
imports: [SharedModule, ImportComponent],
providers: [
{
provide: ImportCollectionServiceAbstraction,
useClass: ImportCollectionAdminService,
deps: [CollectionAdminService],
},
],
})
export class AdminImportComponent extends ImportWebComponent {}

View File

@@ -1,18 +1,20 @@
<h1 bitTypography="h1">{{ "importData" | i18n }}</h1>
<tools-import
(formDisabled)="this.disabled = $event"
(formLoading)="this.loading = $event"
(onSuccessfulImport)="this.onSuccessfulImport($event)"
organizationId="{{ routeOrgId }}"
></tools-import>
<button
[disabled]="disabled"
[loading]="loading"
form="import_form_importForm"
bitButton
type="submit"
bitFormButton
buttonType="primary"
>
{{ "importData" | i18n }}
</button>
<app-header></app-header>
<bit-container>
<tools-import
(formDisabled)="this.disabled = $event"
(formLoading)="this.loading = $event"
(onSuccessfulImport)="this.onSuccessfulImport($event)"
></tools-import>
<button
[disabled]="disabled"
[loading]="loading"
form="import_form_importForm"
bitButton
type="submit"
bitFormButton
buttonType="primary"
>
{{ "importData" | i18n }}
</button>
</bit-container>

View File

@@ -1,51 +1,26 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import {
OrganizationService,
canAccessVaultTab,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { ImportComponent } from "@bitwarden/importer/ui";
import { HeaderModule } from "../../layouts/header/header.module";
import { SharedModule } from "../../shared";
@Component({
templateUrl: "import-web.component.html",
standalone: true,
imports: [SharedModule, ImportComponent],
imports: [SharedModule, ImportComponent, HeaderModule],
})
export class ImportWebComponent implements OnInit {
protected routeOrgId: string = null;
export class ImportWebComponent {
protected loading = false;
protected disabled = false;
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
private router: Router,
) {}
ngOnInit(): void {
this.routeOrgId = this.route.snapshot.paramMap.get("organizationId");
}
constructor(private router: Router) {}
/**
* Callback that is called after a successful import.
*/
protected async onSuccessfulImport(organizationId: string): Promise<void> {
if (!this.routeOrgId) {
await this.router.navigate(["vault"]);
return;
}
const organization = await firstValueFrom(this.organizationService.get$(organizationId));
if (organization == null) {
return;
}
if (canAccessVaultTab(organization)) {
await this.router.navigate(["organizations", organizationId, "vault"]);
}
await this.router.navigate(["vault"]);
}
}

View File

@@ -1,63 +1,64 @@
<div class="page-header">
<h1>{{ "dataBreachReport" | i18n }}</h1>
</div>
<p>{{ "breachDesc" | i18n }}</p>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="row">
<div class="form-group col-6">
<label for="username">{{ "username" | i18n }}</label>
<input
id="username"
type="text"
name="Username"
class="form-control"
[(ngModel)]="username"
required
/>
<small class="form-text text-muted">{{ "breachCheckUsernameEmail" | i18n }}</small>
<app-header></app-header>
<bit-container>
<p>{{ "breachDesc" | i18n }}</p>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="row">
<div class="form-group col-6">
<label for="username">{{ "username" | i18n }}</label>
<input
id="username"
type="text"
name="Username"
class="form-control"
[(ngModel)]="username"
required
/>
<small class="form-text text-muted">{{ "breachCheckUsernameEmail" | i18n }}</small>
</div>
</div>
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
{{ "checkBreaches" | i18n }}
</button>
</form>
<div class="mt-4" *ngIf="!form.loading && checkedUsername">
<p *ngIf="error">{{ "reportError" | i18n }}...</p>
<ng-container *ngIf="!error">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!breachedAccounts.length">
{{ "breachUsernameNotFound" | i18n: checkedUsername }}
</app-callout>
<app-callout type="danger" title="{{ 'breachFound' | i18n }}" *ngIf="breachedAccounts.length">
{{ "breachUsernameFound" | i18n: checkedUsername : breachedAccounts.length }}
</app-callout>
<ul class="list-group list-group-breach" *ngIf="breachedAccounts.length">
<li *ngFor="let a of breachedAccounts" class="list-group-item min-height-fix">
<div class="row">
<div class="col-2 text-center">
<img [src]="a.logoPath" alt="" class="img-fluid" />
</div>
<div class="col-7">
<h3 class="text-lg">{{ a.title }}</h3>
<p [innerHTML]="a.description"></p>
<p class="mb-1">{{ "compromisedData" | i18n }}:</p>
<ul>
<li *ngFor="let d of a.dataClasses">{{ d }}</li>
</ul>
</div>
<div class="col-3">
<dl>
<dt>{{ "website" | i18n }}</dt>
<dd>{{ a.domain }}</dd>
<dt>{{ "affectedUsers" | i18n }}</dt>
<dd>{{ a.pwnCount | number }}</dd>
<dt>{{ "breachOccurred" | i18n }}</dt>
<dd>{{ a.breachDate | date: "mediumDate" }}</dd>
<dt>{{ "breachReported" | i18n }}</dt>
<dd>{{ a.addedDate | date: "mediumDate" }}</dd>
</dl>
</div>
</div>
</li>
</ul>
</ng-container>
</div>
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
{{ "checkBreaches" | i18n }}
</button>
</form>
<div class="mt-4" *ngIf="!form.loading && checkedUsername">
<p *ngIf="error">{{ "reportError" | i18n }}...</p>
<ng-container *ngIf="!error">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!breachedAccounts.length">
{{ "breachUsernameNotFound" | i18n: checkedUsername }}
</app-callout>
<app-callout type="danger" title="{{ 'breachFound' | i18n }}" *ngIf="breachedAccounts.length">
{{ "breachUsernameFound" | i18n: checkedUsername : breachedAccounts.length }}
</app-callout>
<ul class="list-group list-group-breach" *ngIf="breachedAccounts.length">
<li *ngFor="let a of breachedAccounts" class="list-group-item min-height-fix">
<div class="row">
<div class="col-2 text-center">
<img [src]="a.logoPath" alt="" class="img-fluid" />
</div>
<div class="col-7">
<h3 class="text-lg">{{ a.title }}</h3>
<p [innerHTML]="a.description"></p>
<p class="mb-1">{{ "compromisedData" | i18n }}:</p>
<ul>
<li *ngFor="let d of a.dataClasses">{{ d }}</li>
</ul>
</div>
<div class="col-3">
<dl>
<dt>{{ "website" | i18n }}</dt>
<dd>{{ a.domain }}</dd>
<dt>{{ "affectedUsers" | i18n }}</dt>
<dd>{{ a.pwnCount | number }}</dd>
<dt>{{ "breachOccurred" | i18n }}</dt>
<dd>{{ a.breachDate | date: "mediumDate" }}</dd>
<dt>{{ "breachReported" | i18n }}</dt>
<dd>{{ a.addedDate | date: "mediumDate" }}</dd>
</dl>
</div>
</div>
</li>
</ul>
</ng-container>
</div>
</bit-container>

View File

@@ -1,72 +1,77 @@
<div class="page-header">
<h1>{{ "exposedPasswordsReport" | i18n }}</h1>
</div>
<p>{{ "exposedPasswordsReportDesc" | i18n }}</p>
<button type="submit" buttonType="primary" bitButton [loading]="loading" (click)="load()">
{{ "checkExposedPasswords" | i18n }}
</button>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
{{ "noExposedPasswords" | i18n }}
</app-callout>
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{ 'exposedPasswordsFound' | i18n }}" [useAlertRole]="true">
{{ "exposedPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
<app-header></app-header>
<bit-container>
<p>{{ "exposedPasswordsReportDesc" | i18n }}</p>
<button type="submit" buttonType="primary" bitButton [loading]="loading" (click)="load()">
{{ "checkExposedPasswords" | i18n }}
</button>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
{{ "noExposedPasswords" | i18n }}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
c.name
}}</a>
</ng-container>
<ng-template #cantManage>
<span>{{ c.name }}</span>
</ng-template>
<ng-container *ngIf="!organization && c.organizationId">
<i
class="bwi bwi-collection"
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{ 'exposedPasswordsFound' | i18n }}" [useAlertRole]="true">
{{ "exposedPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
<a
href="#"
appStopClick
(click)="selectCipher(c)"
title="{{ 'editItem' | i18n }}"
>{{ c.name }}</a
>
</ng-container>
<ng-template #cantManage>
<span>{{ c.name }}</span>
</ng-template>
<ng-container *ngIf="!organization && c.organizationId">
<i
class="bwi bwi-collection"
appStopProp
title="{{ 'shared' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="bwi bwi-paperclip"
appStopProp
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
<br />
<small>{{ c.subTitle }}</small>
</td>
<td>
<app-org-badge
*ngIf="!organization"
[disabled]="disabled"
[organizationId]="c.organizationId"
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
title="{{ 'shared' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="bwi bwi-paperclip"
appStopProp
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
<br />
<small>{{ c.subTitle }}</small>
</td>
<td>
<app-org-badge
*ngIf="!organization"
[disabled]="disabled"
[organizationId]="c.organizationId"
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
>
</app-org-badge>
</td>
<td class="text-right">
<span bitBadge variant="warning">
{{ "exposedXTimes" | i18n: (exposedPasswordMap.get(c.id) | number) }}
</span>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
>
</app-org-badge>
</td>
<td class="text-right">
<span bitBadge variant="warning">
{{ "exposedXTimes" | i18n: (exposedPasswordMap.get(c.id) | number) }}
</span>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
</bit-container>

View File

@@ -1,88 +1,79 @@
<div class="page-header">
<h1>
{{ "inactive2faReport" | i18n }}
<small *ngIf="hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</small>
</h1>
</div>
<p>{{ "inactive2faReportDesc" | i18n }}</p>
<div *ngIf="!hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
{{ "noInactive2fa" | i18n }}
</app-callout>
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{ 'inactive2faFound' | i18n }}">
{{ "inactive2faFoundDesc" | i18n: (ciphers.length | number) }}
<app-header></app-header>
<bit-container>
<p>{{ "inactive2faReportDesc" | i18n }}</p>
<div *ngIf="!hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
{{ "noInactive2fa" | i18n }}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
c.name
}}</a>
<ng-container *ngIf="!organization && c.organizationId">
<i
class="bwi bwi-collection"
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{ 'inactive2faFound' | i18n }}">
{{ "inactive2faFoundDesc" | i18n: (ciphers.length | number) }}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
c.name
}}</a>
<ng-container *ngIf="!organization && c.organizationId">
<i
class="bwi bwi-collection"
appStopProp
title="{{ 'shared' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="bwi bwi-paperclip"
appStopProp
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
<br />
<small>{{ c.subTitle }}</small>
</td>
<td>
<app-org-badge
*ngIf="!organization"
[disabled]="disabled"
[organizationId]="c.organizationId"
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
title="{{ 'shared' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="bwi bwi-paperclip"
appStopProp
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
<br />
<small>{{ c.subTitle }}</small>
</td>
<td>
<app-org-badge
*ngIf="!organization"
[disabled]="disabled"
[organizationId]="c.organizationId"
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
>
</app-org-badge>
</td>
<td class="text-right">
<a
bitBadge
href="{{ cipherDocs.get(c.id) }}"
target="_blank"
rel="noreferrer"
*ngIf="cipherDocs.has(c.id)"
>
{{ "instructions" | i18n }}</a
>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
>
</app-org-badge>
</td>
<td class="text-right">
<a
bitBadge
href="{{ cipherDocs.get(c.id) }}"
target="_blank"
rel="noreferrer"
*ngIf="cipherDocs.has(c.id)"
>
{{ "instructions" | i18n }}</a
>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
</bit-container>

View File

@@ -1,7 +1,7 @@
<div class="page-header">
<h1>{{ "reports" | i18n }}</h1>
</div>
<app-header></app-header>
<p>{{ "reportsDesc" | i18n }}</p>
<bit-container>
<p>{{ "reportsDesc" | i18n }}</p>
<app-report-list [reports]="reports"></app-report-list>
<app-report-list [reports]="reports"></app-report-list>
</bit-container>

View File

@@ -1,87 +1,82 @@
<div class="page-header">
<h1>
{{ "reusedPasswordsReport" | i18n }}
<small *ngIf="hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</small>
</h1>
</div>
<p>{{ "reusedPasswordsReportDesc" | i18n }}</p>
<div *ngIf="!hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
{{ "noReusedPasswords" | i18n }}
</app-callout>
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{ 'reusedPasswordsFound' | i18n }}">
{{ "reusedPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
<app-header></app-header>
<bit-container>
<p>{{ "reusedPasswordsReportDesc" | i18n }}</p>
<div *ngIf="!hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
{{ "noReusedPasswords" | i18n }}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
c.name
}}</a>
</ng-container>
<ng-template #cantManage>
<span>{{ c.name }}</span>
</ng-template>
<ng-container *ngIf="!organization && c.organizationId">
<i
class="bwi bwi-collection"
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{ 'reusedPasswordsFound' | i18n }}">
{{ "reusedPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
<a
href="#"
appStopClick
(click)="selectCipher(c)"
title="{{ 'editItem' | i18n }}"
>{{ c.name }}</a
>
</ng-container>
<ng-template #cantManage>
<span>{{ c.name }}</span>
</ng-template>
<ng-container *ngIf="!organization && c.organizationId">
<i
class="bwi bwi-collection"
appStopProp
title="{{ 'shared' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="bwi bwi-paperclip"
appStopProp
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
<br />
<small>{{ c.subTitle }}</small>
</td>
<td>
<app-org-badge
*ngIf="!organization"
[disabled]="disabled"
[organizationId]="c.organizationId"
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
title="{{ 'shared' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="bwi bwi-paperclip"
appStopProp
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
<br />
<small>{{ c.subTitle }}</small>
</td>
<td>
<app-org-badge
*ngIf="!organization"
[disabled]="disabled"
[organizationId]="c.organizationId"
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
>
</app-org-badge>
</td>
<td class="text-right">
<span bitBadge variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(c.login.password) }}
</span>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
>
</app-org-badge>
</td>
<td class="text-right">
<span bitBadge variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(c.login.password) }}
</span>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
</bit-container>

View File

@@ -1,77 +1,68 @@
<div class="page-header">
<h1>
{{ "unsecuredWebsitesReport" | i18n }}
<small *ngIf="hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</small>
</h1>
</div>
<p>{{ "unsecuredWebsitesReportDesc" | i18n }}</p>
<div *ngIf="!hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
{{ "noUnsecuredWebsites" | i18n }}
</app-callout>
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{ 'unsecuredWebsitesFound' | i18n }}">
{{ "unsecuredWebsitesFoundDesc" | i18n: (ciphers.length | number) }}
<app-header></app-header>
<bit-container>
<p>{{ "unsecuredWebsitesReportDesc" | i18n }}</p>
<div *ngIf="!hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
{{ "noUnsecuredWebsites" | i18n }}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
c.name
}}</a>
<ng-container *ngIf="!organization && c.organizationId">
<i
class="bwi bwi-collection"
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{ 'unsecuredWebsitesFound' | i18n }}">
{{ "unsecuredWebsitesFoundDesc" | i18n: (ciphers.length | number) }}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
c.name
}}</a>
<ng-container *ngIf="!organization && c.organizationId">
<i
class="bwi bwi-collection"
appStopProp
title="{{ 'shared' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="bwi bwi-paperclip"
appStopProp
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
<br />
<small>{{ c.subTitle }}</small>
</td>
<td>
<app-org-badge
*ngIf="!organization"
[disabled]="disabled"
[organizationId]="c.organizationId"
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
title="{{ 'shared' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="bwi bwi-paperclip"
appStopProp
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
<br />
<small>{{ c.subTitle }}</small>
</td>
<td>
<app-org-badge
*ngIf="!organization"
[disabled]="disabled"
[organizationId]="c.organizationId"
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
>
</app-org-badge>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
>
</app-org-badge>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
</bit-container>

View File

@@ -1,87 +1,82 @@
<div class="page-header">
<h1>
{{ "weakPasswordsReport" | i18n }}
<small *ngIf="hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</small>
</h1>
</div>
<p>{{ "weakPasswordsReportDesc" | i18n }}</p>
<div *ngIf="!hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
{{ "noWeakPasswords" | i18n }}
</app-callout>
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{ 'weakPasswordsFound' | i18n }}">
{{ "weakPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
<app-header></app-header>
<bit-container>
<p>{{ "weakPasswordsReportDesc" | i18n }}</p>
<div *ngIf="!hasLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="mt-4" *ngIf="hasLoaded">
<app-callout type="success" title="{{ 'goodNews' | i18n }}" *ngIf="!ciphers.length">
{{ "noWeakPasswords" | i18n }}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
c.name
}}</a>
</ng-container>
<ng-template #cantManage>
<span>{{ c.name }}</span>
</ng-template>
<ng-container *ngIf="!organization && c.organizationId">
<i
class="bwi bwi-collection"
<ng-container *ngIf="ciphers.length">
<app-callout type="danger" title="{{ 'weakPasswordsFound' | i18n }}">
{{ "weakPasswordsFoundDesc" | i18n: (ciphers.length | number) }}
</app-callout>
<table class="table table-hover table-list table-ciphers">
<tbody>
<tr *ngFor="let c of ciphers">
<td class="table-list-icon">
<app-vault-icon [cipher]="c"></app-vault-icon>
</td>
<td class="reduced-lh wrap">
<ng-container *ngIf="!organization || canManageCipher(c); else cantManage">
<a
href="#"
appStopClick
(click)="selectCipher(c)"
title="{{ 'editItem' | i18n }}"
>{{ c.name }}</a
>
</ng-container>
<ng-template #cantManage>
<span>{{ c.name }}</span>
</ng-template>
<ng-container *ngIf="!organization && c.organizationId">
<i
class="bwi bwi-collection"
appStopProp
title="{{ 'shared' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="bwi bwi-paperclip"
appStopProp
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
<br />
<small>{{ c.subTitle }}</small>
</td>
<td>
<app-org-badge
*ngIf="!organization"
[disabled]="disabled"
[organizationId]="c.organizationId"
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
title="{{ 'shared' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="bwi bwi-paperclip"
appStopProp
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
<br />
<small>{{ c.subTitle }}</small>
</td>
<td>
<app-org-badge
*ngIf="!organization"
[disabled]="disabled"
[organizationId]="c.organizationId"
[organizationName]="c.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
>
</app-org-badge>
</td>
<td class="text-right">
<span bitBadge [variant]="passwordStrengthMap.get(c.id)[1]">
{{ passwordStrengthMap.get(c.id)[0] | i18n }}
</span>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
>
</app-org-badge>
</td>
<td class="text-right">
<span bitBadge [variant]="passwordStrengthMap.get(c.id)[1]">
{{ passwordStrengthMap.get(c.id)[0] | i18n }}
</span>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
</bit-container>

View File

@@ -1,12 +1,10 @@
<div class="container page-content">
<router-outlet></router-outlet>
<router-outlet></router-outlet>
<div class="row mt-4">
<div class="col">
<a bitButton routerLink="./" *ngIf="!homepage">
<i class="bwi bwi-angle-left" aria-hidden="true"></i>
{{ "backToReports" | i18n }}
</a>
</div>
<div class="row mt-4">
<div class="col">
<a bitButton routerLink="./" *ngIf="!homepage">
<i class="bwi bwi-angle-left" aria-hidden="true"></i>
{{ "backToReports" | i18n }}
</a>
</div>
</div>

View File

@@ -20,7 +20,12 @@ const routes: Routes = [
component: ReportsLayoutComponent,
canActivate: [AuthGuard],
children: [
{ path: "", pathMatch: "full", component: ReportsHomeComponent, data: { homepage: true } },
{
path: "",
pathMatch: "full",
component: ReportsHomeComponent,
data: { titleId: "reports", homepage: true },
},
{
path: "breach-report",
component: BreachReportComponent,

View File

@@ -1,6 +1,7 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { HeaderModule } from "../../layouts/header/header.module";
import { SharedModule } from "../../shared";
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
@@ -24,6 +25,7 @@ import { ReportsSharedModule } from "./shared";
ReportsRoutingModule,
OrganizationBadgeModule,
PipesModule,
HeaderModule,
],
declarations: [
BreachReportComponent,

View File

@@ -1,221 +1,206 @@
<div class="container page-content">
<bit-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
{{ "sendDisabledWarning" | i18n }}
</bit-callout>
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
<div class="groupings tw-col-span-3">
<div class="card vault-filters">
<div class="card-header d-flex">
{{ "filters" | i18n }}
<app-header>
<ng-container slot="title-suffix">
<small #actionSpinner [appApiAction]="actionPromise">
<ng-container *ngIf="$any(actionSpinner).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>
</small>
</ng-container>
<button type="button" bitButton buttonType="primary" (click)="addSend()" [disabled]="disableSend">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "createSend" | i18n }}
</button>
</app-header>
<bit-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
{{ "sendDisabledWarning" | i18n }}
</bit-callout>
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
<div class="groupings tw-col-span-3">
<div class="card vault-filters">
<div class="card-header d-flex">
{{ "filters" | i18n }}
</div>
<div class="card-body">
<div class="tw-mb-4">
<bit-search
[(ngModel)]="searchText"
[placeholder]="'searchSends' | i18n"
(input)="searchTextChanged()"
appAutofocus
/>
</div>
<div class="card-body">
<div class="tw-mb-4">
<bit-search
[(ngModel)]="searchText"
[placeholder]="'searchSends' | i18n"
(input)="searchTextChanged()"
appAutofocus
/>
</div>
<div class="filter">
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: selectedAll }">
<span class="filter-buttons">
<button type="button" class="filter-button" appStopClick (click)="selectAll()">
<i class="bwi bwi-fw bwi-filter"></i>{{ "allSends" | i18n }}
</button>
</span>
</li>
</ul>
</div>
<div class="filter">
<div class="filter-heading">
<h3>{{ "types" | i18n }}</h3>
</div>
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.Text }">
<span class="filter-buttons">
<button
type="button"
class="filter-button"
appStopClick
(click)="selectType(sendType.Text)"
>
<i class="bwi bwi-fw bwi-file-text"></i>{{ "sendTypeText" | i18n }}
</button>
</span>
</li>
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.File }">
<span class="filter-buttons">
<button
type="button"
class="filter-button"
appStopClick
(click)="selectType(sendType.File)"
>
<i class="bwi bwi-fw bwi-file"></i>{{ "sendTypeFile" | i18n }}
</button>
</span>
</li>
</ul>
<div class="filter">
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: selectedAll }">
<span class="filter-buttons">
<button type="button" class="filter-button" appStopClick (click)="selectAll()">
<i class="bwi bwi-fw bwi-filter"></i>{{ "allSends" | i18n }}
</button>
</span>
</li>
</ul>
</div>
<div class="filter">
<div class="filter-heading">
<h3>{{ "types" | i18n }}</h3>
</div>
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.Text }">
<span class="filter-buttons">
<button
type="button"
class="filter-button"
appStopClick
(click)="selectType(sendType.Text)"
>
<i class="bwi bwi-fw bwi-file-text"></i>{{ "sendTypeText" | i18n }}
</button>
</span>
</li>
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.File }">
<span class="filter-buttons">
<button
type="button"
class="filter-button"
appStopClick
(click)="selectType(sendType.File)"
>
<i class="bwi bwi-fw bwi-file"></i>{{ "sendTypeFile" | i18n }}
</button>
</span>
</li>
</ul>
</div>
</div>
</div>
<div class="tw-col-span-9">
<div class="tw-flex">
<h1 bitTypography="h1">
{{ "send" | i18n }}
<small #actionSpinner [appApiAction]="actionPromise">
<ng-container *ngIf="$any(actionSpinner).loading">
</div>
<div class="tw-col-span-9">
<!--Listing Table-->
<bit-table [dataSource]="dataSource" *ngIf="filteredSends && filteredSends.length">
<ng-container header>
<tr>
<th bitCell bitSortable="name" default>{{ "name" | i18n }}</th>
<th bitCell bitSortable="deletionDate">{{ "deletionDate" | i18n }}</th>
<th bitCell>{{ "options" | i18n }}</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let s of rows$ | async">
<td bitCell (click)="editSend(s)" class="tw-cursor-pointer">
<span class="tw-mr-2" aria-hidden="true">
<i class="bwi bwi-fw bwi-lg bwi-file" *ngIf="s.type == sendType.File"></i>
<i class="bwi bwi-fw bwi-lg bwi-file-text" *ngIf="s.type == sendType.Text"></i>
</span>
<button type="button" bitLink>
{{ s.name }}
</button>
<ng-container *ngIf="s.disabled">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
class="bwi bwi-exclamation-triangle"
appStopProp
title="{{ 'disabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
<span class="sr-only">{{ "disabled" | i18n }}</span>
</ng-container>
</small>
</h1>
<div class="tw-ml-auto">
<button
type="button"
bitButton
buttonType="primary"
(click)="addSend()"
[disabled]="disableSend"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "createSend" | i18n }}
</button>
</div>
</div>
<!--Listing Table-->
<bit-table [dataSource]="dataSource" *ngIf="filteredSends && filteredSends.length">
<ng-container header>
<tr>
<th bitCell bitSortable="name" default>{{ "name" | i18n }}</th>
<th bitCell bitSortable="deletionDate">{{ "deletionDate" | i18n }}</th>
<th bitCell>{{ "options" | i18n }}</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let s of rows$ | async">
<td bitCell (click)="editSend(s)" class="tw-cursor-pointer">
<span class="tw-mr-2" aria-hidden="true">
<i class="bwi bwi-fw bwi-lg bwi-file" *ngIf="s.type == sendType.File"></i>
<i class="bwi bwi-fw bwi-lg bwi-file-text" *ngIf="s.type == sendType.Text"></i>
</span>
<button type="button" bitLink>
{{ s.name }}
<ng-container *ngIf="s.password">
<i
class="bwi bwi-key"
appStopProp
title="{{ 'password' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "password" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.maxAccessCountReached">
<i
class="bwi bwi-ban"
appStopProp
title="{{ 'maxAccessCountReached' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "maxAccessCountReached" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.expired">
<i
class="bwi bwi-clock"
appStopProp
title="{{ 'expired' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "expired" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
<i
class="bwi bwi-trash"
appStopProp
title="{{ 'pendingDeletion' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
</ng-container>
</td>
<td bitCell class="tw-text-muted" (click)="editSend(s)" class="tw-cursor-pointer">
<small bitTypography="body2" appStopProp>{{ s.deletionDate | date: "medium" }}</small>
</td>
<td bitCell class="tw-w-0 tw-text-right">
<button
type="button"
[bitMenuTriggerFor]="sendOptions"
bitIconButton="bwi-ellipsis-v"
appA11yTitle="{{ 'options' | i18n }}"
></button>
<bit-menu #sendOptions>
<button type="button" bitMenuItem (click)="copy(s)">
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySendLink" | i18n }}
</button>
<ng-container *ngIf="s.disabled">
<i
class="bwi bwi-exclamation-triangle"
appStopProp
title="{{ 'disabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "disabled" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.password">
<i
class="bwi bwi-key"
appStopProp
title="{{ 'password' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "password" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.maxAccessCountReached">
<i
class="bwi bwi-ban"
appStopProp
title="{{ 'maxAccessCountReached' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "maxAccessCountReached" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.expired">
<i
class="bwi bwi-clock"
appStopProp
title="{{ 'expired' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "expired" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
<i
class="bwi bwi-trash"
appStopProp
title="{{ 'pendingDeletion' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
</ng-container>
</td>
<td bitCell class="tw-text-muted" (click)="editSend(s)" class="tw-cursor-pointer">
<small bitTypography="body2" appStopProp>{{ s.deletionDate | date: "medium" }}</small>
</td>
<td bitCell class="tw-w-0 tw-text-right">
<button
type="button"
[bitMenuTriggerFor]="sendOptions"
bitIconButton="bwi-ellipsis-v"
appA11yTitle="{{ 'options' | i18n }}"
></button>
<bit-menu #sendOptions>
<button type="button" bitMenuItem (click)="copy(s)">
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySendLink" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="removePassword(s)"
*ngIf="s.password && !disableSend"
>
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "removePassword" | i18n }}
</button>
<button type="button" bitMenuItem (click)="delete(s)">
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ "delete" | i18n }}
</span>
</button>
</bit-menu>
</td>
</tr>
</ng-template>
</bit-table>
<div class="no-items" *ngIf="filteredSends && !filteredSends.length">
<ng-container *ngIf="!loaded">
<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="loaded">
<bit-no-items [icon]="noItemIcon" class="tw-text-main">
<ng-container slot="title">{{ "sendsNoItemsTitle" | i18n }}</ng-container>
<ng-container slot="description">{{ "sendsNoItemsMessage" | i18n }}</ng-container>
<button
slot="button"
type="button"
bitButton
buttonType="secondary"
(click)="addSend()"
>
<i class="bwi bwi-plus" aria-hidden="true"></i>
{{ "createSend" | i18n }}
</button>
</bit-no-items>
</ng-container>
</div>
bitMenuItem
(click)="removePassword(s)"
*ngIf="s.password && !disableSend"
>
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "removePassword" | i18n }}
</button>
<button type="button" bitMenuItem (click)="delete(s)">
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ "delete" | i18n }}
</span>
</button>
</bit-menu>
</td>
</tr>
</ng-template>
</bit-table>
<div class="no-items" *ngIf="filteredSends && !filteredSends.length">
<ng-container *ngIf="!loaded">
<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="loaded">
<bit-no-items [icon]="noItemIcon" class="tw-text-main">
<ng-container slot="title">{{ "sendsNoItemsTitle" | i18n }}</ng-container>
<ng-container slot="description">{{ "sendsNoItemsMessage" | i18n }}</ng-container>
<button slot="button" type="button" bitButton buttonType="secondary" (click)="addSend()">
<i class="bwi bwi-plus" aria-hidden="true"></i>
{{ "createSend" | i18n }}
</button>
</bit-no-items>
</ng-container>
</div>
</div>
</div>

View File

@@ -14,6 +14,7 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService, NoItemsModule, SearchModule, TableDataSource } from "@bitwarden/components";
import { HeaderModule } from "../../layouts/header/header.module";
import { SharedModule } from "../../shared";
import { AddEditComponent } from "./add-edit.component";
@@ -24,7 +25,7 @@ const BroadcasterSubscriptionId = "SendComponent";
@Component({
selector: "app-send",
standalone: true,
imports: [SharedModule, SearchModule, NoItemsModule],
imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule],
templateUrl: "send.component.html",
})
export class SendComponent extends BaseSendComponent {

View File

@@ -1,23 +0,0 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<div class="card mb-4">
<div class="card-header">{{ "tools" | i18n }}</div>
<div class="list-group list-group-flush">
<a routerLink="generator" class="list-group-item" routerLinkActive="active">
{{ "generator" | i18n }}
</a>
<a routerLink="import" class="list-group-item" routerLinkActive="active">
{{ "importData" | i18n }}
</a>
<a routerLink="export" class="list-group-item" routerLinkActive="active">
{{ "exportVault" | i18n }}
</a>
</div>
</div>
</div>
<div class="col-9">
<router-outlet></router-outlet>
</div>
</div>
</div>

View File

@@ -1,79 +1,101 @@
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
[formGroup]="exportForm"
*ngIf="exportForm"
>
<h1 bitTypography="h1">{{ "exportVault" | i18n }}</h1>
<app-header></app-header>
<bit-callout type="danger" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
{{ "personalVaultExportPolicyInEffect" | i18n }}
</bit-callout>
<app-export-scope-callout
[organizationId]="organizationId"
*ngIf="!disabledByPolicy"
></app-export-scope-callout>
<bit-container>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
[formGroup]="exportForm"
*ngIf="exportForm"
>
<bit-callout type="danger" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
{{ "personalVaultExportPolicyInEffect" | i18n }}
</bit-callout>
<app-export-scope-callout
[organizationId]="organizationId"
*ngIf="!disabledByPolicy"
></app-export-scope-callout>
<ng-container *ngIf="organizations$ | async as organizations">
<bit-form-field *ngIf="organizations.length > 0">
<bit-label>{{ "exportFrom" | i18n }}</bit-label>
<bit-select formControlName="vaultSelector">
<bit-option [label]="'myVault' | i18n" value="myVault" icon="bwi-user" />
<bit-option
*ngFor="let o of organizations$ | async"
[value]="o.id"
[label]="o.name"
icon="bwi-business"
/>
<ng-container *ngIf="organizations$ | async as organizations">
<bit-form-field *ngIf="organizations.length > 0">
<bit-label>{{ "exportFrom" | i18n }}</bit-label>
<bit-select formControlName="vaultSelector">
<bit-option [label]="'myVault' | i18n" value="myVault" icon="bwi-user" />
<bit-option
*ngFor="let o of organizations$ | async"
[value]="o.id"
[label]="o.name"
icon="bwi-business"
/>
</bit-select>
</bit-form-field>
</ng-container>
<bit-form-field>
<bit-label>{{ "fileFormat" | i18n }}</bit-label>
<bit-select formControlName="format">
<bit-option *ngFor="let f of formatOptions" [value]="f.value" [label]="f.name" />
</bit-select>
</bit-form-field>
</ng-container>
<bit-form-field>
<bit-label>{{ "fileFormat" | i18n }}</bit-label>
<bit-select formControlName="format">
<bit-option *ngFor="let f of formatOptions" [value]="f.value" [label]="f.name" />
</bit-select>
</bit-form-field>
<ng-container *ngIf="format === 'encrypted_json'">
<bit-radio-group formControlName="fileEncryptionType" aria-label="exportTypeHeading">
<bit-label>{{ "exportTypeHeading" | i18n }}</bit-label>
<ng-container *ngIf="format === 'encrypted_json'">
<bit-radio-group formControlName="fileEncryptionType" aria-label="exportTypeHeading">
<bit-label>{{ "exportTypeHeading" | i18n }}</bit-label>
<bit-radio-button
id="AccountEncrypted"
name="fileEncryptionType"
class="tw-block"
[value]="encryptedExportType.AccountEncrypted"
checked="fileEncryptionType === encryptedExportType.AccountEncrypted"
>
<bit-label>{{ "accountRestricted" | i18n }}</bit-label>
<bit-hint>{{ "accountRestrictedOptionDescription" | i18n }}</bit-hint>
</bit-radio-button>
<bit-radio-button
id="AccountEncrypted"
name="fileEncryptionType"
class="tw-block"
[value]="encryptedExportType.AccountEncrypted"
checked="fileEncryptionType === encryptedExportType.AccountEncrypted"
>
<bit-label>{{ "accountRestricted" | i18n }}</bit-label>
<bit-hint>{{ "accountRestrictedOptionDescription" | i18n }}</bit-hint>
</bit-radio-button>
<bit-radio-button
id="FileEncrypted"
name="fileEncryptionType"
class="tw-block"
[value]="encryptedExportType.FileEncrypted"
checked="fileEncryptionType === encryptedExportType.FileEncrypted"
>
<bit-label>{{ "passwordProtected" | i18n }}</bit-label>
<bit-hint>{{ "passwordProtectedOptionDescription" | i18n }}</bit-hint>
</bit-radio-button>
</bit-radio-group>
<bit-radio-button
id="FileEncrypted"
name="fileEncryptionType"
class="tw-block"
[value]="encryptedExportType.FileEncrypted"
checked="fileEncryptionType === encryptedExportType.FileEncrypted"
>
<bit-label>{{ "passwordProtected" | i18n }}</bit-label>
<bit-hint>{{ "passwordProtectedOptionDescription" | i18n }}</bit-hint>
</bit-radio-button>
</bit-radio-group>
<ng-container *ngIf="fileEncryptionType == encryptedExportType.FileEncrypted">
<div class="tw-mb-3">
<ng-container *ngIf="fileEncryptionType == encryptedExportType.FileEncrypted">
<div class="tw-mb-3">
<bit-form-field>
<bit-label>{{ "filePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="filePassword"
formControlName="filePassword"
name="password"
/>
<button
type="button"
bitSuffix
bitIconButton
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
</bit-form-field>
<app-password-strength [password]="filePassword" [showText]="true">
</app-password-strength>
</div>
<bit-form-field>
<bit-label>{{ "filePassword" | i18n }}</bit-label>
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="filePassword"
formControlName="filePassword"
name="password"
id="confirmFilePassword"
formControlName="confirmFilePassword"
name="confirmFilePassword"
/>
<button
type="button"
@@ -82,37 +104,18 @@
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
</bit-form-field>
<app-password-strength [password]="filePassword" [showText]="true"> </app-password-strength>
</div>
<bit-form-field>
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="confirmFilePassword"
formControlName="confirmFilePassword"
name="confirmFilePassword"
/>
<button
type="button"
bitSuffix
bitIconButton
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
</bit-form-field>
</ng-container>
</ng-container>
</ng-container>
<button
bitButton
type="submit"
buttonType="primary"
[loading]="form.loading"
[disabled]="disabledByPolicy"
>
{{ "confirmFormat" | i18n }}
</button>
</form>
<button
bitButton
type="submit"
buttonType="primary"
[loading]="form.loading"
[disabled]="disabledByPolicy"
>
{{ "confirmFormat" | i18n }}
</button>
</form>
</bit-container>