509 lines
18 KiB
HTML
509 lines
18 KiB
HTML
<header>
|
|
<div class="left">
|
|
<button type="button" appBlurClick (click)="close()">{{ "close" | i18n }}</button>
|
|
</div>
|
|
<h1 class="center">
|
|
<span class="title">{{ "viewItem" | i18n }}</span>
|
|
</h1>
|
|
<div class="right" *ngIf="cipher">
|
|
<button type="button" appBlurClick (click)="edit()" *ngIf="!cipher.isDeleted">
|
|
{{ "edit" | i18n }}
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<content *ngIf="cipher">
|
|
<div class="box">
|
|
<h2 class="box-header">
|
|
{{ "itemInformation" | i18n }}
|
|
</h2>
|
|
<div class="box-content">
|
|
<div class="box-content-row">
|
|
<label for="name">{{ "name" | i18n }}</label>
|
|
<input id="name" type="text" [value]="cipher.name" readonly aria-readonly="true" />
|
|
</div>
|
|
<!-- Login -->
|
|
<div *ngIf="cipher.login">
|
|
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.username">
|
|
<div class="row-main">
|
|
<label
|
|
for="loginUsername"
|
|
class="row-label draggable"
|
|
draggable="true"
|
|
(dragstart)="setTextDataOnDrag($event, cipher.login.username)"
|
|
>{{ "username" | i18n }}
|
|
</label>
|
|
<input
|
|
id="loginUsername"
|
|
type="text"
|
|
[value]="cipher.login.username"
|
|
readonly
|
|
aria-readonly="true"
|
|
/>
|
|
</div>
|
|
<div class="action-buttons">
|
|
<button
|
|
type="button"
|
|
class="row-btn"
|
|
appStopClick
|
|
appA11yTitle="{{ 'copyUsername' | i18n }}"
|
|
(click)="copy(cipher.login.username, 'username', 'Username')"
|
|
>
|
|
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.password">
|
|
<div class="row-main">
|
|
<span
|
|
class="row-label draggable"
|
|
draggable="true"
|
|
(dragstart)="setTextDataOnDrag($event, cipher.login.password)"
|
|
>{{ "password" | i18n }}</span
|
|
>
|
|
<div [hidden]="showPassword" class="monospaced">
|
|
{{ cipher.login.maskedPassword }}
|
|
</div>
|
|
<div
|
|
[hidden]="!showPassword"
|
|
class="monospaced password-wrapper"
|
|
appSelectCopy
|
|
[innerHTML]="cipher.login.password | colorPassword"
|
|
></div>
|
|
</div>
|
|
<div class="action-buttons">
|
|
<button
|
|
type="button"
|
|
#checkPasswordBtn
|
|
class="row-btn btn"
|
|
appBlurClick
|
|
appA11yTitle="{{ 'checkPassword' | i18n }}"
|
|
(click)="checkPassword()"
|
|
[appApiAction]="checkPasswordPromise"
|
|
[disabled]="checkPasswordBtn.loading"
|
|
*ngIf="cipher.viewPassword"
|
|
>
|
|
<i
|
|
class="bwi bwi-lg bwi-check-circle"
|
|
[hidden]="checkPasswordBtn.loading"
|
|
aria-hidden="true"
|
|
></i>
|
|
<i
|
|
class="bwi bwi-lg bwi-spinner bwi-spin"
|
|
[hidden]="!checkPasswordBtn.loading"
|
|
aria-hidden="true"
|
|
></i>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="row-btn"
|
|
appStopClick
|
|
attr.aria-label="{{ 'toggleVisibility' | i18n }} {{ 'password' | i18n }}"
|
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
|
(click)="togglePassword()"
|
|
*ngIf="cipher.viewPassword"
|
|
[attr.aria-pressed]="showPassword"
|
|
>
|
|
<i
|
|
class="bwi bwi-lg"
|
|
aria-hidden="true"
|
|
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
|
></i>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="row-btn"
|
|
appStopClick
|
|
appA11yTitle="{{ 'copyPassword' | i18n }}"
|
|
(click)="copy(cipher.login.password, 'password', 'Password')"
|
|
*ngIf="cipher.viewPassword"
|
|
>
|
|
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="box-content-row box-content-row-flex totp"
|
|
[ngClass]="{ low: totpLow }"
|
|
*ngIf="cipher.login.totp && totpCode"
|
|
>
|
|
<div class="row-main">
|
|
<span
|
|
class="row-label draggable"
|
|
draggable="true"
|
|
(dragstart)="setTextDataOnDrag($event, totpCode)"
|
|
>{{ "verificationCodeTotp" | i18n }}</span
|
|
>
|
|
<span class="totp-code">{{ totpCodeFormatted }}</span>
|
|
</div>
|
|
<span class="totp-countdown">
|
|
<span class="totp-sec">{{ totpSec }}</span>
|
|
<svg>
|
|
<g>
|
|
<circle
|
|
class="totp-circle inner"
|
|
r="12.6"
|
|
cy="16"
|
|
cx="16"
|
|
[ngStyle]="{ 'stroke-dashoffset.px': totpDash }"
|
|
></circle>
|
|
<circle class="totp-circle outer" r="14" cy="16" cx="16"></circle>
|
|
</g>
|
|
</svg>
|
|
</span>
|
|
<div class="action-buttons">
|
|
<button
|
|
type="button"
|
|
class="row-btn"
|
|
appStopClick
|
|
appA11yTitle="{{ 'copyVerificationCode' | i18n }}"
|
|
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')"
|
|
>
|
|
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Card -->
|
|
<div *ngIf="cipher.card">
|
|
<div class="box-content-row" *ngIf="cipher.card.cardholderName">
|
|
<span class="row-label">{{ "cardholderName" | i18n }}</span>
|
|
{{ cipher.card.cardholderName }}
|
|
</div>
|
|
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.number">
|
|
<div class="row-main">
|
|
<span class="row-label">{{ "number" | i18n }}</span>
|
|
<span [hidden]="showCardNumber" class="monospaced">{{ cipher.card.maskedNumber }}</span>
|
|
<span [hidden]="!showCardNumber" class="monospaced">{{ cipher.card.number }}</span>
|
|
</div>
|
|
<div class="action-buttons">
|
|
<button
|
|
type="button"
|
|
class="row-btn"
|
|
appStopClick
|
|
attr.aria-label="{{ 'toggleVisibility' | i18n }} {{ 'number' | i18n }}"
|
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
|
(click)="toggleCardNumber()"
|
|
[attr.aria-pressed]="showCardNumber"
|
|
>
|
|
<i
|
|
class="bwi bwi-lg"
|
|
aria-hidden="true"
|
|
[ngClass]="{ 'bwi-eye': !showCardNumber, 'bwi-eye-slash': showCardNumber }"
|
|
></i>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="row-btn"
|
|
appStopClick
|
|
appA11yTitle="{{ 'copyNumber' | i18n }}"
|
|
(click)="copy(cipher.card.number, 'number', 'Card Number')"
|
|
>
|
|
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.card.brand">
|
|
<span class="row-label">{{ "brand" | i18n }}</span>
|
|
{{ cipher.card.brand }}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.card.expiration">
|
|
<span class="row-label">{{ "expiration" | i18n }}</span>
|
|
{{ cipher.card.expiration }}
|
|
</div>
|
|
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.code">
|
|
<div class="row-main">
|
|
<span class="row-label">{{ "securityCode" | i18n }}</span>
|
|
<span [hidden]="showCardCode" class="monospaced">{{ cipher.card.maskedCode }}</span>
|
|
<span [hidden]="!showCardCode" class="monospaced">{{ cipher.card.code }}</span>
|
|
</div>
|
|
<div class="action-buttons">
|
|
<button
|
|
type="button"
|
|
class="row-btn"
|
|
appStopClick
|
|
attr.aria-label="{{ 'toggleVisibility' | i18n }} {{ 'securityCode' | i18n }}"
|
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
|
(click)="toggleCardCode()"
|
|
[attr.aria-pressed]="showCardCode"
|
|
>
|
|
<i
|
|
class="bwi bwi-lg"
|
|
aria-hidden="true"
|
|
[ngClass]="{ 'bwi-eye': !showCardCode, 'bwi-eye-slash': showCardCode }"
|
|
></i>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="row-btn"
|
|
appStopClick
|
|
appA11yTitle="{{ 'copySecurityCode' | i18n }}"
|
|
(click)="copy(cipher.card.code, 'securityCode', 'Security Code')"
|
|
>
|
|
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Identity -->
|
|
<div *ngIf="cipher.identity">
|
|
<div class="box-content-row" *ngIf="cipher.identity.fullName">
|
|
<span class="row-label">{{ "identityName" | i18n }}</span>
|
|
{{ cipher.identity.fullName }}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.username">
|
|
<span class="row-label">{{ "username" | i18n }}</span>
|
|
{{ cipher.identity.username }}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.company">
|
|
<span class="row-label">{{ "company" | i18n }}</span>
|
|
{{ cipher.identity.company }}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.ssn">
|
|
<span class="row-label">{{ "ssn" | i18n }}</span>
|
|
{{ cipher.identity.ssn }}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.passportNumber">
|
|
<span class="row-label">{{ "passportNumber" | i18n }}</span>
|
|
{{ cipher.identity.passportNumber }}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.licenseNumber">
|
|
<span class="row-label">{{ "licenseNumber" | i18n }}</span>
|
|
{{ cipher.identity.licenseNumber }}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.email">
|
|
<span class="row-label">{{ "email" | i18n }}</span>
|
|
{{ cipher.identity.email }}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.phone">
|
|
<span class="row-label">{{ "phone" | i18n }}</span>
|
|
{{ cipher.identity.phone }}
|
|
</div>
|
|
<div
|
|
class="box-content-row"
|
|
*ngIf="cipher.identity.address1 || cipher.identity.city || cipher.identity.country"
|
|
>
|
|
<span class="row-label">{{ "address" | i18n }}</span>
|
|
<div *ngIf="cipher.identity.address1">{{ cipher.identity.address1 }}</div>
|
|
<div *ngIf="cipher.identity.address2">{{ cipher.identity.address2 }}</div>
|
|
<div *ngIf="cipher.identity.address3">{{ cipher.identity.address3 }}</div>
|
|
<div *ngIf="cipher.identity.fullAddressPart2">{{ cipher.identity.fullAddressPart2 }}</div>
|
|
<div *ngIf="cipher.identity.country">{{ cipher.identity.country }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="box" *ngIf="cipher.login && cipher.login.hasUris">
|
|
<div class="box-content">
|
|
<div
|
|
class="box-content-row box-content-row-flex"
|
|
*ngFor="let u of cipher.login.uris; let i = index"
|
|
>
|
|
<div class="row-main">
|
|
<label for="hostOrUri{{ i }}" class="row-label" *ngIf="!u.isWebsite">{{
|
|
"uri" | i18n
|
|
}}</label>
|
|
<label for="hostOrUri{{ i }}" class="row-label" *ngIf="u.isWebsite">{{
|
|
"website" | i18n
|
|
}}</label>
|
|
<span title="{{ u.uri }}">
|
|
<input
|
|
id="hostOrUri{{ i }}"
|
|
type="text"
|
|
[value]="u.hostOrUri"
|
|
readonly
|
|
aria-readonly="true"
|
|
/>
|
|
</span>
|
|
</div>
|
|
<div class="action-buttons">
|
|
<button
|
|
type="button"
|
|
class="row-btn"
|
|
appStopClick
|
|
attr.aria-label="{{ 'launch' | i18n }} {{ u.uri }}"
|
|
appA11yTitle="{{ 'launch' | i18n }}"
|
|
*ngIf="u.canLaunch"
|
|
(click)="launch(u)"
|
|
>
|
|
<i class="bwi bwi-lg bwi-share-square" aria-hidden="true"></i>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="row-btn"
|
|
appStopClick
|
|
attr.aria-label="{{ 'copyUri' | i18n }} {{ u.uri }}"
|
|
appA11yTitle="{{ 'copyUri' | i18n }}"
|
|
(click)="copy(u.uri, u.isWebsite ? 'website' : 'uri', 'URI')"
|
|
>
|
|
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="box" *ngIf="cipher.notes">
|
|
<h2 class="box-header">
|
|
<label for="notes">{{ "notes" | i18n }}</label>
|
|
</h2>
|
|
<div class="box-content">
|
|
<div class="box-content-row">
|
|
<textarea
|
|
id="notes"
|
|
[value]="cipher.notes"
|
|
rows="6"
|
|
readonly
|
|
aria-readonly="true"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="box" *ngIf="cipher.hasFields">
|
|
<app-vault-view-custom-fields
|
|
[cipher]="cipher"
|
|
[promptPassword]="promptPassword.bind(this)"
|
|
[copy]="copy.bind(this)"
|
|
></app-vault-view-custom-fields>
|
|
</div>
|
|
<div
|
|
class="box"
|
|
*ngIf="cipher.hasAttachments && (canAccessPremium || cipher.organizationId) && showAttachments"
|
|
>
|
|
<h2 class="box-header">
|
|
{{ "attachments" | i18n }}
|
|
</h2>
|
|
<div class="box-content">
|
|
<button
|
|
type="button"
|
|
class="box-content-row box-content-row-flex text-default"
|
|
*ngFor="let attachment of cipher.attachments"
|
|
appStopClick
|
|
appBlurCLick
|
|
(click)="downloadAttachment(attachment)"
|
|
>
|
|
<span class="row-main">{{ attachment.fileName }}</span>
|
|
<small class="row-sub-label">{{ attachment.sizeName }}</small>
|
|
<i
|
|
class="bwi bwi-download bwi-fw row-sub-icon"
|
|
*ngIf="!attachment.downloading"
|
|
aria-hidden="true"
|
|
></i>
|
|
<i
|
|
class="bwi bwi-spinner bwi-fw bwi-spin row-sub-icon"
|
|
*ngIf="attachment.downloading"
|
|
aria-hidden="true"
|
|
></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="box list">
|
|
<div class="box-content single-line">
|
|
<button
|
|
type="button"
|
|
class="box-content-row"
|
|
appStopClick
|
|
appBlurClick
|
|
(click)="fillCipher()"
|
|
*ngIf="cipher.type !== cipherType.SecureNote && !cipher.isDeleted && !inPopout"
|
|
>
|
|
<div class="row-main text-primary">
|
|
<div class="icon text-primary" aria-hidden="true">
|
|
<i class="bwi bwi-pencil-square bwi-lg bwi-fw"></i>
|
|
</div>
|
|
<span>{{ "autoFill" | i18n }}</span>
|
|
</div>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="box-content-row"
|
|
appStopClick
|
|
appBlurClick
|
|
(click)="fillCipherAndSave()"
|
|
*ngIf="cipher.type === cipherType.Login && !cipher.isDeleted && !inPopout"
|
|
>
|
|
<div class="row-main text-primary">
|
|
<div class="icon text-primary" aria-hidden="true">
|
|
<i class="bwi bwi-bookmark bwi-lg bwi-fw"></i>
|
|
</div>
|
|
<span>{{ "autoFillAndSave" | i18n }}</span>
|
|
</div>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="box-content-row"
|
|
appStopClick
|
|
appBlurClick
|
|
(click)="clone()"
|
|
*ngIf="!cipher.organizationId && !cipher.isDeleted"
|
|
>
|
|
<div class="row-main text-primary">
|
|
<div class="icon text-primary" aria-hidden="true">
|
|
<i class="bwi bwi-files bwi-lg bwi-fw"></i>
|
|
</div>
|
|
<span>{{ "cloneItem" | i18n }}</span>
|
|
</div>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="box-content-row"
|
|
appStopClick
|
|
appBlurClick
|
|
(click)="share()"
|
|
*ngIf="!cipher.organizationId"
|
|
>
|
|
<div class="row-main text-primary">
|
|
<div class="icon text-primary" aria-hidden="true">
|
|
<i class="bwi bwi-arrow-circle-right bwi-lg bwi-fw"></i>
|
|
</div>
|
|
<span>{{ "moveToOrganization" | i18n }}</span>
|
|
</div>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="box-content-row"
|
|
appStopClick
|
|
appBlurClick
|
|
(click)="restore()"
|
|
*ngIf="cipher.isDeleted"
|
|
>
|
|
<div class="row-main text-primary">
|
|
<div class="icon text-primary" aria-hidden="true">
|
|
<i class="bwi bwi-undo bwi-lg bwi-fw"></i>
|
|
</div>
|
|
<span>{{ "restoreItem" | i18n }}</span>
|
|
</div>
|
|
</button>
|
|
<button type="button" class="box-content-row" appStopClick appBlurClick (click)="delete()">
|
|
<div class="row-main text-danger">
|
|
<div class="icon text-danger" aria-hidden="true">
|
|
<i class="bwi bwi-trash bwi-lg bwi-fw"></i>
|
|
</div>
|
|
<span>{{ (cipher.isDeleted ? "permanentlyDeleteItem" : "deleteItem") | i18n }}</span>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="box">
|
|
<div class="box-footer">
|
|
<div>
|
|
<b class="font-weight-semibold">{{ "dateUpdated" | i18n }}:</b>
|
|
{{ cipher.revisionDate | date: "medium" }}
|
|
</div>
|
|
<div *ngIf="cipher.passwordRevisionDisplayDate">
|
|
<b class="font-weight-semibold">{{ "datePasswordUpdated" | i18n }}:</b>
|
|
{{ cipher.passwordRevisionDisplayDate | date: "medium" }}
|
|
</div>
|
|
<div *ngIf="cipher.hasPasswordHistory">
|
|
<b class="font-weight-semibold">{{ "passwordHistory" | i18n }}:</b>
|
|
<a
|
|
routerLink="/cipher-password-history"
|
|
[queryParams]="{ cipherId: cipher.id }"
|
|
appStopClick
|
|
title="{{ 'passwordHistory' | i18n }}"
|
|
>
|
|
{{ cipher.passwordHistory.length }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</content>
|