Apply Prettier (#1202)

This commit is contained in:
Oscar Hinton 2021-12-20 15:47:17 +01:00 committed by GitHub
parent b4df834b16
commit 521feae535
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
141 changed files with 12454 additions and 10311 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -6,7 +6,7 @@ body:
attributes:
value: |
Thanks for taking the time to fill out this bug report!
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
- type: textarea
id: reproduce

View File

@ -1,4 +1,5 @@
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
@ -6,27 +7,26 @@
- [ ] Other
## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories-->
* **file.ext:** Description of what was changed and why
- **file.ext:** Description of what was changed and why
## Screenshots
<!--Required for any UI changes. Delete if not applicable-->
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

@ -128,8 +128,8 @@ jobs:
- name: Install Node dependencies
run: npm ci
# - name: Run linter
# run: npm run lint
- name: Run linter
run: npm run lint
- name: Build application
run: npm run dist:lin
@ -224,8 +224,8 @@ jobs:
- name: Install Node dependencies
run: npm ci
# - name: Run linter
# run: npm run lint
- name: Run linter
run: npm run lint
- name: Build & Sign (dev)
env:
@ -477,8 +477,8 @@ jobs:
- name: Install Node dependencies
run: npm ci
# - name: Run linter
# run: npm run lint
- name: Run linter
run: npm run lint
- name: Build application (dev)
run: npm run build

View File

@ -5,7 +5,7 @@ on:
workflow_dispatch:
inputs: {}
schedule:
- cron: '0 0 * * 5'
- cron: "0 0 * * 5"
jobs:
crowdin-sync:
@ -15,7 +15,7 @@ jobs:
_CROWDIN_PROJECT_ID: "299360"
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a

View File

@ -22,7 +22,7 @@ jobs:
fi
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Get Package Version
id: retrieve-version
@ -58,34 +58,34 @@ jobs:
branch: ${{ steps.branch.outputs.branch-name }}
- name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
with:
artifacts: "Bitwarden-${{ env.PKG_VERSION }}-amd64.deb,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm,
Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd,
bitwarden_${{ env.PKG_VERSION }}_amd64.snap,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage,
latest-linux.yml,
Bitwarden-Portable-${{ env.PKG_VERSION }}.exe,
Bitwarden-Installer-${{ env.PKG_VERSION }}.exe,
Bitwarden-${{ env.PKG_VERSION }}-ia32-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-x64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-arm64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.nsis.7z,
bitwarden.${{ env.PKG_VERSION }}.nupkg,
latest.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal-mac.zip,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg.blockmap,
latest-mac.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal.pkg"
Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm,
Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd,
bitwarden_${{ env.PKG_VERSION }}_amd64.snap,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage,
latest-linux.yml,
Bitwarden-Portable-${{ env.PKG_VERSION }}.exe,
Bitwarden-Installer-${{ env.PKG_VERSION }}.exe,
Bitwarden-${{ env.PKG_VERSION }}-ia32-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-x64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-arm64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.nsis.7z,
bitwarden.${{ env.PKG_VERSION }}.nupkg,
latest.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal-mac.zip,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg.blockmap,
latest-mac.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal.pkg"
commit: ${{ github.sha }}
tag: v${{ env.PKG_VERSION }}
name: Version ${{ env.PKG_VERSION }}
@ -93,7 +93,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
snap:
name: Deploy Snap
runs-on: ubuntu-20.04
@ -102,10 +101,10 @@ jobs:
_PKG_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Install Snap
uses: samuelmeuli/action-snapcraft@10d7d0a84d9d86098b19f872257df314b0bd8e2d # v1.2.0
uses: samuelmeuli/action-snapcraft@10d7d0a84d9d86098b19f872257df314b0bd8e2d # v1.2.0
with:
snapcraft_token: ${{ secrets.SNAP_TOKEN }}
@ -129,7 +128,6 @@ jobs:
snapcraft upload dist/bitwarden_${{ env._PKG_VERSION }}_amd64.snap --release stable
snapcraft logout
choco:
name: Deploy Choco
runs-on: windows-2019
@ -138,7 +136,7 @@ jobs:
_PKG_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Setup Chocolatey
run: choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/

4
.vscode/launch.json vendored
View File

@ -10,9 +10,7 @@
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"args": [
"."
]
"args": ["."]
}
]
}

View File

@ -6,17 +6,12 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Translate:** See the localization (l10n) section below
- **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
- **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
- **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Translate:** See the localization (l10n) section below
## Contributor Agreement
@ -24,9 +19,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/deskt
## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch
* include a link to your Community Forums post
- use `npm run lint` and fix any linting suggestions before submitting a pull request
- commit any pull requests against the `master` branch
- include a link to your Community Forums post
# Localization (l10n)

View File

@ -16,8 +16,7 @@ The Bitwarden desktop app is written using Electron and Angular. The application
- [Node.js](https://nodejs.org) v16.13.1 (LTS) or greater
- NPM v8
- Windows users: To compile the native node modules used in the app you will need the *Visual C++ toolset*, available through the standard Visual Studio installer. You will also need to install the *Microsoft Build Tools 2015* and *Windows 10 SDK 17134* as additional dependencies in the Visual Studio installer.
- Windows users: To compile the native node modules used in the app you will need the _Visual C++ toolset_, available through the standard Visual Studio installer. You will also need to install the _Microsoft Build Tools 2015_ and _Windows 10 SDK 17134_ as additional dependencies in the Visual Studio installer.
**Run the app**
@ -31,10 +30,10 @@ npm run electron
Native Messaging (communication with the browser extension) works by having the browser start a lightweight proxy application baked into our desktop binary. To setup an environment which allows
for easy debugging you will need to build the application for distribution, i.e. `npm run dist:<platform>`, start the dist version and enable desktop integration. This will write some manifests
to disk, Consult the [native manifests](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#Manifest_location) documentation for more details of the manifest
format, and the exact locations for the different platforms. *Note* that disabling the desktop integration will delete the manifests, and the files will need to be updated again.
format, and the exact locations for the different platforms. _Note_ that disabling the desktop integration will delete the manifests, and the files will need to be updated again.
The generated manifests are pre-configured with the production ID for the browser extensions. In order to use them with the development builds, the browser extension ID of the development build
needs to be added to the `allowed_extensions` section of the manifest. These IDs are generated by the browser, and can be found in the extension settings within the browser.
needs to be added to the `allowed_extensions` section of the manifest. These IDs are generated by the browser, and can be found in the extension settings within the browser.
It will then be possible to run the desktop application as usual using `npm run electron` and communicate with the browser.

View File

@ -7,7 +7,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
third-party. We may publicly disclose the issue before resolving it, if appropriate.
third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.

View File

@ -27,7 +27,7 @@
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
"symlink:mac": "npm run symlink:lin",
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
"lint": "tslint 'src/**/*.ts'",
"lint": "tslint 'src/**/*.ts' && prettier --check .",
"lint:fix": "tslint 'src/**/*.ts' --fix",
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
"build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"",

View File

@ -1,53 +1,60 @@
require('dotenv').config();
const path = require('path');
const fse = require('fs-extra');
const { notarize } = require('electron-notarize');
const { deepAssign } = require('builder-util');
require("dotenv").config();
const path = require("path");
const fse = require("fs-extra");
const { notarize } = require("electron-notarize");
const { deepAssign } = require("builder-util");
exports.default = run;
async function run(context) {
console.log('## After sign');
// console.log(context);
console.log("## After sign");
// console.log(context);
const appName = context.packager.appInfo.productFilename;
const appPath = `${context.appOutDir}/${appName}.app`;
const macBuild = context.electronPlatformName === 'darwin';
const copyPlugIn = ['darwin', 'mas'].includes(context.electronPlatformName);
const appName = context.packager.appInfo.productFilename;
const appPath = `${context.appOutDir}/${appName}.app`;
const macBuild = context.electronPlatformName === "darwin";
const copyPlugIn = ["darwin", "mas"].includes(context.electronPlatformName);
if (copyPlugIn) {
// Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552
const plugIn = path.join(__dirname, '../PlugIns');
if (fse.existsSync(plugIn)) {
fse.mkdirSync(path.join(appPath, 'Contents/PlugIns'));
fse.copySync(path.join(plugIn, 'safari.appex'), path.join(appPath, 'Contents/PlugIns/safari.appex'));
if (copyPlugIn) {
// Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552
const plugIn = path.join(__dirname, "../PlugIns");
if (fse.existsSync(plugIn)) {
fse.mkdirSync(path.join(appPath, "Contents/PlugIns"));
fse.copySync(
path.join(plugIn, "safari.appex"),
path.join(appPath, "Contents/PlugIns/safari.appex")
);
// Resign to sign safari extension
if (context.electronPlatformName === 'mas') {
const masBuildOptions = deepAssign({}, context.packager.platformSpecificBuildOptions, context.packager.config.mas);
if (context.targets.some(e => e.name === 'mas-dev')) {
deepAssign(masBuildOptions, {
type: 'development',
});
}
if (context.packager.packagerOptions.prepackaged == null) {
await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch);
}
} else {
await context.packager.signApp(context, true);
}
// Resign to sign safari extension
if (context.electronPlatformName === "mas") {
const masBuildOptions = deepAssign(
{},
context.packager.platformSpecificBuildOptions,
context.packager.config.mas
);
if (context.targets.some((e) => e.name === "mas-dev")) {
deepAssign(masBuildOptions, {
type: "development",
});
}
if (context.packager.packagerOptions.prepackaged == null) {
await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch);
}
} else {
await context.packager.signApp(context, true);
}
}
}
if (macBuild) {
console.log('### Notarizing ' + appPath);
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
return await notarize({
appBundleId: 'com.bitwarden.desktop',
appPath: appPath,
appleId: appleId,
appleIdPassword: appleIdPassword,
});
}
if (macBuild) {
console.log("### Notarizing " + appPath);
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
return await notarize({
appBundleId: "com.bitwarden.desktop",
appPath: appPath,
appleId: appleId,
appleIdPassword: appleIdPassword,
});
}
}

29
sign.js
View File

@ -1,22 +1,19 @@
exports.default = async function(configuration) {
if (
parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 &&
configuration.path.slice(-4) == ".exe"
) {
console.log(`[*] Signing file: ${configuration.path}`)
exports.default = async function (configuration) {
if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") {
console.log(`[*] Signing file: ${configuration.path}`);
require("child_process").execSync(
`azuresigntool sign -v ` +
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
`-kvi ${process.env.SIGNING_CLIENT_ID} ` +
`-kvt ${process.env.SIGNING_TENANT_ID} ` +
`-kvs ${process.env.SIGNING_CLIENT_SECRET} ` +
`-kvc ${process.env.SIGNING_CERT_NAME} ` +
`-fd ${configuration.hash} ` +
`-du ${configuration.site} ` +
`-tr http://timestamp.digicert.com ` +
`${configuration.path}`,
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
`-kvi ${process.env.SIGNING_CLIENT_ID} ` +
`-kvt ${process.env.SIGNING_TENANT_ID} ` +
`-kvs ${process.env.SIGNING_CLIENT_SECRET} ` +
`-kvc ${process.env.SIGNING_CERT_NAME} ` +
`-fd ${configuration.hash} ` +
`-du ${configuration.site} ` +
`-tr http://timestamp.digicert.com ` +
`${configuration.path}`,
{
stdio: "inherit"
stdio: "inherit",
}
);
}

View File

@ -1,66 +1,97 @@
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{'settings' | i18n}}">
<div class="modal-dialog" role="document">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{'selfHostedEnvironment' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl"
placeholder="{{'ex' | i18n}} https://bitwarden.company.com" appInputVerbatim>
</div>
</div>
<div class="box-footer">
{{'selfHostedEnvironmentFooter' | i18n}}
</div>
</div>
<div class="box">
<div class="box-header">
<button type="button" (click)="toggleCustom()" appA11yTitle="{{'toggleVisibility' | i18n}}">
<i class="fa fa-plus-square-o" [hidden]="showCustom" aria-hidden="true"></i>
<i class="fa fa-minus-square-o" [hidden]="!showCustom" aria-hidden="true"></i>
{{'customEnvironment' | i18n}}
</button>
</div>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl"
appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{'identityUrl' | i18n}}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl"
appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{'notificationsUrl' | i18n}}</label>
<input id="notificationsUrl" type="text" name="NotificationsUrl"
[(ngModel)]="notificationsUrl" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{'iconsUrl' | i18n}}</label>
<input id="iconsUrl" type="text" name="IconsUrl" [(ngModel)]="iconsUrl" appInputVerbatim>
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{'customEnvironmentFooter' | i18n}}
</div>
</div>
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
<div class="modal-dialog" role="document">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{ "selfHostedEnvironment" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="baseUrl"
type="text"
name="BaseUrl"
[(ngModel)]="baseUrl"
placeholder="{{ 'ex' | i18n }} https://bitwarden.company.com"
appInputVerbatim
/>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="box-footer">
{{ "selfHostedEnvironmentFooter" | i18n }}
</div>
</div>
<div class="box">
<div class="box-header">
<button
type="button"
(click)="toggleCustom()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
>
<i class="fa fa-plus-square-o" [hidden]="showCustom" aria-hidden="true"></i>
<i class="fa fa-minus-square-o" [hidden]="!showCustom" aria-hidden="true"></i>
{{ "customEnvironment" | i18n }}
</button>
</div>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{ "webVaultUrl" | i18n }}</label>
<input
id="webVaultUrl"
type="text"
name="WebVaultUrl"
[(ngModel)]="webVaultUrl"
appInputVerbatim
/>
</div>
</form>
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{ "apiUrl" | i18n }}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" appInputVerbatim />
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{ "identityUrl" | i18n }}</label>
<input
id="identityUrl"
type="text"
name="IdentityUrl"
[(ngModel)]="identityUrl"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{ "notificationsUrl" | i18n }}</label>
<input
id="notificationsUrl"
type="text"
name="NotificationsUrl"
[(ngModel)]="notificationsUrl"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{ "iconsUrl" | i18n }}</label>
<input
id="iconsUrl"
type="text"
name="IconsUrl"
[(ngModel)]="iconsUrl"
appInputVerbatim
/>
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{ "customEnvironmentFooter" | i18n }}
</div>
</div>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{ 'save' | i18n }}">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@ -1,18 +1,21 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib-angular/components/environment.component';
import { EnvironmentComponent as BaseEnvironmentComponent } from "jslib-angular/components/environment.component";
@Component({
selector: 'app-environment',
templateUrl: 'environment.component.html',
selector: "app-environment",
templateUrl: "environment.component.html",
})
export class EnvironmentComponent extends BaseEnvironmentComponent {
constructor(platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
i18nService: I18nService) {
super(platformUtilsService, environmentService, i18nService);
}
constructor(
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
i18nService: I18nService
) {
super(platformUtilsService, environmentService, i18nService);
}
}

View File

@ -1,24 +1,31 @@
<form id="hint-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{'passwordHint' | i18n}}</h1>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required appAutofocus
appInputVerbatim>
</div>
</div>
<div class="box-footer">
{{'enterEmailToGetHint' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
<div class="content">
<h1>{{ "passwordHint" | i18n }}</h1>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
appInputVerbatim
/>
</div>
</div>
<div class="box-footer">
{{ "enterEmailToGetHint" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
</div>
</form>

View File

@ -1,20 +1,25 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { HintComponent as BaseHintComponent } from 'jslib-angular/components/hint.component';
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
@Component({
selector: 'app-hint',
templateUrl: 'hint.component.html',
selector: "app-hint",
templateUrl: "hint.component.html",
})
export class HintComponent extends BaseHintComponent {
constructor(router: Router, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, apiService: ApiService, logService: LogService) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
constructor(
router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
apiService: ApiService,
logService: LogService
) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
}

View File

@ -1,48 +1,77 @@
<form id="lock-page" (ngSubmit)="submit()">
<div class="content">
<p aria-hidden="true"><i class="fa fa-lock fa-4x text-muted"></i></p>
<p>{{'yourVaultIsLocked' | i18n}}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock">
<label for="pin">{{'pin' | i18n}}</label>
<input id="pin" type="{{showPassword ? 'text' : 'password'}}" name="PIN" class="monospaced"
[(ngModel)]="pin" required appInputVerbatim>
</div>
<div class="row-main" *ngIf="!pinLock">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'loggedInAsOn' | i18n : email : webVaultHostname}}
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row" *ngIf="supportsBiometric && biometricLock">
<button type="button" class="btn block" [ngClass]="{'primary font-weight-bold': hideInput}"
appBlurClick (click)="unlockBiometric()">
{{biometricText | i18n}}
</button>
</div>
<div class="buttons-row">
<button type="submit" class="btn primary block" appBlurClick *ngIf="!hideInput">
<i class="fa fa-unlock-alt" aria-hidden="true"></i> <b>{{'unlock' | i18n}}</b>
</button>
<button type="button" class="btn block" appBlurClick (click)="logOut()">
{{'logOut' | i18n}}
</button>
</div>
<div class="content">
<p aria-hidden="true"><i class="fa fa-lock fa-4x text-muted"></i></p>
<p>{{ "yourVaultIsLocked" | i18n }}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock">
<label for="pin">{{ "pin" | i18n }}</label>
<input
id="pin"
type="{{ showPassword ? 'text' : 'password' }}"
name="PIN"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
/>
</div>
<div class="row-main" *ngIf="!pinLock">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{ "loggedInAsOn" | i18n: email:webVaultHostname }}
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row" *ngIf="supportsBiometric && biometricLock">
<button
type="button"
class="btn block"
[ngClass]="{ 'primary font-weight-bold': hideInput }"
appBlurClick
(click)="unlockBiometric()"
>
{{ biometricText | i18n }}
</button>
</div>
<div class="buttons-row">
<button type="submit" class="btn primary block" appBlurClick *ngIf="!hideInput">
<i class="fa fa-unlock-alt" aria-hidden="true"></i> <b>{{ "unlock" | i18n }}</b>
</button>
<button type="button" class="btn block" appBlurClick (click)="logOut()">
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</form>

View File

@ -1,95 +1,108 @@
import {
Component,
NgZone,
OnDestroy,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ipcRenderer } from 'electron';
import { Component, NgZone, OnDestroy } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { ipcRenderer } from "electron";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component';
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
const BroadcasterSubscriptionId = 'LockComponent';
const BroadcasterSubscriptionId = "LockComponent";
@Component({
selector: 'app-lock',
templateUrl: 'lock.component.html',
selector: "app-lock",
templateUrl: "lock.component.html",
})
export class LockComponent extends BaseLockComponent implements OnDestroy {
private deferFocus: boolean = null;
private deferFocus: boolean = null;
constructor(router: Router, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
cryptoService: CryptoService, vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService, stateService: StateService,
apiService: ApiService, private route: ActivatedRoute,
private broadcasterService: BroadcasterService, ngZone: NgZone,
logService: LogService, keyConnectorService: KeyConnectorService) {
super(router, i18nService, platformUtilsService, messagingService, cryptoService,
vaultTimeoutService, environmentService, stateService, apiService, logService,
keyConnectorService, ngZone);
}
constructor(
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
messagingService: MessagingService,
cryptoService: CryptoService,
vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService,
stateService: StateService,
apiService: ApiService,
private route: ActivatedRoute,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
logService: LogService,
keyConnectorService: KeyConnectorService
) {
super(
router,
i18nService,
platformUtilsService,
messagingService,
cryptoService,
vaultTimeoutService,
environmentService,
stateService,
apiService,
logService,
keyConnectorService,
ngZone
);
}
async ngOnInit() {
await super.ngOnInit();
const autoPromptBiometric = !await this.stateService.getNoAutoPromptBiometrics();
async ngOnInit() {
await super.ngOnInit();
const autoPromptBiometric = !(await this.stateService.getNoAutoPromptBiometrics());
this.route.queryParams.subscribe(params => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {
setTimeout(async() => {
if (await ipcRenderer.invoke('windowVisible')) {
this.unlockBiometric();
}
}, 1000);
this.route.queryParams.subscribe((params) => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {
setTimeout(async () => {
if (await ipcRenderer.invoke("windowVisible")) {
this.unlockBiometric();
}
}, 1000);
}
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
case "windowIsFocused":
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
case 'windowIsFocused':
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send('getWindowIsFocused');
}
break;
default:
}
});
});
this.messagingService.send("getWindowIsFocused");
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
private focusInput() {
document.getElementById(this.pinLock ? 'pin' : 'masterPassword').focus();
}
private focusInput() {
document.getElementById(this.pinLock ? "pin" : "masterPassword").focus();
}
}

View File

@ -1,58 +1,96 @@
<div class="login-header">
<a href="#" appStopClick (click)="settings()" class="environment-urls-settings-icon" attr.aria-label="{{'settings' | i18n}}">
{{'settings' | i18n}}
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
</a>
<a
href="#"
appStopClick
(click)="settings()"
class="environment-urls-settings-icon"
attr.aria-label="{{ 'settings' | i18n }}"
>
{{ "settings" | i18n }}
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
</a>
</div>
<form id="login-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise" attr.aria-hidden="{{showingModal}}">
<div id="content" class="content">
<img class="logo-image" alt="Bitwarden">
<p class="lead">{{'loginOrCreateNewAccount' | i18n}}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required appInputVerbatim="false">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
<form
id="login-page"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
attr.aria-hidden="{{ showingModal }}"
>
<div id="content" class="content">
<img class="logo-image" alt="Bitwarden" />
<p class="lead">{{ "loginOrCreateNewAccount" | i18n }}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
appInputVerbatim="false"
/>
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading"><i class="fa fa-sign-in" aria-hidden="true"></i> {{'logIn' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/register" class="btn block">
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{'createAccount' | i18n}}
</a>
</div>
<div class="buttons-row">
<a (click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')" class="btn block">
<i class="fa fa-bank" aria-hidden="true"></i> {{'enterpriseSingleSignOn' | i18n}}
</a>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="sub-options">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading"
><i class="fa fa-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }}</b
>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/register" class="btn block">
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{ "createAccount" | i18n }}
</a>
</div>
<div class="buttons-row">
<a (click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')" class="btn block">
<i class="fa fa-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</a>
</div>
</div>
<div class="sub-options">
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
</div>
</div>
</form>
<ng-template #environment></ng-template>

View File

@ -1,112 +1,129 @@
import {
Component,
NgZone,
OnDestroy,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
import { Router } from '@angular/router';
import { Router } from "@angular/router";
import { EnvironmentComponent } from './environment.component';
import { EnvironmentComponent } from "./environment.component";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { LoginComponent as BaseLoginComponent } from 'jslib-angular/components/login.component';
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
const BroadcasterSubscriptionId = 'LoginComponent';
const BroadcasterSubscriptionId = "LoginComponent";
@Component({
selector: 'app-login',
templateUrl: 'login.component.html',
selector: "app-login",
templateUrl: "login.component.html",
})
export class LoginComponent extends BaseLoginComponent implements OnDestroy {
@ViewChild('environment', { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef;
@ViewChild("environment", { read: ViewContainerRef, static: true })
environmentModal: ViewContainerRef;
showingModal = false;
showingModal = false;
private deferFocus: boolean = null;
private deferFocus: boolean = null;
constructor(authService: AuthService, router: Router, i18nService: I18nService,
syncService: SyncService, private modalService: ModalService,
platformUtilsService: PlatformUtilsService, stateService: StateService,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService,
cryptoFunctionService: CryptoFunctionService, private broadcasterService: BroadcasterService,
ngZone: NgZone, private messagingService: MessagingService,
logService: LogService) {
super(authService, router, platformUtilsService, i18nService, stateService, environmentService,
passwordGenerationService, cryptoFunctionService, logService, ngZone);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
syncService: SyncService,
private modalService: ModalService,
platformUtilsService: PlatformUtilsService,
stateService: StateService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
cryptoFunctionService: CryptoFunctionService,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
private messagingService: MessagingService,
logService: LogService
) {
super(
authService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
logService,
ngZone
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
case 'windowIsFocused':
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send('getWindowIsFocused');
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async settings() {
const [modal, childComponent] = await this.modalService.openViewRef(EnvironmentComponent, this.environmentModal);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onSaved.subscribe(() => {
modal.close();
});
}
onWindowHidden() {
this.showPassword = false;
}
async submit() {
await super.submit();
if (this.captchaSiteKey) {
const content = document.getElementById('content') as HTMLDivElement;
content.setAttribute('style', 'width:335px');
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
case "windowIsFocused":
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send("getWindowIsFocused");
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async settings() {
const [modal, childComponent] = await this.modalService.openViewRef(
EnvironmentComponent,
this.environmentModal
);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onSaved.subscribe(() => {
modal.close();
});
}
onWindowHidden() {
this.showPassword = false;
}
async submit() {
await super.submit();
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
}
}
}

View File

@ -1,70 +1,91 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="premiumTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="premiumTitle">
{{'premiumMembership' | i18n}}
</div>
<div class="box-content box-content-padded">
<div *ngIf="!isPremium">
<p class="text-center lead">{{'premiumNotCurrentMember' | i18n}}</p>
<p>{{'premiumSignUpAndGet' | i18n}}</p>
<ul class="fa-ul">
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpStorage' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTwoStep' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpReports' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTotp' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpSupport' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpFuture' | i18n}}
</li>
</ul>
<p class="text-center lead no-margin">
{{'premiumPrice' | i18n : (price | currency:'$')}}
</p>
</div>
<div *ngIf="isPremium">
<p class="text-center lead">{{'premiumCurrentMember' | i18n}}</p>
<p class="text-center">{{'premiumCurrentMemberThanks' | i18n}}</p>
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="premiumTitle">
{{ "premiumMembership" | i18n }}
</div>
<div class="box-content box-content-padded">
<div *ngIf="!isPremium">
<p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p>
<p>{{ "premiumSignUpAndGet" | i18n }}</p>
<ul class="fa-ul">
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpStorage" | i18n }}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpTwoStep" | i18n }}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpReports" | i18n }}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpTotp" | i18n }}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpSupport" | i18n }}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpFuture" | i18n }}
</li>
</ul>
<p class="text-center lead no-margin">
{{ "premiumPrice" | i18n: (price | currency: "$") }}
</p>
</div>
<div class="modal-footer">
<button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium">
<b>{{'premiumManage' | i18n}}</b>
</button>
<button #purchaseBtn type="button" class="primary" appBlurClick (click)="purchase()" *ngIf="!isPremium"
[disabled]="purchaseBtn.loading">
<b>{{'premiumPurchase' | i18n}}</b>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
<div class="right" *ngIf="!isPremium">
<button #refreshBtn type="button" appBlurClick (click)="refresh()" [disabled]="refreshBtn.loading"
appA11yTitle="{{'premiumRefresh' | i18n}}" [appApiAction]="refreshPromise">
<i class="fa fa-refresh fa-lg fa-fw" [hidden]="refreshBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!refreshBtn.loading"
aria-hidden="true"></i>
</button>
</div>
<div *ngIf="isPremium">
<p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p>
<p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium">
<b>{{ "premiumManage" | i18n }}</b>
</button>
<button
#purchaseBtn
type="button"
class="primary"
appBlurClick
(click)="purchase()"
*ngIf="!isPremium"
[disabled]="purchaseBtn.loading"
>
<b>{{ "premiumPurchase" | i18n }}</b>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
<div class="right" *ngIf="!isPremium">
<button
#refreshBtn
type="button"
appBlurClick
(click)="refresh()"
[disabled]="refreshBtn.loading"
appA11yTitle="{{ 'premiumRefresh' | i18n }}"
[appApiAction]="refreshPromise"
>
<i
class="fa fa-refresh fa-lg fa-fw"
[hidden]="refreshBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!refreshBtn.loading"
aria-hidden="true"
></i>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,21 +1,25 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/components/premium.component';
import { PremiumComponent as BasePremiumComponent } from "jslib-angular/components/premium.component";
@Component({
selector: 'app-premium',
templateUrl: 'premium.component.html',
selector: "app-premium",
templateUrl: "premium.component.html",
})
export class PremiumComponent extends BasePremiumComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, logService: LogService,
stateService: StateService) {
super(i18nService, platformUtilsService, apiService, logService, stateService);
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService,
stateService: StateService
) {
super(i18nService, platformUtilsService, apiService, logService, stateService);
}
}

View File

@ -1,93 +1,150 @@
<form id="register-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{'createAccount' | i18n}}</h1>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required
[appAutofocus]="email === ''" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreColor}}"
*ngIf="masterPasswordScoreText">
{{masterPasswordScoreText}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
[appAutofocus]="email !== ''" (input)="updatePasswordStrength()" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100" [ngStyle]="{width: (masterPasswordScoreWidth + '%')}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}"></div>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
<div class="content">
<h1>{{ "createAccount" | i18n }}</h1>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
[appAutofocus]="email === ''"
appInputVerbatim
/>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="confirmMasterPassword" required
appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="email !== ''"
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreColor }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div>
</div>
<div class="box last" *ngIf="showTerms">
<div class="box-footer checkbox">
<input type="checkbox" id="acceptPolicies" [(ngModel)]="acceptPolicies" name="AcceptPolicies">
<label for="acceptPolicies">
{{'acceptPolicies' | i18n}}<br>
<a href="https://bitwarden.com/terms/" target="_blank"
rel="noopener">{{'termsOfService' | i18n}}</a>,
<a href="https://bitwarden.com/privacy/" target="_blank"
rel="noopener">{{'privacyPolicy' | i18n}}</a>
</label>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</div>
</div>
<div class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="confirmMasterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="box last" *ngIf="showTerms">
<div class="box-footer checkbox">
<input
type="checkbox"
id="acceptPolicies"
[(ngModel)]="acceptPolicies"
name="AcceptPolicies"
/>
<label for="acceptPolicies">
{{ "acceptPolicies" | i18n }}<br />
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
"termsOfService" | i18n
}}</a
>,
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
"privacyPolicy" | i18n
}}</a>
</label>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
</div>
</form>

View File

@ -1,61 +1,74 @@
import {
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { RegisterComponent as BaseRegisterComponent } from 'jslib-angular/components/register.component';
import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
const BroadcasterSubscriptionId = 'RegisterComponent';
const BroadcasterSubscriptionId = "RegisterComponent";
@Component({
selector: 'app-register',
templateUrl: 'register.component.html',
selector: "app-register",
templateUrl: "register.component.html",
})
export class RegisterComponent extends BaseRegisterComponent implements OnInit, OnDestroy {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, cryptoService: CryptoService,
apiService: ApiService, stateService: StateService,
platformUtilsService: PlatformUtilsService, passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, logService: LogService) {
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
passwordGenerationService, environmentService, logService);
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
cryptoService: CryptoService,
apiService: ApiService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
logService: LogService
) {
super(
authService,
router,
i18nService,
cryptoService,
apiService,
stateService,
platformUtilsService,
passwordGenerationService,
environmentService,
logService
);
}
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
});
});
super.ngOnInit();
}
super.ngOnInit();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
}

View File

@ -1,18 +1,28 @@
<div id="remove-password-page" *ngIf="!loading">
<div class="content">
<h1>{{'removeMasterPassword' | i18n}}</h1>
<p>{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}</p>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="actionPromise" appBlurClick
(click)="convert()">
<b [hidden]="continuing">{{'removeMasterPassword' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!continuing" aria-hidden="true"></i>
</button>
<button type="button" class="btn secondary block" [disabled]="actionPromise" appBlurClick
(click)="leave()">
<b [hidden]="leaving">{{'leaveOrganization' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!leaving" aria-hidden="true"></i>
</button>
</div>
<div class="content">
<h1>{{ "removeMasterPassword" | i18n }}</h1>
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
<div class="buttons">
<button
type="submit"
class="btn primary block"
[disabled]="actionPromise"
appBlurClick
(click)="convert()"
>
<b [hidden]="continuing">{{ "removeMasterPassword" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!continuing" aria-hidden="true"></i>
</button>
<button
type="button"
class="btn secondary block"
[disabled]="actionPromise"
appBlurClick
(click)="leave()"
>
<b [hidden]="leaving">{{ "leaveOrganization" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!leaving" aria-hidden="true"></i>
</button>
</div>
</div>
</div>

View File

@ -1,10 +1,9 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { RemovePasswordComponent as BaseRemovePasswordComponent } from 'jslib-angular/components/remove-password.component';
import { RemovePasswordComponent as BaseRemovePasswordComponent } from "jslib-angular/components/remove-password.component";
@Component({
selector: 'app-remove-password',
templateUrl: 'remove-password.component.html',
selector: "app-remove-password",
templateUrl: "remove-password.component.html",
})
export class RemovePasswordComponent extends BaseRemovePasswordComponent {
}
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}

View File

@ -1,101 +1,159 @@
<form id="set-password-page" #form>
<div class="content">
<img class="logo-image" alt="Bitwarden">
<p class="lead">{{'setMasterPassword' | i18n}}</p>
<div class="box text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<div *ngIf="!syncLoading">
<div class="box">
<app-callout type="tip">{{'ssoCompleteRegistration' | i18n}}</app-callout>
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}"
*ngIf="resetPasswordAutoEnroll">
{{'resetPasswordAutoEnrollInviteWarning' | i18n}}
</app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</app-callout>
</div>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreColor}}"
*ngIf="masterPasswordScoreText">
{{masterPasswordScoreText}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
(input)="updatePasswordStrength()" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
[ngStyle]="{width: (masterPasswordScoreWidth + '%')}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}">
</div>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="password" name="MasterPasswordRetype"
class="monospaced" [(ngModel)]="masterPasswordRetype" required appInputVerbatim
autocomplete="new-password">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<i *ngIf="form.loading" class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"
aria-hidden="true"></i>
<span>{{'submit' | i18n}}</span>
</button>
<button class="btn block" (click)="logOut()">
<span>{{'logOut' | i18n}}</span>
</button>
</div>
</form>
</div>
<div class="content">
<img class="logo-image" alt="Bitwarden" />
<p class="lead">{{ "setMasterPassword" | i18n }}</p>
<div class="box text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<div *ngIf="!syncLoading">
<div class="box">
<app-callout type="tip">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
</div>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword"
>{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreColor }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div>
</div>
</div>
<div class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<i
*ngIf="form.loading"
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "submit" | i18n }}</span>
</button>
<button class="btn block" (click)="logOut()">
<span>{{ "logOut" | i18n }}</span>
</button>
</div>
</form>
</div>
</div>
</form>

View File

@ -1,97 +1,106 @@
import {
Component,
NgZone,
OnDestroy,
} from '@angular/core';
import { Component, NgZone, OnDestroy } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
const BroadcasterSubscriptionId = 'SetPasswordComponent';
const BroadcasterSubscriptionId = "SetPasswordComponent";
import {
SetPasswordComponent as BaseSetPasswordComponent,
} from 'jslib-angular/components/set-password.component';
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
@Component({
selector: 'app-set-password',
templateUrl: 'set-password.component.html',
selector: "app-set-password",
templateUrl: "set-password.component.html",
})
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnDestroy {
constructor(apiService: ApiService, i18nService: I18nService,
cryptoService: CryptoService, messagingService: MessagingService,
passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
policyService: PolicyService, router: Router,
syncService: SyncService, route: ActivatedRoute,
private broadcasterService: BroadcasterService, private ngZone: NgZone,
stateService: StateService) {
super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route,
stateService);
}
constructor(
apiService: ApiService,
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
stateService: StateService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
router,
apiService,
syncService,
route,
stateService
);
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return 'success';
case 3:
return 'primary';
case 2:
return 'warning';
default:
return 'danger';
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return "success";
case 3:
return "primary";
case 2:
return "warning";
default:
return "danger";
}
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t("strong");
case 3:
return this.i18nService.t("good");
case 2:
return this.i18nService.t("weak");
default:
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
}
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
}
});
});
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t('strong');
case 3:
return this.i18nService.t('good');
case 2:
return this.i18nService.t('weak');
default:
return this.masterPasswordScore != null ? this.i18nService.t('weak') : null;
}
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
}

View File

@ -1,195 +1,285 @@
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{'settings' | i18n}}">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body form">
<div class="box">
<div class="box-header">
{{'security' | i18n}}
</div>
<div class="box-content box-content-padded">
<app-vault-timeout-input [vaultTimeouts]="vaultTimeouts" [formControl]="vaultTimeout" ngDefaultControl></app-vault-timeout-input>
<div class="form-group">
<label>{{'vaultTimeoutAction' | i18n}}</label>
<div class="radio radio-mt-2">
<label for="vaultTimeoutActionLock">
<input type="radio" name="VaultTimeoutAction" id="vaultTimeoutActionLock"
value="lock" [(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()">
{{'lock' | i18n}}
</label>
</div>
<small class="help-block">{{'vaultTimeoutActionLockDesc' | i18n}}</small>
<div class="radio">
<label for="vaultTimeoutActionLogOut">
<input type="radio" name="VaultTimeoutAction" id="vaultTimeoutActionLogOut"
value="logOut" [(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()">
{{'logOut' | i18n}}
</label>
</div>
<small class="help-block">{{'vaultTimeoutActionLogOutDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="pin">
<input id="pin" type="checkbox" name="PIN" [(ngModel)]="pin" (change)="updatePin()">
{{'unlockWithPin' | i18n}}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="biometric">
<input id="biometric" type="checkbox" name="biometric" [checked]="biometric" (change)="updateBiometric()">
{{biometricText | i18n}}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="noAutoPromptBiometrics">
<input id="noAutoPromptBiometrics" type="checkbox" name="noAutoPromptBiometrics" [(ngModel)]="noAutoPromptBiometrics"
[disabled]="!biometric" (change)="updateNoAutoPromptBiometrics()">
{{noAutoPromptBiometricsText | i18n}}
</label>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'options' | i18n}}
</div>
<div class="box-content box-content-padded">
<div class="form-group">
<label for="clearClipboard">{{'clearClipboard' | i18n}}</label>
<select id="clearClipboard" name="ClearClipboard" [(ngModel)]="clearClipboard"
(change)="saveClearClipboard()">
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'clearClipboardDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="minimizeOnCopyToClipboard">
<input id="minimizeOnCopyToClipboard" type="checkbox"
name="MinimizeOnCopyToClipboard" [(ngModel)]="minimizeOnCopyToClipboard"
(change)="saveMinOnCopyToClipboard()">
{{'minimizeOnCopyToClipboard' | i18n}}
</label>
</div>
<small class="help-block">{{'minimizeOnCopyToClipboardDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="disableFavicons">
<input id="disableFavicons" type="checkbox" name="DisableFavicons"
[(ngModel)]="disableFavicons" (change)="saveFavicons()">
{{'disableFavicon' | i18n}}
</label>
</div>
<small class="help-block">{{'disableFaviconDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegration">
<input id="enableBrowserIntegration" type="checkbox" name="EnableBrowserIntegration"
[(ngModel)]="enableBrowserIntegration" (change)="saveBrowserIntegration()">
{{'enableBrowserIntegration' | i18n}}
</label>
</div>
<small class="help-block">{{'enableBrowserIntegrationDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegrationFingerprint">
<input id="enableBrowserIntegrationFingerprint" type="checkbox" name="EnableBrowserIntegrationFingerprint"
[(ngModel)]="enableBrowserIntegrationFingerprint" (change)="saveBrowserIntegrationFingerprint()" [disabled]="!enableBrowserIntegration">
{{'enableBrowserIntegrationFingerprint' | i18n}}
</label>
</div>
<small class="help-block">{{'enableBrowserIntegrationFingerprintDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableTray">
<input id="enableTray" type="checkbox" name="EnableTray" [(ngModel)]="enableTray"
(change)="saveTray()">
{{enableTrayText}}
</label>
</div>
<small class="help-block">{{enableTrayDescText}}</small>
</div>
<div class="form-group" *ngIf="showMinToTray">
<div class="checkbox">
<label for="enableMinToTray">
<input id="enableMinToTray" type="checkbox" name="EnableMinToTray"
[(ngModel)]="enableMinToTray" (change)="saveMinToTray()">
{{enableMinToTrayText}}
</label>
</div>
<small class="help-block">{{enableMinToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableCloseToTray">
<input id="enableCloseToTray" type="checkbox" name="EnableCloseToTray"
[(ngModel)]="enableCloseToTray" (change)="saveCloseToTray()">
{{enableCloseToTrayText}}
</label>
</div>
<small class="help-block">{{enableCloseToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="startToTray">
<input id="startToTray" type="checkbox" name="StartToTray" [(ngModel)]="startToTray"
(change)="saveStartToTray()">
{{startToTrayText}}
</label>
</div>
<small class="help-block">{{startToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="openAtLogin">
<input id="openAtLogin" type="checkbox" name="OpenAtLogin" [(ngModel)]="openAtLogin"
(change)="saveOpenAtLogin()">
{{'openAtLogin' | i18n}}
</label>
</div>
<small class="help-block">{{'openAtLoginDesc' | i18n}}</small>
</div>
<div class="form-group" *ngIf="showAlwaysShowDock">
<div class="checkbox">
<label for="alwaysShowDock">
<input id="alwaysShowDock" type="checkbox" name="AlwaysShowDock" [(ngModel)]="alwaysShowDock"
(change)="saveAlwaysShowDock()">
{{'alwaysShowDock' | i18n}}
</label>
</div>
<small class="help-block">{{'alwaysShowDockDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="theme">{{'theme' | i18n}}</label>
<select id="theme" name="Theme" [(ngModel)]="theme" (change)="saveTheme()">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'themeDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="locale">{{'language' | i18n}}</label>
<select id="locale" name="Locale" [(ngModel)]="locale" (change)="saveLocale()">
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'languageDesc' | i18n}}</small>
</div>
</div>
</div>
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body form">
<div class="box">
<div class="box-header">
{{ "security" | i18n }}
</div>
<div class="box-content box-content-padded">
<app-vault-timeout-input
[vaultTimeouts]="vaultTimeouts"
[formControl]="vaultTimeout"
ngDefaultControl
></app-vault-timeout-input>
<div class="form-group">
<label>{{ "vaultTimeoutAction" | i18n }}</label>
<div class="radio radio-mt-2">
<label for="vaultTimeoutActionLock">
<input
type="radio"
name="VaultTimeoutAction"
id="vaultTimeoutActionLock"
value="lock"
[(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()"
/>
{{ "lock" | i18n }}
</label>
</div>
<small class="help-block">{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
<div class="radio">
<label for="vaultTimeoutActionLogOut">
<input
type="radio"
name="VaultTimeoutAction"
id="vaultTimeoutActionLogOut"
value="logOut"
[(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()"
/>
{{ "logOut" | i18n }}
</label>
</div>
<small class="help-block">{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
<div class="form-group">
<div class="checkbox">
<label for="pin">
<input
id="pin"
type="checkbox"
name="PIN"
[(ngModel)]="pin"
(change)="updatePin()"
/>
{{ "unlockWithPin" | i18n }}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="biometric">
<input
id="biometric"
type="checkbox"
name="biometric"
[checked]="biometric"
(change)="updateBiometric()"
/>
{{ biometricText | i18n }}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="noAutoPromptBiometrics">
<input
id="noAutoPromptBiometrics"
type="checkbox"
name="noAutoPromptBiometrics"
[(ngModel)]="noAutoPromptBiometrics"
[disabled]="!biometric"
(change)="updateNoAutoPromptBiometrics()"
/>
{{ noAutoPromptBiometricsText | i18n }}
</label>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{ "options" | i18n }}
</div>
<div class="box-content box-content-padded">
<div class="form-group">
<label for="clearClipboard">{{ "clearClipboard" | i18n }}</label>
<select
id="clearClipboard"
name="ClearClipboard"
[(ngModel)]="clearClipboard"
(change)="saveClearClipboard()"
>
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
<small class="help-block">{{ "clearClipboardDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="minimizeOnCopyToClipboard">
<input
id="minimizeOnCopyToClipboard"
type="checkbox"
name="MinimizeOnCopyToClipboard"
[(ngModel)]="minimizeOnCopyToClipboard"
(change)="saveMinOnCopyToClipboard()"
/>
{{ "minimizeOnCopyToClipboard" | i18n }}
</label>
</div>
<small class="help-block">{{ "minimizeOnCopyToClipboardDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="disableFavicons">
<input
id="disableFavicons"
type="checkbox"
name="DisableFavicons"
[(ngModel)]="disableFavicons"
(change)="saveFavicons()"
/>
{{ "disableFavicon" | i18n }}
</label>
</div>
<small class="help-block">{{ "disableFaviconDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegration">
<input
id="enableBrowserIntegration"
type="checkbox"
name="EnableBrowserIntegration"
[(ngModel)]="enableBrowserIntegration"
(change)="saveBrowserIntegration()"
/>
{{ "enableBrowserIntegration" | i18n }}
</label>
</div>
<small class="help-block">{{ "enableBrowserIntegrationDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegrationFingerprint">
<input
id="enableBrowserIntegrationFingerprint"
type="checkbox"
name="EnableBrowserIntegrationFingerprint"
[(ngModel)]="enableBrowserIntegrationFingerprint"
(change)="saveBrowserIntegrationFingerprint()"
[disabled]="!enableBrowserIntegration"
/>
{{ "enableBrowserIntegrationFingerprint" | i18n }}
</label>
</div>
<small class="help-block">{{
"enableBrowserIntegrationFingerprintDesc" | i18n
}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableTray">
<input
id="enableTray"
type="checkbox"
name="EnableTray"
[(ngModel)]="enableTray"
(change)="saveTray()"
/>
{{ enableTrayText }}
</label>
</div>
<small class="help-block">{{ enableTrayDescText }}</small>
</div>
<div class="form-group" *ngIf="showMinToTray">
<div class="checkbox">
<label for="enableMinToTray">
<input
id="enableMinToTray"
type="checkbox"
name="EnableMinToTray"
[(ngModel)]="enableMinToTray"
(change)="saveMinToTray()"
/>
{{ enableMinToTrayText }}
</label>
</div>
<small class="help-block">{{ enableMinToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableCloseToTray">
<input
id="enableCloseToTray"
type="checkbox"
name="EnableCloseToTray"
[(ngModel)]="enableCloseToTray"
(change)="saveCloseToTray()"
/>
{{ enableCloseToTrayText }}
</label>
</div>
<small class="help-block">{{ enableCloseToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="startToTray">
<input
id="startToTray"
type="checkbox"
name="StartToTray"
[(ngModel)]="startToTray"
(change)="saveStartToTray()"
/>
{{ startToTrayText }}
</label>
</div>
<small class="help-block">{{ startToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="openAtLogin">
<input
id="openAtLogin"
type="checkbox"
name="OpenAtLogin"
[(ngModel)]="openAtLogin"
(change)="saveOpenAtLogin()"
/>
{{ "openAtLogin" | i18n }}
</label>
</div>
<small class="help-block">{{ "openAtLoginDesc" | i18n }}</small>
</div>
<div class="form-group" *ngIf="showAlwaysShowDock">
<div class="checkbox">
<label for="alwaysShowDock">
<input
id="alwaysShowDock"
type="checkbox"
name="AlwaysShowDock"
[(ngModel)]="alwaysShowDock"
(change)="saveAlwaysShowDock()"
/>
{{ "alwaysShowDock" | i18n }}
</label>
</div>
<small class="help-block">{{ "alwaysShowDockDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="theme">{{ "theme" | i18n }}</label>
<select id="theme" name="Theme" [(ngModel)]="theme" (change)="saveTheme()">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "themeDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="locale">{{ "language" | i18n }}</label>
<select id="locale" name="Locale" [(ngModel)]="locale" (change)="saveLocale()">
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "languageDesc" | i18n }}</small>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div>
</div>
</div>

View File

@ -1,360 +1,393 @@
import {
Component,
OnInit,
} from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { Component, OnInit } from "@angular/core";
import { FormControl } from "@angular/forms";
import { debounceTime } from "rxjs/operators";
import { DeviceType } from 'jslib-common/enums/deviceType';
import { ThemeType } from 'jslib-common/enums/themeType';
import { DeviceType } from "jslib-common/enums/deviceType";
import { ThemeType } from "jslib-common/enums/themeType";
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { SetPinComponent } from '../components/set-pin.component';
import { SetPinComponent } from "../components/set-pin.component";
import { Utils } from 'jslib-common/misc/utils';
import { isWindowsStore } from 'jslib-electron/utils';
import { Utils } from "jslib-common/misc/utils";
import { isWindowsStore } from "jslib-electron/utils";
import { StorageLocation } from 'jslib-common/enums/storageLocation';
import { StorageLocation } from "jslib-common/enums/storageLocation";
@Component({
selector: 'app-settings',
templateUrl: 'settings.component.html',
selector: "app-settings",
templateUrl: "settings.component.html",
})
export class SettingsComponent implements OnInit {
vaultTimeoutAction: string;
pin: boolean = null;
disableFavicons: boolean = false;
enableBrowserIntegration: boolean = false;
enableBrowserIntegrationFingerprint: boolean = false;
enableMinToTray: boolean = false;
enableCloseToTray: boolean = false;
enableTray: boolean = false;
showMinToTray: boolean = false;
startToTray: boolean = false;
minimizeOnCopyToClipboard: boolean = false;
locale: string;
vaultTimeouts: any[];
localeOptions: any[];
theme: string;
themeOptions: any[];
clearClipboard: number;
clearClipboardOptions: any[];
supportsBiometric: boolean;
biometric: boolean;
biometricText: string;
noAutoPromptBiometrics: boolean;
noAutoPromptBiometricsText: string;
alwaysShowDock: boolean;
showAlwaysShowDock: boolean = false;
openAtLogin: boolean;
requireEnableTray: boolean = false;
vaultTimeoutAction: string;
pin: boolean = null;
disableFavicons: boolean = false;
enableBrowserIntegration: boolean = false;
enableBrowserIntegrationFingerprint: boolean = false;
enableMinToTray: boolean = false;
enableCloseToTray: boolean = false;
enableTray: boolean = false;
showMinToTray: boolean = false;
startToTray: boolean = false;
minimizeOnCopyToClipboard: boolean = false;
locale: string;
vaultTimeouts: any[];
localeOptions: any[];
theme: string;
themeOptions: any[];
clearClipboard: number;
clearClipboardOptions: any[];
supportsBiometric: boolean;
biometric: boolean;
biometricText: string;
noAutoPromptBiometrics: boolean;
noAutoPromptBiometricsText: string;
alwaysShowDock: boolean;
showAlwaysShowDock: boolean = false;
openAtLogin: boolean;
requireEnableTray: boolean = false;
enableTrayText: string;
enableTrayDescText: string;
enableMinToTrayText: string;
enableMinToTrayDescText: string;
enableCloseToTrayText: string;
enableCloseToTrayDescText: string;
startToTrayText: string;
startToTrayDescText: string;
enableTrayText: string;
enableTrayDescText: string;
enableMinToTrayText: string;
enableMinToTrayDescText: string;
enableCloseToTrayText: string;
enableCloseToTrayDescText: string;
startToTrayText: string;
startToTrayDescText: string;
vaultTimeout: FormControl = new FormControl(null);
vaultTimeout: FormControl = new FormControl(null);
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private vaultTimeoutService: VaultTimeoutService, private stateService: StateService,
private messagingService: MessagingService, private cryptoService: CryptoService,
private modalService: ModalService) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private vaultTimeoutService: VaultTimeoutService,
private stateService: StateService,
private messagingService: MessagingService,
private cryptoService: CryptoService,
private modalService: ModalService
) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
const trayKey = isMac ? 'enableMenuBar' : 'enableTray';
this.enableTrayText = this.i18nService.t(trayKey);
this.enableTrayDescText = this.i18nService.t(trayKey + 'Desc');
const trayKey = isMac ? "enableMenuBar" : "enableTray";
this.enableTrayText = this.i18nService.t(trayKey);
this.enableTrayDescText = this.i18nService.t(trayKey + "Desc");
const minToTrayKey = isMac ? 'enableMinToMenuBar' : 'enableMinToTray';
this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + 'Desc');
const minToTrayKey = isMac ? "enableMinToMenuBar" : "enableMinToTray";
this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + "Desc");
const closeToTrayKey = isMac ? 'enableCloseToMenuBar' : 'enableCloseToTray';
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + 'Desc');
const closeToTrayKey = isMac ? "enableCloseToMenuBar" : "enableCloseToTray";
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + "Desc");
const startToTrayKey = isMac ? 'startToMenuBar' : 'startToTray';
this.startToTrayText = this.i18nService.t(startToTrayKey);
this.startToTrayDescText = this.i18nService.t(startToTrayKey + 'Desc');
const startToTrayKey = isMac ? "startToMenuBar" : "startToTray";
this.startToTrayText = this.i18nService.t(startToTrayKey);
this.startToTrayDescText = this.i18nService.t(startToTrayKey + "Desc");
this.vaultTimeouts = [
// { name: i18nService.t('immediately'), value: 0 },
{ name: i18nService.t('oneMinute'), value: 1 },
{ name: i18nService.t('fiveMinutes'), value: 5 },
{ name: i18nService.t('fifteenMinutes'), value: 15 },
{ name: i18nService.t('thirtyMinutes'), value: 30 },
{ name: i18nService.t('oneHour'), value: 60 },
{ name: i18nService.t('fourHours'), value: 240 },
{ name: i18nService.t('onIdle'), value: -4 },
{ name: i18nService.t('onSleep'), value: -3 },
];
this.vaultTimeouts = [
// { name: i18nService.t('immediately'), value: 0 },
{ name: i18nService.t("oneMinute"), value: 1 },
{ name: i18nService.t("fiveMinutes"), value: 5 },
{ name: i18nService.t("fifteenMinutes"), value: 15 },
{ name: i18nService.t("thirtyMinutes"), value: 30 },
{ name: i18nService.t("oneHour"), value: 60 },
{ name: i18nService.t("fourHours"), value: 240 },
{ name: i18nService.t("onIdle"), value: -4 },
{ name: i18nService.t("onSleep"), value: -3 },
];
if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) {
this.vaultTimeouts.push({ name: i18nService.t('onLocked'), value: -2 });
}
this.vaultTimeouts = this.vaultTimeouts.concat([
{ name: i18nService.t('onRestart'), value: -1 },
{ name: i18nService.t('never'), value: null },
]);
this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => {
this.saveVaultTimeoutOptions();
});
const localeOptions: any[] = [];
i18nService.supportedTranslationLocales.forEach(locale => {
let name = locale;
if (i18nService.localeNames.has(locale)) {
name += (' - ' + i18nService.localeNames.get(locale));
}
localeOptions.push({ name: name, value: locale });
});
localeOptions.sort(Utils.getSortFunction(i18nService, 'name'));
localeOptions.splice(0, 0, { name: i18nService.t('default'), value: null });
this.localeOptions = localeOptions;
this.themeOptions = [
{ name: i18nService.t('default'), value: null },
{ name: i18nService.t('light'), value: ThemeType.Light },
{ name: i18nService.t('dark'), value: ThemeType.Dark },
{ name: 'Nord', value: ThemeType.Nord },
];
this.clearClipboardOptions = [
{ name: i18nService.t('never'), value: null },
{ name: i18nService.t('tenSeconds'), value: 10 },
{ name: i18nService.t('twentySeconds'), value: 20 },
{ name: i18nService.t('thirtySeconds'), value: 30 },
{ name: i18nService.t('oneMinute'), value: 60 },
{ name: i18nService.t('twoMinutes'), value: 120 },
{ name: i18nService.t('fiveMinutes'), value: 300 },
];
if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) {
this.vaultTimeouts.push({ name: i18nService.t("onLocked"), value: -2 });
}
async ngOnInit() {
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout());
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
const pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pin = pinSet[0] || pinSet[1];
this.disableFavicons = await this.stateService.getDisableFavicon();
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
this.enableBrowserIntegrationFingerprint = await this.stateService.getEnableBrowserIntegrationFingerprint();
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray();
this.enableCloseToTray = await this.stateService.getEnableCloseToTray();
this.enableTray = await this.stateService.getEnableTray();
this.startToTray = await this.stateService.getEnableStartToTray();
this.locale = await this.stateService.getLocale();
this.theme = await this.stateService.getTheme();
this.clearClipboard = await this.stateService.getClearClipboard();
this.minimizeOnCopyToClipboard = await this.stateService.getMinimizeOnCopyToClipboard();
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
this.biometricText = await this.stateService.getBiometricText();
this.noAutoPromptBiometrics = await this.stateService.getNoAutoPromptBiometrics();
this.noAutoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
this.alwaysShowDock = await this.stateService.getAlwaysShowDock();
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.openAtLogin = await this.stateService.getOpenAtLogin();
this.vaultTimeouts = this.vaultTimeouts.concat([
{ name: i18nService.t("onRestart"), value: -1 },
{ name: i18nService.t("never"), value: null },
]);
this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => {
this.saveVaultTimeoutOptions();
});
const localeOptions: any[] = [];
i18nService.supportedTranslationLocales.forEach((locale) => {
let name = locale;
if (i18nService.localeNames.has(locale)) {
name += " - " + i18nService.localeNames.get(locale);
}
localeOptions.push({ name: name, value: locale });
});
localeOptions.sort(Utils.getSortFunction(i18nService, "name"));
localeOptions.splice(0, 0, { name: i18nService.t("default"), value: null });
this.localeOptions = localeOptions;
this.themeOptions = [
{ name: i18nService.t("default"), value: null },
{ name: i18nService.t("light"), value: ThemeType.Light },
{ name: i18nService.t("dark"), value: ThemeType.Dark },
{ name: "Nord", value: ThemeType.Nord },
];
this.clearClipboardOptions = [
{ name: i18nService.t("never"), value: null },
{ name: i18nService.t("tenSeconds"), value: 10 },
{ name: i18nService.t("twentySeconds"), value: 20 },
{ name: i18nService.t("thirtySeconds"), value: 30 },
{ name: i18nService.t("oneMinute"), value: 60 },
{ name: i18nService.t("twoMinutes"), value: 120 },
{ name: i18nService.t("fiveMinutes"), value: 300 },
];
}
async ngOnInit() {
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout());
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
const pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pin = pinSet[0] || pinSet[1];
this.disableFavicons = await this.stateService.getDisableFavicon();
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
this.enableBrowserIntegrationFingerprint =
await this.stateService.getEnableBrowserIntegrationFingerprint();
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray();
this.enableCloseToTray = await this.stateService.getEnableCloseToTray();
this.enableTray = await this.stateService.getEnableTray();
this.startToTray = await this.stateService.getEnableStartToTray();
this.locale = await this.stateService.getLocale();
this.theme = await this.stateService.getTheme();
this.clearClipboard = await this.stateService.getClearClipboard();
this.minimizeOnCopyToClipboard = await this.stateService.getMinimizeOnCopyToClipboard();
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
this.biometricText = await this.stateService.getBiometricText();
this.noAutoPromptBiometrics = await this.stateService.getNoAutoPromptBiometrics();
this.noAutoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
this.alwaysShowDock = await this.stateService.getAlwaysShowDock();
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.openAtLogin = await this.stateService.getOpenAtLogin();
}
async saveVaultTimeoutOptions() {
if (this.vaultTimeoutAction === "logOut") {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("vaultTimeoutLogOutConfirmation"),
this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
this.i18nService.t("yes"),
this.i18nService.t("cancel"),
"warning"
);
if (!confirmed) {
this.vaultTimeoutAction = "lock";
return;
}
}
async saveVaultTimeoutOptions() {
if (this.vaultTimeoutAction === 'logOut') {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('vaultTimeoutLogOutConfirmation'),
this.i18nService.t('vaultTimeoutLogOutConfirmationTitle'),
this.i18nService.t('yes'), this.i18nService.t('cancel'), 'warning');
if (!confirmed) {
this.vaultTimeoutAction = 'lock';
return;
}
}
// Avoid saving 0 since it's useless as a timeout value.
if (this.vaultTimeout.value === 0) {
return;
}
if (!this.vaultTimeout.valid) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('vaultTimeoutTooLarge'));
return;
}
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction);
// Avoid saving 0 since it's useless as a timeout value.
if (this.vaultTimeout.value === 0) {
return;
}
async updatePin() {
if (this.pin) {
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
if (ref == null) {
this.pin = false;
return;
}
this.pin = await ref.onClosedPromise();
}
if (!this.pin) {
await this.cryptoService.clearPinProtectedKey();
await this.vaultTimeoutService.clear();
}
if (!this.vaultTimeout.valid) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("vaultTimeoutTooLarge")
);
return;
}
async updateBiometric() {
const current = this.biometric;
if (this.biometric) {
this.biometric = false;
} else if (this.supportsBiometric) {
this.biometric = await this.platformUtilsService.authenticateBiometric();
}
if (this.biometric === current) {
return;
}
if (this.biometric) {
await this.stateService.setBiometricUnlock(true);
} else {
await this.stateService.setBiometricUnlock(null);
await this.stateService.setNoAutoPromptBiometrics(null);
this.noAutoPromptBiometrics = false;
}
await this.stateService.setBiometricLocked(false);
await this.cryptoService.toggleKey();
await this.vaultTimeoutService.setVaultTimeoutOptions(
this.vaultTimeout.value,
this.vaultTimeoutAction
);
}
async updatePin() {
if (this.pin) {
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
if (ref == null) {
this.pin = false;
return;
}
this.pin = await ref.onClosedPromise();
}
if (!this.pin) {
await this.cryptoService.clearPinProtectedKey();
await this.vaultTimeoutService.clear();
}
}
async updateBiometric() {
const current = this.biometric;
if (this.biometric) {
this.biometric = false;
} else if (this.supportsBiometric) {
this.biometric = await this.platformUtilsService.authenticateBiometric();
}
if (this.biometric === current) {
return;
}
if (this.biometric) {
await this.stateService.setBiometricUnlock(true);
} else {
await this.stateService.setBiometricUnlock(null);
await this.stateService.setNoAutoPromptBiometrics(null);
this.noAutoPromptBiometrics = false;
}
await this.stateService.setBiometricLocked(false);
await this.cryptoService.toggleKey();
}
async updateNoAutoPromptBiometrics() {
if (!this.biometric) {
this.noAutoPromptBiometrics = false;
}
async updateNoAutoPromptBiometrics() {
if (!this.biometric) {
this.noAutoPromptBiometrics = false;
}
if (this.noAutoPromptBiometrics) {
await this.stateService.setNoAutoPromptBiometrics(true);
} else {
await this.stateService.setNoAutoPromptBiometrics(null);
}
}
if (this.noAutoPromptBiometrics) {
await this.stateService.setNoAutoPromptBiometrics(true);
} else {
await this.stateService.setNoAutoPromptBiometrics(null);
}
async saveFavicons() {
await this.stateService.setDisableFavicon(this.disableFavicons);
await this.stateService.setDisableFavicon(this.disableFavicons, {
storageLocation: StorageLocation.Disk,
});
this.messagingService.send("refreshCiphers");
}
async saveMinToTray() {
await this.stateService.setEnableMinimizeToTray(this.enableMinToTray);
}
async saveCloseToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
async saveFavicons() {
await this.stateService.setDisableFavicon(this.disableFavicons);
await this.stateService.setDisableFavicon(this.disableFavicons, { storageLocation: StorageLocation.Disk });
this.messagingService.send('refreshCiphers');
}
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
}
async saveMinToTray() {
await this.stateService.setEnableMinimizeToTray(this.enableMinToTray);
}
async saveCloseToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
}
async saveTray() {
if (this.requireEnableTray && !this.enableTray && (this.startToTray || this.enableCloseToTray)) {
const confirm = await this.platformUtilsService.showDialog(
this.i18nService.t('confirmTrayDesc'), this.i18nService.t('confirmTrayTitle'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (confirm) {
this.startToTray = false;
await this.stateService.setEnableStartToTray(this.startToTray);
this.enableCloseToTray = false;
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
} else {
this.enableTray = true;
}
return;
}
await this.stateService.setEnableTray(this.enableTray);
this.messagingService.send(this.enableTray ? 'showTray' : 'removeTray');
}
async saveStartToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
async saveTray() {
if (
this.requireEnableTray &&
!this.enableTray &&
(this.startToTray || this.enableCloseToTray)
) {
const confirm = await this.platformUtilsService.showDialog(
this.i18nService.t("confirmTrayDesc"),
this.i18nService.t("confirmTrayTitle"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (confirm) {
this.startToTray = false;
await this.stateService.setEnableStartToTray(this.startToTray);
this.enableCloseToTray = false;
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
} else {
this.enableTray = true;
}
return;
}
async saveLocale() {
await this.stateService.setLocale(this.locale);
await this.stateService.setEnableTray(this.enableTray);
this.messagingService.send(this.enableTray ? "showTray" : "removeTray");
}
async saveStartToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
async saveTheme() {
await this.stateService.setTheme(this.theme);
window.setTimeout(() => window.location.reload(), 200);
await this.stateService.setEnableStartToTray(this.startToTray);
}
async saveLocale() {
await this.stateService.setLocale(this.locale);
}
async saveTheme() {
await this.stateService.setTheme(this.theme);
window.setTimeout(() => window.location.reload(), 200);
}
async saveMinOnCopyToClipboard() {
await this.stateService.setMinimizeOnCopyToClipboard(this.minimizeOnCopyToClipboard);
}
async saveClearClipboard() {
await this.stateService.setClearClipboard(this.clearClipboard);
}
async saveAlwaysShowDock() {
await this.stateService.setAlwaysShowDock(this.alwaysShowDock);
}
async saveOpenAtLogin() {
this.stateService.setOpenAtLogin(this.openAtLogin);
this.messagingService.send(this.openAtLogin ? "addOpenAtLogin" : "removeOpenAtLogin");
}
async saveBrowserIntegration() {
if (process.platform === "darwin" && !this.platformUtilsService.isMacAppStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t("browserIntegrationMasOnlyDesc"),
this.i18nService.t("browserIntegrationMasOnlyTitle"),
this.i18nService.t("ok"),
null,
"warning"
);
this.enableBrowserIntegration = false;
return;
} else if (isWindowsStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t("browserIntegrationWindowsStoreDesc"),
this.i18nService.t("browserIntegrationWindowsStoreTitle"),
this.i18nService.t("ok"),
null,
"warning"
);
this.enableBrowserIntegration = false;
return;
}
async saveMinOnCopyToClipboard() {
await this.stateService.setMinimizeOnCopyToClipboard(this.minimizeOnCopyToClipboard);
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
this.messagingService.send(
this.enableBrowserIntegration ? "enableBrowserIntegration" : "disableBrowserIntegration"
);
if (!this.enableBrowserIntegration) {
this.enableBrowserIntegrationFingerprint = false;
this.saveBrowserIntegrationFingerprint();
}
}
async saveClearClipboard() {
await this.stateService.setClearClipboard(this.clearClipboard);
}
async saveAlwaysShowDock() {
await this.stateService.setAlwaysShowDock(this.alwaysShowDock);
}
async saveOpenAtLogin() {
this.stateService.setOpenAtLogin(this.openAtLogin);
this.messagingService.send(this.openAtLogin ? 'addOpenAtLogin' : 'removeOpenAtLogin');
}
async saveBrowserIntegration() {
if (process.platform === 'darwin' && !this.platformUtilsService.isMacAppStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t('browserIntegrationMasOnlyDesc'),
this.i18nService.t('browserIntegrationMasOnlyTitle'),
this.i18nService.t('ok'), null, 'warning');
this.enableBrowserIntegration = false;
return;
} else if (isWindowsStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t('browserIntegrationWindowsStoreDesc'),
this.i18nService.t('browserIntegrationWindowsStoreTitle'),
this.i18nService.t('ok'), null, 'warning');
this.enableBrowserIntegration = false;
return;
}
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
this.messagingService.send(this.enableBrowserIntegration ? 'enableBrowserIntegration' : 'disableBrowserIntegration');
if (!this.enableBrowserIntegration) {
this.enableBrowserIntegrationFingerprint = false;
this.saveBrowserIntegrationFingerprint();
}
}
async saveBrowserIntegrationFingerprint() {
await this.stateService.setEnableBrowserIntegrationFingerprint(this.enableBrowserIntegrationFingerprint);
}
async saveBrowserIntegrationFingerprint() {
await this.stateService.setEnableBrowserIntegrationFingerprint(
this.enableBrowserIntegrationFingerprint
);
}
}

View File

@ -1,9 +1,9 @@
<form id="sso-page" (ngSubmit)="submit()">
<div class="content">
<img class="logo-image" alt="Bitwarden">
<div class="box">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<div class="content">
<img class="logo-image" alt="Bitwarden" />
<div class="box">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
</div>
</form>

View File

@ -1,40 +1,56 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component';
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
@Component({
selector: 'app-sso',
templateUrl: 'sso.component.html',
selector: "app-sso",
templateUrl: "sso.component.html",
})
export class SsoComponent extends BaseSsoComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, syncService: SyncService, route: ActivatedRoute,
stateService: StateService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService,
logService: LogService) {
super(authService, router, i18nService, route, stateService, platformUtilsService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
this.redirectUri = 'bitwarden://sso-callback';
this.clientId = 'desktop';
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
syncService: SyncService,
route: ActivatedRoute,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
logService: LogService
) {
super(
authService,
router,
i18nService,
route,
stateService,
platformUtilsService,
apiService,
cryptoFunctionService,
environmentService,
passwordGenerationService,
logService
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
this.redirectUri = "bitwarden://sso-callback";
this.clientId = "desktop";
}
}

View File

@ -1,28 +1,33 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="twoStepTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="twoStepTitle">
{{'twoStepOptions' | i18n}}
</div>
<div class="box-content">
<a href="#" appStopClick *ngFor="let p of providers" class="box-content-row"
(click)="choose(p)">
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right">
<span class="text">{{p.name}}</span>
<span class="detail">{{p.description}}</span>
</a>
<a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{'recoveryCodeTitle' | i18n}}</span>
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span>
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="twoStepTitle">
{{ "twoStepOptions" | i18n }}
</div>
<div class="box-content">
<a
href="#"
appStopClick
*ngFor="let p of providers"
class="box-content-row"
(click)="choose(p)"
>
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right" />
<span class="text">{{ p.name }}</span>
<span class="detail">{{ p.description }}</span>
</a>
<a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{ "recoveryCodeTitle" | i18n }}</span>
<span class="detail">{{ "recoveryCodeDesc" | i18n }}</span>
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div>
</div>
</div>

View File

@ -1,21 +1,23 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib-angular/components/two-factor-options.component';
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
@Component({
selector: 'app-two-factor-options',
templateUrl: 'two-factor-options.component.html',
selector: "app-two-factor-options",
templateUrl: "two-factor-options.component.html",
})
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
super(authService, router, i18nService, platformUtilsService, window);
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService
) {
super(authService, router, i18nService, platformUtilsService, window);
}
}

View File

@ -1,89 +1,139 @@
<form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise" attr.aria-hidden="{{showingModal}}">
<div class="content">
<h1>{{title}}</h1>
<p *ngIf="selectedProviderType === providerType.Authenticator">{{'enterVerificationCodeApp' | i18n}}</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
</p>
<div class="box last"
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{'verificationCode' | i18n}}</label>
<input id="code" type="text" name="Code" [(ngModel)]="token" required appAutofocus appInputVerbatim>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
<form
id="two-factor-page"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
attr.aria-hidden="{{ showingModal }}"
>
<div class="content">
<h1>{{ title }}</h1>
<p *ngIf="selectedProviderType === providerType.Authenticator">
{{ "enterVerificationCodeApp" | i18n }}
</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
</p>
<div
class="box last"
*ngIf="
selectedProviderType === providerType.Email ||
selectedProviderType === providerType.Authenticator
"
>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="text"
name="Code"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
/>
</div>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p>{{'insertYubiKey' | i18n}}</p>
<img src="../../images/yubikey.jpg" alt="">
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
<input id="code" type="password" name="Code" [(ngModel)]="token" required appAutofocus
appInputVerbatim>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame">
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo">
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<div class="box last" *ngIf="selectedProviderType == null">
<div class="box-content">
<div class="box-content-row">
<p>{{'noTwoStepProviders' | i18n}}</p>
<p>{{'noTwoStepProviders2' | i18n}}</p>
</div>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick *ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo">
<span [hidden]="form.loading"><i class="fa fa-sign-in" aria-hidden="true"></i>
{{'continue' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</div>
<div class="sub-options">
<a href="#" appStopClick (click)="anotherMethod()" role="button">{{'useAnotherTwoStepMethod' | i18n}}</a>
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise" role="button"
*ngIf="selectedProviderType === providerType.Email">
{{'sendVerificationCodeEmailAgain' | i18n}}
</a>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p>{{ "insertYubiKey" | i18n }}</p>
<img src="../../images/yubikey.jpg" alt="" />
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="password"
name="Code"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame">
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<ng-container
*ngIf="
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
"
>
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<div class="box last" *ngIf="selectedProviderType == null">
<div class="box-content">
<div class="box-content-row">
<p>{{ "noTwoStepProviders" | i18n }}</p>
<p>{{ "noTwoStepProviders2" | i18n }}</p>
</div>
</div>
</div>
<div class="buttons">
<button
type="submit"
class="btn primary block"
[disabled]="form.loading"
appBlurClick
*ngIf="
selectedProviderType != null &&
selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo
"
>
<span [hidden]="form.loading"
><i class="fa fa-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}</span
>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
<div class="sub-options">
<a href="#" appStopClick (click)="anotherMethod()" role="button">{{
"useAnotherTwoStepMethod" | i18n
}}</a>
<a
href="#"
appStopClick
(click)="sendEmail(true)"
[appApiAction]="emailPromise"
role="button"
*ngIf="selectedProviderType === providerType.Email"
>
{{ "sendVerificationCodeEmailAgain" | i18n }}
</a>
</div>
</div>
</form>
<ng-template #twoFactorOptions></ng-template>

View File

@ -1,70 +1,84 @@
import {
Component,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { TwoFactorOptionsComponent } from './two-factor-options.component';
import { TwoFactorOptionsComponent } from "./two-factor-options.component";
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib-angular/components/two-factor.component';
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
@Component({
selector: 'app-two-factor',
templateUrl: 'two-factor.component.html',
selector: "app-two-factor",
templateUrl: "two-factor.component.html",
})
export class TwoFactorComponent extends BaseTwoFactorComponent {
@ViewChild('twoFactorOptions', { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef;
@ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
twoFactorOptionsModal: ViewContainerRef;
showingModal = false;
showingModal = false;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, syncService: SyncService,
environmentService: EnvironmentService, private modalService: ModalService,
stateService: StateService, route: ActivatedRoute,
logService: LogService) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
stateService, route, logService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
apiService: ApiService,
platformUtilsService: PlatformUtilsService,
syncService: SyncService,
environmentService: EnvironmentService,
private modalService: ModalService,
stateService: StateService,
route: ActivatedRoute,
logService: LogService
) {
super(
authService,
router,
i18nService,
apiService,
platformUtilsService,
window,
environmentService,
stateService,
route,
logService
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
async anotherMethod() {
const [modal, childComponent] = await this.modalService.openViewRef(TwoFactorOptionsComponent, this.twoFactorOptionsModal);
async anotherMethod() {
const [modal, childComponent] = await this.modalService.openViewRef(
TwoFactorOptionsComponent,
this.twoFactorOptionsModal
);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
childComponent.onRecoverSelected.subscribe(() => {
modal.close();
});
}
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
childComponent.onRecoverSelected.subscribe(() => {
modal.close();
});
}
}

View File

@ -1,80 +1,124 @@
<form id="update-temp-password-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<app-callout type="warning" title="{{'updateMasterPassword' | i18n}}">
{{'updateMasterPasswordWarning' | i18n}}
</app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</app-callout>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreStyle.Color}}"
*ngIf="masterPasswordScoreStyle.Text">
{{masterPasswordScoreStyle.Text}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
[appAutofocus]="masterPassword === ''" (input)="updatePasswordStrength()"
appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreStyle.Color}}" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
[ngStyle]="{width: (masterPasswordScoreStyle.Width + '%')}"
attr.aria-valuenow="{{masterPasswordScoreStyle.Width}}"></div>
</div>
</div>
<div class="content">
<app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{ "updateMasterPasswordWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreStyle.Color }}"
*ngIf="masterPasswordScoreStyle.Text"
>
{{ masterPasswordScoreStyle.Text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="masterPassword === ''"
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="masterPasswordRetype" required
appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreStyle.Color }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreStyle.Width + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreStyle.Width }}"
></div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a (click)="logOut()" class="btn block">{{'logOut' | i18n}}</a>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a (click)="logOut()" class="btn block">{{ "logOut" | i18n }}</a>
</div>
</div>
</form>

View File

@ -1,65 +1,81 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component';
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
interface MasterPasswordScore {
Color: string;
Text: string;
Width: number;
Color: string;
Text: string;
Width: number;
}
@Component({
selector: 'app-update-temp-password',
templateUrl: 'update-temp-password.component.html',
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
return {
Color: 'bg-success',
Text: 'strong',
Width: scoreWidth,
};
case 3:
return {
Color: 'bg-primary',
Text: 'good',
Width: scoreWidth,
};
case 2:
return {
Color: 'bg-warning',
Text: 'weak',
Width: scoreWidth,
};
default:
return {
Color: 'bg-danger',
Text: 'weak',
Width: scoreWidth,
};
}
}
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, messagingService: MessagingService,
apiService: ApiService, syncService: SyncService,
logService: LogService, stateService: StateService) {
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
messagingService, apiService, stateService, syncService, logService);
get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
return {
Color: "bg-success",
Text: "strong",
Width: scoreWidth,
};
case 3:
return {
Color: "bg-primary",
Text: "good",
Width: scoreWidth,
};
case 2:
return {
Color: "bg-warning",
Text: "weak",
Width: scoreWidth,
};
default:
return {
Color: "bg-danger",
Text: "weak",
Width: scoreWidth,
};
}
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
policyService: PolicyService,
cryptoService: CryptoService,
messagingService: MessagingService,
apiService: ApiService,
syncService: SyncService,
logService: LogService,
stateService: StateService
) {
super(
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
messagingService,
apiService,
stateService,
syncService,
logService
);
}
}

View File

@ -1,24 +1,46 @@
<app-callout type="info" *ngIf="vaultTimeoutPolicy">
{{'vaultTimeoutPolicyInEffect' | i18n : vaultTimeoutPolicyHours : vaultTimeoutPolicyMinutes}}
{{ "vaultTimeoutPolicyInEffect" | i18n: vaultTimeoutPolicyHours:vaultTimeoutPolicyMinutes }}
</app-callout>
<div [formGroup]="form">
<div class="form-group">
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label>
<select id="vaultTimeout" name="VaultTimeout" formControlName="vaultTimeout" class="form-control" appAutofocus>
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small>
<div class="form-group">
<label for="vaultTimeout">{{ "vaultTimeout" | i18n }}</label>
<select
id="vaultTimeout"
name="VaultTimeout"
formControlName="vaultTimeout"
class="form-control"
appAutofocus
>
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="form-text text-muted">{{ "vaultTimeoutDesc" | i18n }}</small>
</div>
<div class="form-group row" *ngIf="showCustom" formGroupName="custom">
<div class="col">
<label for="hours">{{ "hours" | i18n }}</label>
<input
id="hours"
class="form-control"
type="number"
min="0"
name="hours"
formControlName="hours"
/>
</div>
<div class="form-group row" *ngIf="showCustom" formGroupName="custom">
<div class="col">
<label for="hours">{{'hours' | i18n}}</label>
<input id="hours" class="form-control" type="number" min="0" name="hours" formControlName="hours">
</div>
<div class="col">
<label for="minutes">{{'minutes' | i18n}}</label>
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes" formControlName="minutes">
</div>
<div class="col">
<label for="minutes">{{ "minutes" | i18n }}</label>
<input
id="minutes"
class="form-control"
type="number"
min="0"
max="59"
name="minutes"
formControlName="minutes"
/>
</div>
<div class="form-group"></div> <!-- Styling fix -->
</div>
<div class="form-group"></div>
<!-- Styling fix -->
</div>

View File

@ -1,28 +1,22 @@
import { Component } from '@angular/core';
import {
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import { Component } from "@angular/core";
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from "@angular/forms";
import {
VaultTimeoutInputComponent as VaultTimeoutInputComponentBase
} from 'jslib-angular/components/settings/vault-timeout-input.component';
import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "jslib-angular/components/settings/vault-timeout-input.component";
@Component({
selector: 'app-vault-timeout-input',
templateUrl: 'vault-timeout-input.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
],
selector: "app-vault-timeout-input",
templateUrl: "vault-timeout-input.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
],
})
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {
}
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {}

View File

@ -1,72 +1,71 @@
import { NgModule } from '@angular/core';
import {
RouterModule,
Routes,
} from '@angular/router';
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service';
import { LockGuardService } from 'jslib-angular/services/lock-guard.service';
import { LoginGuardService } from '../services/loginGuard.service';
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { LockGuardService } from "jslib-angular/services/lock-guard.service";
import { LoginGuardService } from "../services/loginGuard.service";
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { RegisterComponent } from './accounts/register.component';
import { RemovePasswordComponent } from './accounts/remove-password.component';
import { SetPasswordComponent } from './accounts/set-password.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { SendComponent } from './send/send.component';
import { SendComponent } from "./send/send.component";
import { VaultComponent } from './vault/vault.component';
import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [
{ path: '', redirectTo: '/vault', pathMatch: 'full' },
{
path: 'lock',
component: LockComponent,
canActivate: [LockGuardService],
},
{
path: 'login',
component: LoginComponent,
canActivate: [LoginGuardService],
},
{ path: '2fa', component: TwoFactorComponent },
{ path: 'register', component: RegisterComponent },
{
path: 'vault',
component: VaultComponent,
canActivate: [AuthGuardService],
},
{ path: 'hint', component: HintComponent },
{ path: 'set-password', component: SetPasswordComponent },
{ path: 'sso', component: SsoComponent },
{
path: 'send',
component: SendComponent,
canActivate: [AuthGuardService],
},
{
path: 'update-temp-password',
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
},
{
path: 'remove-password',
component: RemovePasswordComponent,
canActivate: [AuthGuardService],
data: { titleId: 'removeMasterPassword' },
},
{ path: "", redirectTo: "/vault", pathMatch: "full" },
{
path: "lock",
component: LockComponent,
canActivate: [LockGuardService],
},
{
path: "login",
component: LoginComponent,
canActivate: [LoginGuardService],
},
{ path: "2fa", component: TwoFactorComponent },
{ path: "register", component: RegisterComponent },
{
path: "vault",
component: VaultComponent,
canActivate: [AuthGuardService],
},
{ path: "hint", component: HintComponent },
{ path: "set-password", component: SetPasswordComponent },
{ path: "sso", component: SsoComponent },
{
path: "send",
component: SendComponent,
canActivate: [AuthGuardService],
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
},
{
path: "remove-password",
component: RemovePasswordComponent,
canActivate: [AuthGuardService],
data: { titleId: "removeMasterPassword" },
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
})],
exports: [RouterModule],
imports: [
RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
}),
],
exports: [RouterModule],
})
export class AppRoutingModule { }
export class AppRoutingModule {}

File diff suppressed because it is too large Load Diff

View File

@ -1,257 +1,257 @@
import 'zone.js/dist/zone';
import "zone.js/dist/zone";
import { A11yModule } from '@angular/cdk/a11y';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { OverlayModule } from '@angular/cdk/overlay';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { DatePipe } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import 'zone.js/dist/zone';
import { A11yModule } from "@angular/cdk/a11y";
import { DragDropModule } from "@angular/cdk/drag-drop";
import { OverlayModule } from "@angular/cdk/overlay";
import { ScrollingModule } from "@angular/cdk/scrolling";
import { DatePipe } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import "zone.js/dist/zone";
import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services.module';
import { AppRoutingModule } from "./app-routing.module";
import { ServicesModule } from "./services.module";
import { AppComponent } from './app.component';
import { AppComponent } from "./app.component";
import { EnvironmentComponent } from './accounts/environment.component';
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { PremiumComponent } from './accounts/premium.component';
import { RegisterComponent } from './accounts/register.component';
import { RemovePasswordComponent } from './accounts/remove-password.component';
import { SetPasswordComponent } from './accounts/set-password.component';
import { SettingsComponent } from './accounts/settings.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
import { VaultTimeoutInputComponent } from './accounts/vault-timeout-input.component';
import { EnvironmentComponent } from "./accounts/environment.component";
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { PremiumComponent } from "./accounts/premium.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SettingsComponent } from "./accounts/settings.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { VaultTimeoutInputComponent } from "./accounts/vault-timeout-input.component";
import { AvatarComponent } from 'jslib-angular/components/avatar.component';
import { CalloutComponent } from 'jslib-angular/components/callout.component';
import { IconComponent } from 'jslib-angular/components/icon.component';
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component';
import { AvatarComponent } from "jslib-angular/components/avatar.component";
import { CalloutComponent } from "jslib-angular/components/callout.component";
import { IconComponent } from "jslib-angular/components/icon.component";
import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive';
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive';
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive';
import { CipherListVirtualScroll } from 'jslib-angular/directives/cipherListVirtualScroll.directive';
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive';
import { InputVerbatimDirective } from 'jslib-angular/directives/input-verbatim.directive';
import { SelectCopyDirective } from 'jslib-angular/directives/select-copy.directive';
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive';
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive';
import { TrueFalseValueDirective } from 'jslib-angular/directives/true-false-value.directive';
import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
import { CipherListVirtualScroll } from "jslib-angular/directives/cipherListVirtualScroll.directive";
import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive";
import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
import { StopPropDirective } from "jslib-angular/directives/stop-prop.directive";
import { TrueFalseValueDirective } from "jslib-angular/directives/true-false-value.directive";
import { ColorPasswordPipe } from 'jslib-angular/pipes/color-password.pipe';
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe';
import { ColorPasswordPipe } from "jslib-angular/pipes/color-password.pipe";
import { I18nPipe } from "jslib-angular/pipes/i18n.pipe";
import { SearchCiphersPipe } from "jslib-angular/pipes/search-ciphers.pipe";
import { AddEditCustomFieldsComponent } from './vault/add-edit-custom-fields.component';
import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component';
import { CiphersComponent } from './vault/ciphers.component';
import { CollectionsComponent } from './vault/collections.component';
import { ExportComponent } from './vault/export.component';
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
import { GroupingsComponent } from './vault/groupings.component';
import { PasswordGeneratorHistoryComponent } from './vault/password-generator-history.component';
import { PasswordGeneratorComponent } from './vault/password-generator.component';
import { PasswordHistoryComponent } from './vault/password-history.component';
import { ShareComponent } from './vault/share.component';
import { VaultComponent } from './vault/vault.component';
import { ViewCustomFieldsComponent } from './vault/view-custom-fields.component';
import { ViewComponent } from './vault/view.component';
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component";
import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component";
import { ExportComponent } from "./vault/export.component";
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
import { GroupingsComponent } from "./vault/groupings.component";
import { PasswordGeneratorHistoryComponent } from "./vault/password-generator-history.component";
import { PasswordGeneratorComponent } from "./vault/password-generator.component";
import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from "./vault/share.component";
import { VaultComponent } from "./vault/vault.component";
import { ViewCustomFieldsComponent } from "./vault/view-custom-fields.component";
import { ViewComponent } from "./vault/view.component";
import { AddEditComponent as SendAddEditComponent } from './send/add-edit.component';
import { EffluxDatesComponent as SendEffluxDatesComponent } from './send/efflux-dates.component';
import { SendComponent } from './send/send.component';
import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
import { SendComponent } from "./send/send.component";
import { AccountSwitcherComponent } from './layout/account-switcher.component';
import { HeaderComponent } from './layout/header.component';
import { NavComponent } from './layout/nav.component';
import { SearchComponent } from './layout/search/search.component';
import { AccountSwitcherComponent } from "./layout/account-switcher.component";
import { HeaderComponent } from "./layout/header.component";
import { NavComponent } from "./layout/nav.component";
import { SearchComponent } from "./layout/search/search.component";
import { PasswordRepromptComponent } from './components/password-reprompt.component';
import { SetPinComponent } from './components/set-pin.component';
import { VerifyMasterPasswordComponent } from './components/verify-master-password.component';
import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { SetPinComponent } from "./components/set-pin.component";
import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component";
import { registerLocaleData } from '@angular/common';
import localeAf from '@angular/common/locales/af';
import localeAz from '@angular/common/locales/az';
import localeBe from '@angular/common/locales/be';
import localeBg from '@angular/common/locales/bg';
import localeBn from '@angular/common/locales/bn';
import localeCa from '@angular/common/locales/ca';
import localeCs from '@angular/common/locales/cs';
import localeDa from '@angular/common/locales/da';
import localeDe from '@angular/common/locales/de';
import localeEl from '@angular/common/locales/el';
import localeEnGb from '@angular/common/locales/en-GB';
import localeEnIn from '@angular/common/locales/en-IN';
import localeEs from '@angular/common/locales/es';
import localeEt from '@angular/common/locales/et';
import localeFa from '@angular/common/locales/fa';
import localeFi from '@angular/common/locales/fi';
import localeFr from '@angular/common/locales/fr';
import localeHe from '@angular/common/locales/he';
import localeHr from '@angular/common/locales/hr';
import localeHu from '@angular/common/locales/hu';
import localeId from '@angular/common/locales/id';
import localeIt from '@angular/common/locales/it';
import localeJa from '@angular/common/locales/ja';
import localeKn from '@angular/common/locales/kn';
import localeKo from '@angular/common/locales/ko';
import localeLv from '@angular/common/locales/lv';
import localeMl from '@angular/common/locales/ml';
import localeNb from '@angular/common/locales/nb';
import localeNl from '@angular/common/locales/nl';
import localePl from '@angular/common/locales/pl';
import localePtBr from '@angular/common/locales/pt';
import localePtPt from '@angular/common/locales/pt-PT';
import localeRo from '@angular/common/locales/ro';
import localeRu from '@angular/common/locales/ru';
import localeSk from '@angular/common/locales/sk';
import localeSr from '@angular/common/locales/sr';
import localeMe from '@angular/common/locales/sr-Latn-ME';
import localeSv from '@angular/common/locales/sv';
import localeTh from '@angular/common/locales/th';
import localeTr from '@angular/common/locales/tr';
import localeUk from '@angular/common/locales/uk';
import localeVi from '@angular/common/locales/vi';
import localeZhCn from '@angular/common/locales/zh-Hans';
import localeZhTw from '@angular/common/locales/zh-Hant';
import { registerLocaleData } from "@angular/common";
import localeAf from "@angular/common/locales/af";
import localeAz from "@angular/common/locales/az";
import localeBe from "@angular/common/locales/be";
import localeBg from "@angular/common/locales/bg";
import localeBn from "@angular/common/locales/bn";
import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs";
import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el";
import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN";
import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et";
import localeFa from "@angular/common/locales/fa";
import localeFi from "@angular/common/locales/fi";
import localeFr from "@angular/common/locales/fr";
import localeHe from "@angular/common/locales/he";
import localeHr from "@angular/common/locales/hr";
import localeHu from "@angular/common/locales/hu";
import localeId from "@angular/common/locales/id";
import localeIt from "@angular/common/locales/it";
import localeJa from "@angular/common/locales/ja";
import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko";
import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml";
import localeNb from "@angular/common/locales/nb";
import localeNl from "@angular/common/locales/nl";
import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from "@angular/common/locales/ro";
import localeRu from "@angular/common/locales/ru";
import localeSk from "@angular/common/locales/sk";
import localeSr from "@angular/common/locales/sr";
import localeMe from "@angular/common/locales/sr-Latn-ME";
import localeSv from "@angular/common/locales/sv";
import localeTh from "@angular/common/locales/th";
import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk";
import localeVi from "@angular/common/locales/vi";
import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from "@angular/common/locales/zh-Hant";
registerLocaleData(localeAf, 'af');
registerLocaleData(localeAz, 'az');
registerLocaleData(localeBe, 'be');
registerLocaleData(localeBg, 'bg');
registerLocaleData(localeBn, 'bn');
registerLocaleData(localeCa, 'ca');
registerLocaleData(localeCs, 'cs');
registerLocaleData(localeDa, 'da');
registerLocaleData(localeDe, 'de');
registerLocaleData(localeEl, 'el');
registerLocaleData(localeEnGb, 'en-GB');
registerLocaleData(localeEnIn, 'en-IN');
registerLocaleData(localeEs, 'es');
registerLocaleData(localeEt, 'et');
registerLocaleData(localeFa, 'fa');
registerLocaleData(localeFi, 'fi');
registerLocaleData(localeFr, 'fr');
registerLocaleData(localeHe, 'he');
registerLocaleData(localeHr, 'hr');
registerLocaleData(localeHu, 'hu');
registerLocaleData(localeId, 'id');
registerLocaleData(localeIt, 'it');
registerLocaleData(localeJa, 'ja');
registerLocaleData(localeKn, 'kn');
registerLocaleData(localeKo, 'ko');
registerLocaleData(localeLv, 'lv');
registerLocaleData(localeMe, 'me');
registerLocaleData(localeMl, 'ml');
registerLocaleData(localeNb, 'nb');
registerLocaleData(localeNl, 'nl');
registerLocaleData(localePl, 'pl');
registerLocaleData(localePtBr, 'pt-BR');
registerLocaleData(localePtPt, 'pt-PT');
registerLocaleData(localeRo, 'ro');
registerLocaleData(localeRu, 'ru');
registerLocaleData(localeSk, 'sk');
registerLocaleData(localeSr, 'sr');
registerLocaleData(localeSv, 'sv');
registerLocaleData(localeTh, 'th');
registerLocaleData(localeTr, 'tr');
registerLocaleData(localeUk, 'uk');
registerLocaleData(localeVi, 'vi');
registerLocaleData(localeZhCn, 'zh-CN');
registerLocaleData(localeZhTw, 'zh-TW');
registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, "bn");
registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et");
registerLocaleData(localeFa, "fa");
registerLocaleData(localeFi, "fi");
registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, "he");
registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, "id");
registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, "ja");
registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, "ko");
registerLocaleData(localeLv, "lv");
registerLocaleData(localeMe, "me");
registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, "nl");
registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, "ru");
registerLocaleData(localeSk, "sk");
registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv");
registerLocaleData(localeTh, "th");
registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW");
@NgModule({
imports: [
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
DragDropModule,
FormsModule,
ReactiveFormsModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
ScrollingModule,
A11yModule,
OverlayModule,
],
declarations: [
A11yTitleDirective,
AddEditComponent,
AddEditCustomFieldsComponent,
ApiActionDirective,
AppComponent,
AttachmentsComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
CipherListVirtualScroll,
CiphersComponent,
CollectionsComponent,
ColorPasswordPipe,
EnvironmentComponent,
ExportComponent,
FallbackSrcDirective,
FolderAddEditComponent,
GroupingsComponent,
HintComponent,
I18nPipe,
IconComponent,
InputVerbatimDirective,
LockComponent,
LoginComponent,
NavComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordHistoryComponent,
PasswordRepromptComponent,
PremiumComponent,
RegisterComponent,
RemovePasswordComponent,
SearchCiphersPipe,
SelectCopyDirective,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SetPasswordComponent,
SetPinComponent,
SettingsComponent,
ShareComponent,
SsoComponent,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
TwoFactorComponent,
TwoFactorOptionsComponent,
UpdateTempPasswordComponent,
VaultComponent,
VaultTimeoutInputComponent,
VerifyMasterPasswordComponent,
ViewComponent,
ViewCustomFieldsComponent,
HeaderComponent,
AccountSwitcherComponent,
AvatarComponent,
SearchComponent,
],
providers: [DatePipe],
bootstrap: [AppComponent],
imports: [
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
DragDropModule,
FormsModule,
ReactiveFormsModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
ScrollingModule,
A11yModule,
OverlayModule,
],
declarations: [
A11yTitleDirective,
AddEditComponent,
AddEditCustomFieldsComponent,
ApiActionDirective,
AppComponent,
AttachmentsComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
CipherListVirtualScroll,
CiphersComponent,
CollectionsComponent,
ColorPasswordPipe,
EnvironmentComponent,
ExportComponent,
FallbackSrcDirective,
FolderAddEditComponent,
GroupingsComponent,
HintComponent,
I18nPipe,
IconComponent,
InputVerbatimDirective,
LockComponent,
LoginComponent,
NavComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordHistoryComponent,
PasswordRepromptComponent,
PremiumComponent,
RegisterComponent,
RemovePasswordComponent,
SearchCiphersPipe,
SelectCopyDirective,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SetPasswordComponent,
SetPinComponent,
SettingsComponent,
ShareComponent,
SsoComponent,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
TwoFactorComponent,
TwoFactorOptionsComponent,
UpdateTempPasswordComponent,
VaultComponent,
VaultTimeoutInputComponent,
VerifyMasterPasswordComponent,
ViewComponent,
ViewCustomFieldsComponent,
HeaderComponent,
AccountSwitcherComponent,
AvatarComponent,
SearchComponent,
],
providers: [DatePipe],
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}

View File

@ -1,38 +1,55 @@
<div class="modal fade" role="dialog" aria-modal="true">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">{{'passwordConfirmation' | i18n}}</div>
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required appAutofocus>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'passwordConfirmationDesc' | i18n}}
</div>
</div>
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">{{ "passwordConfirmation" | i18n }}</div>
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appAutofocus
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{'ok' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}}
</button>
</div>
</form>
</div>
</div>
<div class="box-footer">
{{ "passwordConfirmationDesc" | i18n }}
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@ -1,8 +1,8 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from 'jslib-angular/components/password-reprompt.component';
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from "jslib-angular/components/password-reprompt.component";
@Component({
templateUrl: 'password-reprompt.component.html',
templateUrl: "password-reprompt.component.html",
})
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}

View File

@ -1,44 +1,65 @@
<div class="modal fade" role="dialog" aria-modal="true">
<div class="modal-dialog modal-dialog-scrollable set-pin-modal" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div>
{{'setYourPinCode' | i18n}}
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="pin">{{'pin' | i18n}}</label>
<input id="pin" type="{{showPin ? 'text' : 'password'}}" name="Pin" class="monospaced"
[(ngModel)]="pin" required appInputVerbatim appAutofocus>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleVisibility()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPin, 'fa-eye-slash': showPin}"></i>
</a>
</div>
</div>
</div>
</div>
<div class="checkbox" *ngIf="showMasterPassOnRestart">
<label for="masterPasswordOnRestart">
<input type="checkbox" id="masterPasswordOnRestart" name="MasterPasswordOnRestart"
[(ngModel)]="masterPassOnRestart">
<span>{{'lockWithMasterPassOnRestart' | i18n}}</span>
</label>
</div>
<div class="modal-dialog modal-dialog-scrollable set-pin-modal" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div>
{{ "setYourPinCode" | i18n }}
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="pin">{{ "pin" | i18n }}</label>
<input
id="pin"
type="{{ showPin ? 'text' : 'password' }}"
name="Pin"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
appAutofocus
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleVisibility()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPin, 'fa-eye-slash': showPin }"
></i>
</a>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{'ok' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}}
</button>
</div>
</form>
</div>
</div>
</div>
<div class="checkbox" *ngIf="showMasterPassOnRestart">
<label for="masterPasswordOnRestart">
<input
type="checkbox"
id="masterPasswordOnRestart"
name="MasterPasswordOnRestart"
[(ngModel)]="masterPassOnRestart"
/>
<span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@ -1,8 +1,8 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { SetPinComponent as BaseSetPinComponent } from 'jslib-angular/components/set-pin.component';
import { SetPinComponent as BaseSetPinComponent } from "jslib-angular/components/set-pin.component";
@Component({
templateUrl: 'set-pin.component.html',
templateUrl: "set-pin.component.html",
})
export class SetPinComponent extends BaseSetPinComponent { }
export class SetPinComponent extends BaseSetPinComponent {}

View File

@ -1,25 +1,46 @@
<ng-container *ngIf="!usesKeyConnector">
<div class="box-content-row" appBoxRow>
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
[formControl]="secret" required appAutofocus appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div>
</ng-container>
<ng-container *ngIf="usesKeyConnector">
<div class="box-content-row" appBoxRow>
<label class="d-block">{{'sendVerificationCode' | i18n}}</label>
<button type="button" class="btn btn-outline-secondary" (click)="requestOTP()" [disabled]="disableRequestOTP">
{{'sendCode' | i18n}}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{'codeSent' | i18n}}
</span>
</div>
<div class="box-content-row" appBoxRow>
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
<button
type="button"
class="btn btn-outline-secondary"
(click)="requestOTP()"
[disabled]="disableRequestOTP"
>
{{ "sendCode" | i18n }}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{ "codeSent" | i18n }}
</span>
</div>
<div class="box-content-row" appBoxRow>
<label for="verificationCode">{{'verificationCode' | i18n}}</label>
<input id="verificationCode" type="input" name="verificationCode" class="form-control"
[formControl]="secret" required appAutofocus appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
<input
id="verificationCode"
type="input"
name="verificationCode"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div>
</ng-container>

View File

@ -1,31 +1,23 @@
import {
animate,
style,
transition,
trigger,
} from '@angular/animations';
import { Component } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { animate, style, transition, trigger } from "@angular/animations";
import { Component } from "@angular/core";
import { NG_VALUE_ACCESSOR } from "@angular/forms";
import { VerifyMasterPasswordComponent as BaseComponent } from 'jslib-angular/components/verify-master-password.component';
import { VerifyMasterPasswordComponent as BaseComponent } from "jslib-angular/components/verify-master-password.component";
@Component({
selector: 'app-verify-master-password',
templateUrl: 'verify-master-password.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger('sent', [
transition(':enter', [
style({ opacity: 0 }),
animate('100ms', style({ opacity: 1 })),
]),
]),
],
selector: "app-verify-master-password",
templateUrl: "verify-master-password.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger("sent", [
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
]),
],
})
export class VerifyMasterPasswordComponent extends BaseComponent { }
export class VerifyMasterPasswordComponent extends BaseComponent {}

View File

@ -1,22 +1,50 @@
<a class="account-switcher" (click)="toggle()" cdkOverlayOrigin #trigger="cdkOverlayOrigin" [hidden]="!showSwitcher">
<app-avatar [data]="activeAccountEmail" size="25" [circle]="true" [fontSize]="14" [dynamic]="true" *ngIf="activeAccountEmail != null"></app-avatar>
<span>{{activeAccountEmail}}</span>
<i class="fa" aria-hidden="true" [ngClass]="{'fa-chevron-down': !isOpen, 'fa-chevron-up': isOpen}"></i>
<a
class="account-switcher"
(click)="toggle()"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
[hidden]="!showSwitcher"
>
<app-avatar
[data]="activeAccountEmail"
size="25"
[circle]="true"
[fontSize]="14"
[dynamic]="true"
*ngIf="activeAccountEmail != null"
></app-avatar>
<span>{{ activeAccountEmail }}</span>
<i
class="fa"
aria-hidden="true"
[ngClass]="{ 'fa-chevron-down': !isOpen, 'fa-chevron-up': isOpen }"
></i>
</a>
<ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOpen]="isOpen" cdkConnectedOverlayMinWidth="250px">
<div class="account-switcher-dropdown" [@transformPanel]="'open'">
<div class="accounts">
<a *ngFor="let a of accounts | keyvalue" class="account" [ngClass]="{'active': a.value.profile.authenticationStatus == 'active'}"
href="#" appStopClick (click)="switch(a.key)">
<span class="email">{{a.value.profile.email}}</span>
<span class="server">{{a.value.settings.environmentUrls.server}}</span>
<span class="status">{{a.value.profile.authenticationStatus}}</span>
</a>
</div>
<div class="border"></div>
<a class="add" routerLink="/login" (click)="toggle()">
<i class="fa fa-plus" aria-hidden="true"></i> {{'addAccount' | i18n}}
</a>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen"
cdkConnectedOverlayMinWidth="250px"
>
<div class="account-switcher-dropdown" [@transformPanel]="'open'">
<div class="accounts">
<a
*ngFor="let a of accounts | keyvalue"
class="account"
[ngClass]="{ active: a.value.profile.authenticationStatus == 'active' }"
href="#"
appStopClick
(click)="switch(a.key)"
>
<span class="email">{{ a.value.profile.email }}</span>
<span class="server">{{ a.value.settings.environmentUrls.server }}</span>
<span class="status">{{ a.value.profile.authenticationStatus }}</span>
</a>
</div>
<div class="border"></div>
<a class="add" routerLink="/login" (click)="toggle()">
<i class="fa fa-plus" aria-hidden="true"></i> {{ "addAccount" | i18n }}
</a>
</div>
</ng-template>

View File

@ -1,78 +1,87 @@
import {
animate,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { animate, state, style, transition, trigger } from "@angular/animations";
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { AuthenticationStatus } from 'jslib-common/enums/authenticationStatus';
import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus";
import { Account } from 'jslib-common/models/domain/account';
import { Account } from "jslib-common/models/domain/account";
@Component({
selector: 'app-account-switcher',
templateUrl: 'account-switcher.component.html',
animations: [
trigger('transformPanel', [
state('void', style({
opacity: 0,
})),
transition('void => open', animate('100ms linear', style({
opacity: 1,
}))),
transition('* => void', animate('100ms linear', style({opacity: 0}))),
]),
],
selector: "app-account-switcher",
templateUrl: "account-switcher.component.html",
animations: [
trigger("transformPanel", [
state(
"void",
style({
opacity: 0,
})
),
transition(
"void => open",
animate(
"100ms linear",
style({
opacity: 1,
})
)
),
transition("* => void", animate("100ms linear", style({ opacity: 0 }))),
]),
],
})
export class AccountSwitcherComponent implements OnInit {
isOpen: boolean = false;
accounts: { [userId: string]: Account } = {};
activeAccountEmail: string;
isOpen: boolean = false;
accounts: { [userId: string]: Account } = {};
activeAccountEmail: string;
get showSwitcher() {
return this.accounts != null && Object.keys(this.accounts).length > 0;
}
get showSwitcher() {
return this.accounts != null && Object.keys(this.accounts).length > 0;
}
constructor(private stateService: StateService, private vaultTimeoutService: VaultTimeoutService,
private messagingService: MessagingService, private router: Router) {}
constructor(
private stateService: StateService,
private vaultTimeoutService: VaultTimeoutService,
private messagingService: MessagingService,
private router: Router
) {}
async ngOnInit(): Promise<void> {
this.stateService.accounts.subscribe(async accounts => {
for (const userId in accounts) {
if (userId === await this.stateService.getUserId()) {
accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active;
} else {
accounts[userId].profile.authenticationStatus = await this.vaultTimeoutService.isLocked(userId) ?
AuthenticationStatus.Locked :
AuthenticationStatus.Unlocked;
}
}
this.accounts = accounts;
this.activeAccountEmail = await this.stateService.getEmail();
});
}
toggle() {
this.isOpen = !this.isOpen;
}
async switch(userId: string) {
await this.stateService.setActiveUser(userId);
const locked = await this.vaultTimeoutService.isLocked(userId);
if (locked) {
this.messagingService.send('locked', { userId: userId });
async ngOnInit(): Promise<void> {
this.stateService.accounts.subscribe(async (accounts) => {
for (const userId in accounts) {
if (userId === (await this.stateService.getUserId())) {
accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active;
} else {
this.messagingService.send('unlocked');
this.messagingService.send('syncVault');
this.router.navigate(['vault']);
accounts[userId].profile.authenticationStatus = (await this.vaultTimeoutService.isLocked(
userId
))
? AuthenticationStatus.Locked
: AuthenticationStatus.Unlocked;
}
}
this.accounts = accounts;
this.activeAccountEmail = await this.stateService.getEmail();
});
}
toggle() {
this.isOpen = !this.isOpen;
}
async switch(userId: string) {
await this.stateService.setActiveUser(userId);
const locked = await this.vaultTimeoutService.isLocked(userId);
if (locked) {
this.messagingService.send("locked", { userId: userId });
} else {
this.messagingService.send("unlocked");
this.messagingService.send("syncVault");
this.router.navigate(["vault"]);
}
}
}

View File

@ -1,4 +1,4 @@
<div class="header">
<app-search></app-search>
<app-account-switcher></app-account-switcher>
<app-search></app-search>
<app-account-switcher></app-account-switcher>
</div>

View File

@ -1,8 +1,7 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
@Component({
selector: 'app-header',
templateUrl: 'header.component.html',
selector: "app-header",
templateUrl: "header.component.html",
})
export class HeaderComponent {
}
export class HeaderComponent {}

View File

@ -1,5 +1,5 @@
<ng-container *ngFor="let item of items">
<a [routerLink]="item.link" class="btn primary" routerLinkActive="active" [title]="item.label">
<i class="fa" [ngClass]="item.icon"></i>{{ item.label }}
</a>
<a [routerLink]="item.link" class="btn primary" routerLinkActive="active" [title]="item.label">
<i class="fa" [ngClass]="item.icon"></i>{{ item.label }}
</a>
</ng-container>

View File

@ -1,23 +1,23 @@
import { Component } from '@angular/core';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { Component } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@Component({
selector: 'app-nav',
templateUrl: 'nav.component.html',
selector: "app-nav",
templateUrl: "nav.component.html",
})
export class NavComponent {
items: any[] = [
{
link: '/vault',
icon: 'fa-lock',
label: this.i18nService.translate('myVault'),
},
{
link: '/send',
icon: 'fa-paper-plane',
label: 'Send',
},
];
items: any[] = [
{
link: "/vault",
icon: "fa-lock",
label: this.i18nService.translate("myVault"),
},
{
link: "/send",
icon: "fa-paper-plane",
label: "Send",
},
];
constructor(private i18nService: I18nService) {}
constructor(private i18nService: I18nService) {}
}

View File

@ -1,39 +1,38 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
export type SearchBarState = {
enabled: boolean;
placeholderText: string;
enabled: boolean;
placeholderText: string;
};
@Injectable()
export class SearchBarService {
searchText = new BehaviorSubject<string>(null);
searchText = new BehaviorSubject<string>(null);
private _state = {
enabled: false,
placeholderText: "",
};
private _state = {
enabled: false,
placeholderText: '',
};
// tslint:disable-next-line:member-ordering
state = new BehaviorSubject<SearchBarState>(this._state);
// tslint:disable-next-line:member-ordering
state = new BehaviorSubject<SearchBarState>(this._state);
setEnabled(enabled: boolean) {
this._state.enabled = enabled;
this.updateState();
}
setEnabled(enabled: boolean) {
this._state.enabled = enabled;
this.updateState();
}
setPlaceholderText(placeholderText: string) {
this._state.placeholderText = placeholderText;
this.updateState();
}
setPlaceholderText(placeholderText: string) {
this._state.placeholderText = placeholderText;
this.updateState();
}
setSearchText(value: string) {
this.searchText.next(value);
}
setSearchText(value: string) {
this.searchText.next(value);
}
private updateState() {
this.state.next(this._state);
}
private updateState() {
this.state.next(this._state);
}
}

View File

@ -1,4 +1,11 @@
<div class="search" *ngIf="state.enabled">
<input type="search" [placeholder]="state.placeholderText" id="search" autocomplete="off" [formControl]="searchText" appAutofocus>
<i class="fa fa-search" aria-hidden="true"></i>
<input
type="search"
[placeholder]="state.placeholderText"
id="search"
autocomplete="off"
[formControl]="searchText"
appAutofocus
/>
<i class="fa fa-search" aria-hidden="true"></i>
</div>

View File

@ -1,24 +1,23 @@
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Component } from "@angular/core";
import { FormControl } from "@angular/forms";
import { SearchBarService, SearchBarState } from './search-bar.service';
import { SearchBarService, SearchBarState } from "./search-bar.service";
@Component({
selector: 'app-search',
templateUrl: 'search.component.html',
selector: "app-search",
templateUrl: "search.component.html",
})
export class SearchComponent {
state: SearchBarState;
searchText: FormControl = new FormControl(null);
state: SearchBarState;
searchText: FormControl = new FormControl(null);
constructor(private searchBarService: SearchBarService) {
this.searchBarService.state.subscribe((state) => {
this.state = state;
});
constructor(private searchBarService: SearchBarService) {
this.searchBarService.state.subscribe(state => {
this.state = state;
});
this.searchText.valueChanges.subscribe(value => {
this.searchBarService.setSearchText(value);
});
}
this.searchText.valueChanges.subscribe((value) => {
this.searchBarService.setSearchText(value);
});
}
}

View File

@ -1,19 +1,19 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { isDev } from 'jslib-electron/utils';
import { isDev } from "jslib-electron/utils";
// tslint:disable-next-line
require('../scss/styles.scss');
require("../scss/styles.scss");
import { AppModule } from './app.module';
import { AppModule } from "./app.module";
if (!isDev()) {
enableProdMode();
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
// Disable drag and drop to prevent malicious links from executing in the context of the app
document.addEventListener('dragover', event => event.preventDefault());
document.addEventListener('drop', event => event.preventDefault());
document.addEventListener("dragover", (event) => event.preventDefault());
document.addEventListener("drop", (event) => event.preventDefault());

View File

@ -1,176 +1,297 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<div class="inner-content" *ngIf="send">
<div class="box">
<app-callout *ngIf="disableSend">
<span>{{'sendDisabledWarning' | i18n}}</span>
</app-callout>
<app-callout type="info" *ngIf="disableHideEmail && !disableSend">
{{'sendOptionsPolicyInEffect' | i18n}}
</app-callout>
</div>
<div class="box">
<div class="box-header">
{{title}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label>
<input id="name" type="text" name="Name" [(ngModel)]="send.name" appAutofocus [readOnly]="disableSend">
</div>
<div class="box-content-row box-content-row-radio" *ngIf="!editMode">
<label class="radio-header">{{'whatTypeOfSend' | i18n}}</label>
<div class="item" *ngFor="let o of typeOptions">
<input type="radio" class="radio" [(ngModel)]="send.type" name="Type_{{o.value}}"
id="type_{{o.value}}" [value]="o.value" (change)="typeChanged(o)"
[checked]="send.type === o.value" [disabled]="disableSend">
<label class="unstyled" for="type_{{o.value}}">
{{o.name}}
</label>
</div>
</div>
<div class="box-content-row" appBowRow *ngIf="!editMode && send.type === sendType.File">
<label for="file">{{'file' | i18n}}</label>
<input type="file" id="file" class="form-control-file" name="file" required [disabled]="disableSend">
</div>
<div class="box-content-row" appBowRow *ngIf="editMode && send.type === sendType.File">
<label for="file">{{'file' | i18n}}</label>
<div class="row-main">{{send.file.fileName}} ({{send.file.sizeName}})</div>
</div>
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text">
<label for="text">{{'text' | i18n}}</label>
<textarea id="text" name="text" [(ngModel)]="send.text.text" rows="6" [readOnly]="disableSend"></textarea>
</div>
</div>
<div class="box-footer" *ngIf="!editMode && send.type === sendType.File">
{{'sendFileDesc' | i18n}} {{'maxFileSize' | i18n}}
</div>
<div class="box-footer" *ngIf="send.type === sendType.Text">
{{'sendTextDesc' | i18n}}
</div>
</div>
<div class="box" *ngIf="send.type === sendType.Text">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideText">{{'textHiddenByDefault' | i18n}}</label>
<input id="hideText" name="hideText" type="checkbox" [(ngModel)]="send.text.hidden" [disabled]="disableSend">
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'options' | i18n}}
<a class="toggle" href="#" appStopClick appBlurClick role="button" (click)="toggleOptions()">
<i class="fa fa-lg" aria-hidden="true" [ngClass]="{'fa-chevron-down': !showOptions, 'fa-chevron-up': showOptions}"></i>
</a>
</div>
</div>
<div [hidden]="!showOptions">
<app-send-efflux-dates
[initialDeletionDate]="send.deletionDate" [initialExpirationDate]="send.expirationDate"
[editMode]="editMode" [disabled]="disableSend" (datesChanged)="setDates($event)">
</app-send-efflux-dates>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label>
<input id="maxAccessCount" type="number" name="maxAccessCount" [(ngModel)]="send.maxAccessCount" [readOnly]="disableSend">
</div>
</div>
<div class="box-footer" *ngIf="!editMode">
{{'maxAccessCountDesc' | i18n}}
</div>
<div class="box-footer" *ngIf="editMode">
<p>{{'maxAccessCountDesc' | i18n}}</p>
{{'currentAccessCount' | i18n}}: <strong>{{send.accessCount}}</strong>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="password">{{(hasPassword ? 'newPassword' : 'password') | i18n}}</label>
<input id="password" name="password" type="{{showPassword ? 'text' : 'password'}}"
[(ngModel)]="password" [readOnly]="disableSend" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePasswordVisible()" [disabled]="disableSend">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'sendPasswordDesc' | i18n}}
</div>
</div>
<div class="box">
<div class="box-header">
{{'notes' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea id="notes" name="notes" [(ngModel)]="send.notes" rows="6" [readOnly]="disableSend"></textarea>
</div>
</div>
<div class="box-footer">
{{'sendNotesDesc' | i18n}}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideEmail">{{'hideEmail' | i18n}}</label>
<input id="hideEmail" type="checkbox" name="HideEmail" [(ngModel)]="send.hideEmail"
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend">
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="disabled">{{'disableSend' | i18n}}</label>
<input id="disabled" type="checkbox" name="disabled" [(ngModel)]="send.disabled" [disabled]="disableSend">
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'share' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow *ngIf="editMode">
<label for="link">{{'sendLinkLabel' | i18n}}</label>
<input id="link" name="link" [ngModel]="link" readonly>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="copyLink">{{'copySendLinkOnSave' | i18n}}</label>
<input id="copyLink" name="copyLink" [(ngModel)]="copyLink" type="checkbox" [disabled]="disableSend">
</div>
</div>
</div>
<div class="content">
<div class="inner-content" *ngIf="send">
<div class="box">
<app-callout *ngIf="disableSend">
<span>{{ "sendDisabledWarning" | i18n }}</span>
</app-callout>
<app-callout type="info" *ngIf="disableHideEmail && !disableSend">
{{ "sendOptionsPolicyInEffect" | i18n }}
</app-callout>
</div>
<div class="box">
<div class="box-header">
{{ title }}
</div>
</div>
<div class="footer">
<button appBlurClick type="submit" class="primary btn-submit" appA11yTitle="{{'save' | i18n}}" [disabled]="form.loading" *ngIf="!disableSend">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span><i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i></span>
</button>
<button appBlurClick type="button" (click)="cancel()" [disabled]="form.loading">
{{'cancel' | i18n}}
</button>
<div class="right">
<button appBlurClick type="button" (click)="copyLinkToClipboard(link)" appA11yTitle="{{'copySendLinkToClipboard' | i18n}}" *ngIf="editMode">
<i class="fa fa-copy fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" aria-hidden="true"></i>
</button>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
type="text"
name="Name"
[(ngModel)]="send.name"
appAutofocus
[readOnly]="disableSend"
/>
</div>
<div class="box-content-row box-content-row-radio" *ngIf="!editMode">
<label class="radio-header">{{ "whatTypeOfSend" | i18n }}</label>
<div class="item" *ngFor="let o of typeOptions">
<input
type="radio"
class="radio"
[(ngModel)]="send.type"
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="typeChanged(o)"
[checked]="send.type === o.value"
[disabled]="disableSend"
/>
<label class="unstyled" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<div class="box-content-row" appBowRow *ngIf="!editMode && send.type === sendType.File">
<label for="file">{{ "file" | i18n }}</label>
<input
type="file"
id="file"
class="form-control-file"
name="file"
required
[disabled]="disableSend"
/>
</div>
<div class="box-content-row" appBowRow *ngIf="editMode && send.type === sendType.File">
<label for="file">{{ "file" | i18n }}</label>
<div class="row-main">{{ send.file.fileName }} ({{ send.file.sizeName }})</div>
</div>
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text">
<label for="text">{{ "text" | i18n }}</label>
<textarea
id="text"
name="text"
[(ngModel)]="send.text.text"
rows="6"
[readOnly]="disableSend"
></textarea>
</div>
</div>
<div class="box-footer" *ngIf="!editMode && send.type === sendType.File">
{{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}
</div>
<div class="box-footer" *ngIf="send.type === sendType.Text">
{{ "sendTextDesc" | i18n }}
</div>
</div>
<div class="box" *ngIf="send.type === sendType.Text">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideText">{{ "textHiddenByDefault" | i18n }}</label>
<input
id="hideText"
name="hideText"
type="checkbox"
[(ngModel)]="send.text.hidden"
[disabled]="disableSend"
/>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{ "options" | i18n }}
<a
class="toggle"
href="#"
appStopClick
appBlurClick
role="button"
(click)="toggleOptions()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-chevron-down': !showOptions, 'fa-chevron-up': showOptions }"
></i>
</a>
</div>
</div>
<div [hidden]="!showOptions">
<app-send-efflux-dates
[initialDeletionDate]="send.deletionDate"
[initialExpirationDate]="send.expirationDate"
[editMode]="editMode"
[disabled]="disableSend"
(datesChanged)="setDates($event)"
>
</app-send-efflux-dates>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="maxAccessCount">{{ "maxAccessCount" | i18n }}</label>
<input
id="maxAccessCount"
type="number"
name="maxAccessCount"
[(ngModel)]="send.maxAccessCount"
[readOnly]="disableSend"
/>
</div>
</div>
<div class="box-footer" *ngIf="!editMode">
{{ "maxAccessCountDesc" | i18n }}
</div>
<div class="box-footer" *ngIf="editMode">
<p>{{ "maxAccessCountDesc" | i18n }}</p>
{{ "currentAccessCount" | i18n }}: <strong>{{ send.accessCount }}</strong>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="password">{{
(hasPassword ? "newPassword" : "password") | i18n
}}</label>
<input
id="password"
name="password"
type="{{ showPassword ? 'text' : 'password' }}"
[(ngModel)]="password"
[readOnly]="disableSend"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePasswordVisible()"
[disabled]="disableSend"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{ "sendPasswordDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-header">
{{ "notes" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea
id="notes"
name="notes"
[(ngModel)]="send.notes"
rows="6"
[readOnly]="disableSend"
></textarea>
</div>
</div>
<div class="box-footer">
{{ "sendNotesDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideEmail">{{ "hideEmail" | i18n }}</label>
<input
id="hideEmail"
type="checkbox"
name="HideEmail"
[(ngModel)]="send.hideEmail"
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend"
/>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="disabled">{{ "disableSend" | i18n }}</label>
<input
id="disabled"
type="checkbox"
name="disabled"
[(ngModel)]="send.disabled"
[disabled]="disableSend"
/>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{ "share" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow *ngIf="editMode">
<label for="link">{{ "sendLinkLabel" | i18n }}</label>
<input id="link" name="link" [ngModel]="link" readonly />
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="copyLink">{{ "copySendLinkOnSave" | i18n }}</label>
<input
id="copyLink"
name="copyLink"
[(ngModel)]="copyLink"
type="checkbox"
[disabled]="disableSend"
/>
</div>
</div>
</div>
</div>
</div>
<div class="footer">
<button
appBlurClick
type="submit"
class="primary btn-submit"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
*ngIf="!disableSend"
>
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span><i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i></span>
</button>
<button appBlurClick type="button" (click)="cancel()" [disabled]="form.loading">
{{ "cancel" | i18n }}
</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="copyLinkToClipboard(link)"
appA11yTitle="{{ 'copySendLinkToClipboard' | i18n }}"
*ngIf="editMode"
>
<i class="fa fa-copy fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button
#deleteBtn
appBlurClick
type="button"
(click)="delete()"
class="danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
>
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!deleteBtn.loading"
aria-hidden="true"
></i>
</button>
</div>
</div>
</form>

View File

@ -1,47 +1,64 @@
import { DatePipe } from '@angular/common';
import { DatePipe } from "@angular/common";
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SendService } from "jslib-common/abstractions/send.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/send/add-edit.component';
import { AddEditComponent as BaseAddEditComponent } from "jslib-angular/components/send/add-edit.component";
@Component({
selector: 'app-send-add-edit',
templateUrl: 'add-edit.component.html',
selector: "app-send-add-edit",
templateUrl: "add-edit.component.html",
})
export class AddEditComponent extends BaseAddEditComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService, datePipe: DatePipe,
sendService: SendService, stateService: StateService,
messagingService: MessagingService, policyService: PolicyService,
logService: LogService) {
super(i18nService, platformUtilsService, environmentService,
datePipe, sendService, messagingService, policyService,
logService, stateService);
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
datePipe: DatePipe,
sendService: SendService,
stateService: StateService,
messagingService: MessagingService,
policyService: PolicyService,
logService: LogService
) {
super(
i18nService,
platformUtilsService,
environmentService,
datePipe,
sendService,
messagingService,
policyService,
logService,
stateService
);
}
async refresh() {
this.password = null;
const send = await this.loadSend();
this.send = await send.decrypt();
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
}
async refresh() {
this.password = null;
const send = await this.loadSend();
this.send = await send.decrypt();
this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
}
cancel() {
this.onCancelled.emit(this.send);
}
cancel() {
this.onCancelled.emit(this.send);
}
async copyLinkToClipboard(link: string) {
super.copyLinkToClipboard(link);
this.platformUtilsService.showToast('success', null,
this.i18nService.t('valueCopied', this.i18nService.t('sendLink')));
}
async copyLinkToClipboard(link: string) {
super.copyLinkToClipboard(link);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("valueCopied", this.i18nService.t("sendLink"))
);
}
}

View File

@ -1,35 +1,55 @@
<ng-container [formGroup]="datesForm">
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow *ngIf="!editMode">
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
<select id="deletionDate" name="DeletionDateSelect" formControlName="selectedDeletionDatePreset" required>
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{o.name}}
</option>
</select>
<small class="help-block">{{'deletionDateDesc' | i18n}}</small>
</div>
<div class="box-content-row" *ngIf="selectedDeletionDatePreset.value === 0 || editMode">
<label *ngIf="editMode" for="deletionDateCustom">{{'deletionDate' | i18n}}</label>
<input id="deletionDateCustom" type="datetime-local" name="deletionDate"
formControlName="defaultDeletionDateTime" required placeholder="MM/DD/YYYY HH:MM AM/PM">
<small class="help-block" *ngIf="editMode">{{'deletionDateDesc' | i18n}}</small>
</div>
<div class="box-content-row" appBoxRow *ngIf="!editMode">
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
<select id="expirationDate" name="expirationDateSelect" formControlName="selectedExpirationDatePreset" required>
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{o.name}}
</option>
</select>
<small class="help-block">{{'expirationDateDesc' | i18n}}</small>
</div>
<div class="box-content-row" *ngIf="selectedExpirationDatePreset.value === 0 || editMode">
<label *ngIf="editMode" for="expirationDateCustom">{{'expirationDate' | i18n}}</label>
<input id="expirationDateCustom" type="datetime-local" name="expirationDate"
formControlName="defaultExpirationDateTime" required placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
<small *ngIf="editMode" class="help-block">{{'expirationDateDesc' | i18n}}</small>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow *ngIf="!editMode">
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
<select
id="deletionDate"
name="DeletionDateSelect"
formControlName="selectedDeletionDatePreset"
required
>
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "deletionDateDesc" | i18n }}</small>
</div>
<div class="box-content-row" *ngIf="selectedDeletionDatePreset.value === 0 || editMode">
<label *ngIf="editMode" for="deletionDateCustom">{{ "deletionDate" | i18n }}</label>
<input
id="deletionDateCustom"
type="datetime-local"
name="deletionDate"
formControlName="defaultDeletionDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
/>
<small class="help-block" *ngIf="editMode">{{ "deletionDateDesc" | i18n }}</small>
</div>
<div class="box-content-row" appBoxRow *ngIf="!editMode">
<label for="expirationDate">{{ "expirationDate" | i18n }}</label>
<select
id="expirationDate"
name="expirationDateSelect"
formControlName="selectedExpirationDatePreset"
required
>
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "expirationDateDesc" | i18n }}</small>
</div>
<div class="box-content-row" *ngIf="selectedExpirationDatePreset.value === 0 || editMode">
<label *ngIf="editMode" for="expirationDateCustom">{{ "expirationDate" | i18n }}</label>
<input
id="expirationDateCustom"
type="datetime-local"
name="expirationDate"
formControlName="defaultExpirationDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
[readOnly]="disableSend"
/>
<small *ngIf="editMode" class="help-block">{{ "expirationDateDesc" | i18n }}</small>
</div>
</div>
</div>
</ng-container>

View File

@ -1,38 +1,41 @@
import { DatePipe } from '@angular/common';
import { DatePipe } from "@angular/common";
import {
Component,
OnChanges,
} from '@angular/core';
import { Component, OnChanges } from "@angular/core";
import { ControlContainer, NgForm } from '@angular/forms';
import { ControlContainer, NgForm } from "@angular/forms";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EffluxDatesComponent as BaseEffluxDatesComponent } from 'jslib-angular/components/send/efflux-dates.component';
import { EffluxDatesComponent as BaseEffluxDatesComponent } from "jslib-angular/components/send/efflux-dates.component";
@Component({
selector: 'app-send-efflux-dates',
templateUrl: 'efflux-dates.component.html',
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
selector: "app-send-efflux-dates",
templateUrl: "efflux-dates.component.html",
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
})
export class EffluxDatesComponent extends BaseEffluxDatesComponent implements OnChanges {
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected datePipe: DatePipe) {
super(i18nService, platformUtilsService, datePipe);
}
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected datePipe: DatePipe
) {
super(i18nService, platformUtilsService, datePipe);
}
// We reuse the same form on desktop and just swap content, so need to watch these to maintin proper values.
ngOnChanges() {
this.selectedExpirationDatePreset.setValue(0);
this.selectedDeletionDatePreset.setValue(0);
this.defaultDeletionDateTime.setValue(this.datePipe.transform(new Date(this.initialDeletionDate), 'yyyy-MM-ddTHH:mm'));
if (this.initialExpirationDate) {
this.defaultExpirationDateTime.setValue(
this.datePipe.transform(new Date(this.initialExpirationDate), 'yyyy-MM-ddTHH:mm'));
} else {
this.defaultExpirationDateTime.setValue(null);
}
// We reuse the same form on desktop and just swap content, so need to watch these to maintin proper values.
ngOnChanges() {
this.selectedExpirationDatePreset.setValue(0);
this.selectedDeletionDatePreset.setValue(0);
this.defaultDeletionDateTime.setValue(
this.datePipe.transform(new Date(this.initialDeletionDate), "yyyy-MM-ddTHH:mm")
);
if (this.initialExpirationDate) {
this.defaultExpirationDateTime.setValue(
this.datePipe.transform(new Date(this.initialExpirationDate), "yyyy-MM-ddTHH:mm")
);
} else {
this.defaultExpirationDateTime.setValue(null);
}
}
}

View File

@ -1,97 +1,141 @@
<div id="sends" class="vault">
<div class="groupings">
<div class="mac-bar"></div>
<div class="content">
<div class="inner-content">
<h2 class="sr-only">{{'filters' | i18n}}</h2>
<ul>
<li [ngClass]="{active: selectedAll}">
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{'allSends' | i18n}}
</a>
</li>
</ul>
<h2>{{'types' | i18n}}</h2>
<ul>
<li [ngClass]="{active: selectedType === sendType.Text}">
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.Text)">
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>&nbsp;{{'sendTypeText' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedType === sendType.File}">
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.File)">
<i class="fa fa-fw fa-file-o" aria-hidden="true"></i>&nbsp;{{'sendTypeFile' | i18n}}
</a>
</li>
</ul>
</div>
<div class="footer">
<app-nav class="nav"></app-nav>
</div>
</div>
<div class="groupings">
<div class="mac-bar"></div>
<div class="content">
<div class="inner-content">
<h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedAll }">
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{ "allSends" | i18n }}
</a>
</li>
</ul>
<h2>{{ "types" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedType === sendType.Text }">
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.Text)">
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>&nbsp;{{
"sendTypeText" | i18n
}}
</a>
</li>
<li [ngClass]="{ active: selectedType === sendType.File }">
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.File)">
<i class="fa fa-fw fa-file-o" aria-hidden="true"></i>&nbsp;{{ "sendTypeFile" | i18n }}
</a>
</li>
</ul>
</div>
<div class="footer">
<app-nav class="nav"></app-nav>
</div>
</div>
<div id="items" class="items">
<div class="content">
<div class="list" *ngIf="filteredSends.length">
<a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s.id)"
title="{{'viewItem' | i18n}}" (contextmenu)="viewSendMenu(s)"
[ngClass]="{'active': s.id === sendId}" class="flex-list-item">
<div class="item-icon" aria-hidden="true">
<i class="fa fa-fw fa-lg" [ngClass]="s.type == 0 ? 'fa-file-text-o' : 'fa-file-o'"></i>
</div>
<div class="item-content">
<div class="item-title">
{{s.name}}
<span class="title-badges">
<ng-container *ngIf="s.disabled">
<i class="fa fa-warning" appStopProp title="{{'disabled' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'disabled' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.password">
<i class="fa fa-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="fa fa-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="fa fa-clock-o" appStopProp title="{{'expired' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'expired' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
<i class="fa fa-trash" appStopProp title="{{'pendingDeletion' | i18n}}"
aria-hidden="true"></i>
<span class="sr-only">{{'pendingDeletion' | i18n}}</span>
</ng-container>
</span>
</div>
<span class="item-details">{{s.deletionDate | date}}</span>
</div>
</a>
</div>
<div class="no-items" *ngIf="!filteredSends.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
<p>{{'noItemsInList' | i18n}}</p>
</div>
<div id="items" class="items">
<div class="content">
<div class="list" *ngIf="filteredSends.length">
<a
*ngFor="let s of filteredSends"
appStopClick
(click)="selectSend(s.id)"
title="{{ 'viewItem' | i18n }}"
(contextmenu)="viewSendMenu(s)"
[ngClass]="{ active: s.id === sendId }"
class="flex-list-item"
>
<div class="item-icon" aria-hidden="true">
<i class="fa fa-fw fa-lg" [ngClass]="s.type == 0 ? 'fa-file-text-o' : 'fa-file-o'"></i>
</div>
<div class="item-content">
<div class="item-title">
{{ s.name }}
<span class="title-badges">
<ng-container *ngIf="s.disabled">
<i
class="fa fa-warning"
appStopProp
title="{{ 'disabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "disabled" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.password">
<i
class="fa fa-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="fa fa-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="fa fa-clock-o"
appStopProp
title="{{ 'expired' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "expired" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
<i
class="fa fa-trash"
appStopProp
title="{{ 'pendingDeletion' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
</ng-container>
</span>
</div>
</div>
<div class="footer">
<button appBlurClick (click)="addSend()" class="block primary" appA11yTitle="{{'addItem' | i18n}}">
<i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
</div>
<span class="item-details">{{ s.deletionDate | date }}</span>
</div>
</a>
</div>
<div class="no-items" *ngIf="!filteredSends.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
<p>{{ "noItemsInList" | i18n }}</p>
</ng-container>
</div>
</div>
<app-send-add-edit id="addEdit" class="details" *ngIf="action == 'add' || action == 'edit'" [sendId]="sendId" [type]="selectedSendType"
(onSavedSend)="savedSend($event)" (onCancelled)="cancel($event)" (onDeletedSend)="deletedSend($event)"></app-send-add-edit>
<div class="logo" *ngIf="!action">
<div class="content">
<div class="inner-content">
<img class="logo-image" alt="Bitwarden" aria-hidden="true" />
</div>
</div>
<div class="footer">
<button
appBlurClick
(click)="addSend()"
class="block primary"
appA11yTitle="{{ 'addItem' | i18n }}"
>
<i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
</div>
</div>
<app-send-add-edit
id="addEdit"
class="details"
*ngIf="action == 'add' || action == 'edit'"
[sendId]="sendId"
[type]="selectedSendType"
(onSavedSend)="savedSend($event)"
(onCancelled)="cancel($event)"
(onDeletedSend)="deletedSend($event)"
></app-send-add-edit>
<div class="logo" *ngIf="!action">
<div class="content">
<div class="inner-content">
<img class="logo-image" alt="Bitwarden" aria-hidden="true" />
</div>
</div>
</div>
</div>

View File

@ -1,138 +1,146 @@
import {
Component,
NgZone,
OnDestroy,
OnInit,
ViewChild,
} from '@angular/core';
import { Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { SendService } from "jslib-common/abstractions/send.service";
import { SendComponent as BaseSendComponent } from 'jslib-angular/components/send/send.component';
import { SendComponent as BaseSendComponent } from "jslib-angular/components/send/send.component";
import { invokeMenu, RendererMenuItem } from 'jslib-electron/utils';
import { invokeMenu, RendererMenuItem } from "jslib-electron/utils";
import { SendView } from 'jslib-common/models/view/sendView';
import { SendView } from "jslib-common/models/view/sendView";
import { SearchBarService } from '../layout/search/search-bar.service';
import { AddEditComponent } from './add-edit.component';
import { SearchBarService } from "../layout/search/search-bar.service";
import { AddEditComponent } from "./add-edit.component";
enum Action {
None = '',
Add = 'add',
Edit = 'edit',
None = "",
Add = "add",
Edit = "edit",
}
const BroadcasterSubscriptionId = 'SendComponent';
const BroadcasterSubscriptionId = "SendComponent";
@Component({
selector: 'app-send',
templateUrl: 'send.component.html',
selector: "app-send",
templateUrl: "send.component.html",
})
export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy {
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
sendId: string;
action: Action = Action.None;
sendId: string;
action: Action = Action.None;
constructor(sendService: SendService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
private broadcasterService: BroadcasterService, ngZone: NgZone,
searchService: SearchService, policyService: PolicyService,
private searchBarService: SearchBarService, logService: LogService) {
super(sendService, i18nService, platformUtilsService,
environmentService, ngZone, searchService,
policyService, logService);
this.searchBarService.searchText.subscribe(searchText => {
this.searchText = searchText;
this.searchTextChanged();
});
}
constructor(
sendService: SendService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
searchService: SearchService,
policyService: PolicyService,
private searchBarService: SearchBarService,
logService: LogService
) {
super(
sendService,
i18nService,
platformUtilsService,
environmentService,
ngZone,
searchService,
policyService,
logService
);
this.searchBarService.searchText.subscribe((searchText) => {
this.searchText = searchText;
this.searchTextChanged();
});
}
async ngOnInit() {
this.searchBarService.setEnabled(true);
this.searchBarService.setPlaceholderText(this.i18nService.t('searchSends'));
async ngOnInit() {
this.searchBarService.setEnabled(true);
this.searchBarService.setPlaceholderText(this.i18nService.t("searchSends"));
super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'syncCompleted':
await this.load();
break;
}
});
});
await this.load();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.searchBarService.setEnabled(false);
}
addSend() {
this.action = Action.Add;
if (this.addEditComponent != null) {
this.addEditComponent.sendId = null;
this.addEditComponent.send = null;
this.addEditComponent.load();
super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "syncCompleted":
await this.load();
break;
}
}
});
});
await this.load();
}
cancel(s: SendView) {
this.action = Action.None;
this.sendId = null;
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.searchBarService.setEnabled(false);
}
async deletedSend(s: SendView) {
await this.refresh();
this.action = Action.None;
this.sendId = null;
addSend() {
this.action = Action.Add;
if (this.addEditComponent != null) {
this.addEditComponent.sendId = null;
this.addEditComponent.send = null;
this.addEditComponent.load();
}
}
async savedSend(s: SendView) {
await this.refresh();
this.selectSend(s.id);
cancel(s: SendView) {
this.action = Action.None;
this.sendId = null;
}
async deletedSend(s: SendView) {
await this.refresh();
this.action = Action.None;
this.sendId = null;
}
async savedSend(s: SendView) {
await this.refresh();
this.selectSend(s.id);
}
async selectSend(sendId: string) {
if (sendId === this.sendId && this.action === Action.Edit) {
return;
}
async selectSend(sendId: string) {
if (sendId === this.sendId && this.action === Action.Edit) {
return;
}
this.action = Action.Edit;
this.sendId = sendId;
if (this.addEditComponent != null) {
this.addEditComponent.sendId = sendId;
await this.addEditComponent.refresh();
}
this.action = Action.Edit;
this.sendId = sendId;
if (this.addEditComponent != null) {
this.addEditComponent.sendId = sendId;
await this.addEditComponent.refresh();
}
}
get selectedSendType() {
return this.sends.find(s => s.id === this.sendId)?.type;
}
get selectedSendType() {
return this.sends.find((s) => s.id === this.sendId)?.type;
}
viewSendMenu(send: SendView) {
const menu: RendererMenuItem[] = [];
menu.push({
label: this.i18nService.t('copyLink'),
click: () => this.copy(send),
});
menu.push({
label: this.i18nService.t('delete'),
click: async () => {
await this.delete(send);
await this.deletedSend(send);
},
});
viewSendMenu(send: SendView) {
const menu: RendererMenuItem[] = [];
menu.push({
label: this.i18nService.t("copyLink"),
click: () => this.copy(send),
});
menu.push({
label: this.i18nService.t("delete"),
click: async () => {
await this.delete(send);
await this.deletedSend(send);
},
});
invokeMenu(menu);
}
invokeMenu(menu);
}
}

View File

@ -1,179 +1,173 @@
import {
APP_INITIALIZER,
NgModule,
} from '@angular/core';
import { APP_INITIALIZER, NgModule } from "@angular/core";
import { ElectronLogService } from 'jslib-electron/services/electronLog.service';
import { ElectronPlatformUtilsService } from 'jslib-electron/services/electronPlatformUtils.service';
import { ElectronRendererMessagingService } from 'jslib-electron/services/electronRendererMessaging.service';
import { ElectronRendererSecureStorageService } from 'jslib-electron/services/electronRendererSecureStorage.service';
import { ElectronRendererStorageService } from 'jslib-electron/services/electronRendererStorage.service';
import { ElectronLogService } from "jslib-electron/services/electronLog.service";
import { ElectronPlatformUtilsService } from "jslib-electron/services/electronPlatformUtils.service";
import { ElectronRendererMessagingService } from "jslib-electron/services/electronRendererMessaging.service";
import { ElectronRendererSecureStorageService } from "jslib-electron/services/electronRendererSecureStorage.service";
import { ElectronRendererStorageService } from "jslib-electron/services/electronRendererStorage.service";
import { I18nService } from '../services/i18n.service';
import { LoginGuardService } from '../services/loginGuard.service';
import { NativeMessagingService } from '../services/nativeMessaging.service';
import { PasswordRepromptService } from '../services/passwordReprompt.service';
import { SearchBarService } from './layout/search/search-bar.service';
import { I18nService } from "../services/i18n.service";
import { LoginGuardService } from "../services/loginGuard.service";
import { NativeMessagingService } from "../services/nativeMessaging.service";
import { PasswordRepromptService } from "../services/passwordReprompt.service";
import { SearchBarService } from "./layout/search/search-bar.service";
import { JslibServicesModule } from 'jslib-angular/services/jslib-services.module';
import { JslibServicesModule } from "jslib-angular/services/jslib-services.module";
import { AuthService } from 'jslib-common/services/auth.service';
import { ContainerService } from 'jslib-common/services/container.service';
import { EventService } from 'jslib-common/services/event.service';
import { SystemService } from 'jslib-common/services/system.service';
import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service';
import { AuthService } from "jslib-common/services/auth.service";
import { ContainerService } from "jslib-common/services/container.service";
import { EventService } from "jslib-common/services/event.service";
import { SystemService } from "jslib-common/services/system.service";
import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
import { ElectronCryptoService } from 'jslib-electron/services/electronCrypto.service';
import { ElectronCryptoService } from "jslib-electron/services/electronCrypto.service";
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService as BroadcasterServiceAbstraction } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service';
import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service';
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service';
import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service';
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service';
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service';
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service';
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service';
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service';
import { SystemService as SystemServiceAbstraction } from 'jslib-common/abstractions/system.service';
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service';
import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
import { SystemService as SystemServiceAbstraction } from "jslib-common/abstractions/system.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
import { ThemeType } from 'jslib-common/enums/themeType';
import { ThemeType } from "jslib-common/enums/themeType";
export function initFactory(window: Window, environmentService: EnvironmentServiceAbstraction,
syncService: SyncServiceAbstraction, vaultTimeoutService: VaultTimeoutService,
i18nService: I18nService, eventService: EventService,
authService: AuthService, notificationsService: NotificationsServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction, stateService: StateServiceAbstraction,
cryptoService: CryptoServiceAbstraction): Function {
export function initFactory(
window: Window,
environmentService: EnvironmentServiceAbstraction,
syncService: SyncServiceAbstraction,
vaultTimeoutService: VaultTimeoutService,
i18nService: I18nService,
eventService: EventService,
authService: AuthService,
notificationsService: NotificationsServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
stateService: StateServiceAbstraction,
cryptoService: CryptoServiceAbstraction
): Function {
return async () => {
await stateService.init();
await environmentService.setUrlsFromStorage();
syncService.fullSync(true);
await vaultTimeoutService.init(true);
const locale = await stateService.getLocale();
await i18nService.init(locale);
eventService.init(true);
authService.init();
setTimeout(() => notificationsService.init(), 3000);
const htmlEl = window.document.documentElement;
htmlEl.classList.add("os_" + platformUtilsService.getDeviceString());
htmlEl.classList.add("locale_" + i18nService.translationLocale);
const theme = await platformUtilsService.getEffectiveTheme();
htmlEl.classList.add("theme_" + theme);
platformUtilsService.onDefaultSystemThemeChange(async (sysTheme) => {
const bwTheme = await stateService.getTheme();
if (bwTheme == null || bwTheme === ThemeType.System) {
htmlEl.classList.remove("theme_" + ThemeType.Light, "theme_" + ThemeType.Dark);
htmlEl.classList.add("theme_" + sysTheme);
}
});
return async () => {
await stateService.init();
await environmentService.setUrlsFromStorage();
syncService.fullSync(true);
await vaultTimeoutService.init(true);
const locale = await stateService.getLocale();
await i18nService.init(locale);
eventService.init(true);
authService.init();
setTimeout(() => notificationsService.init(), 3000);
const htmlEl = window.document.documentElement;
htmlEl.classList.add('os_' + platformUtilsService.getDeviceString());
htmlEl.classList.add('locale_' + i18nService.translationLocale);
const theme = await platformUtilsService.getEffectiveTheme();
htmlEl.classList.add('theme_' + theme);
platformUtilsService.onDefaultSystemThemeChange(async sysTheme => {
const bwTheme = await stateService.getTheme();
if (bwTheme == null || bwTheme === ThemeType.System) {
htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark);
htmlEl.classList.add('theme_' + sysTheme);
}
});
let installAction = null;
const installedVersion = await stateService.getInstalledVersion();
const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = "install";
} else if (installedVersion !== currentVersion) {
installAction = "update";
}
let installAction = null;
const installedVersion = await stateService.getInstalledVersion();
const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = 'install';
} else if (installedVersion !== currentVersion) {
installAction = 'update';
}
if (installAction != null) {
await stateService.setInstalledVersion(currentVersion);
}
if (installAction != null) {
await stateService.setInstalledVersion(currentVersion);
}
const containerService = new ContainerService(cryptoService);
containerService.attachToGlobal(window);
};
const containerService = new ContainerService(cryptoService);
containerService.attachToGlobal(window);
};
}
@NgModule({
imports: [
JslibServicesModule,
],
declarations: [],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initFactory,
deps: [
'WINDOW',
EnvironmentServiceAbstraction,
SyncServiceAbstraction,
VaultTimeoutServiceAbstraction,
I18nServiceAbstraction,
EventServiceAbstraction,
AuthServiceAbstraction,
NotificationsServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
CryptoServiceAbstraction,
],
multi: true,
},
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
{
provide: PlatformUtilsServiceAbstraction,
useFactory: (i18nService: I18nServiceAbstraction, messagingService: MessagingServiceAbstraction,
stateService: StateServiceAbstraction) => new ElectronPlatformUtilsService(i18nService,
messagingService, true, stateService),
deps: [
I18nServiceAbstraction,
MessagingServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: I18nServiceAbstraction,
useFactory: (window: Window) => new I18nService(window.navigator.language, './locales'),
deps: [ 'WINDOW' ],
},
{
provide: MessagingServiceAbstraction,
useClass: ElectronRendererMessagingService,
deps: [ BroadcasterServiceAbstraction ],
},
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
{ provide: 'SECURE_STORAGE', useClass: ElectronRendererSecureStorageService },
{
provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: SystemServiceAbstraction,
useClass: SystemService,
deps: [
VaultTimeoutServiceAbstraction,
MessagingServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
NativeMessagingService,
SearchBarService,
{
provide: LoginGuardService,
useClass: LoginGuardService,
deps: [
StateServiceAbstraction,
PlatformUtilsServiceAbstraction,
I18nServiceAbstraction,
],
},
],
imports: [JslibServicesModule],
declarations: [],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initFactory,
deps: [
"WINDOW",
EnvironmentServiceAbstraction,
SyncServiceAbstraction,
VaultTimeoutServiceAbstraction,
I18nServiceAbstraction,
EventServiceAbstraction,
AuthServiceAbstraction,
NotificationsServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
CryptoServiceAbstraction,
],
multi: true,
},
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
{
provide: PlatformUtilsServiceAbstraction,
useFactory: (
i18nService: I18nServiceAbstraction,
messagingService: MessagingServiceAbstraction,
stateService: StateServiceAbstraction
) => new ElectronPlatformUtilsService(i18nService, messagingService, true, stateService),
deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StateServiceAbstraction],
},
{
provide: I18nServiceAbstraction,
useFactory: (window: Window) => new I18nService(window.navigator.language, "./locales"),
deps: ["WINDOW"],
},
{
provide: MessagingServiceAbstraction,
useClass: ElectronRendererMessagingService,
deps: [BroadcasterServiceAbstraction],
},
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
{ provide: "SECURE_STORAGE", useClass: ElectronRendererSecureStorageService },
{
provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: SystemServiceAbstraction,
useClass: SystemService,
deps: [
VaultTimeoutServiceAbstraction,
MessagingServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
NativeMessagingService,
SearchBarService,
{
provide: LoginGuardService,
useClass: LoginGuardService,
deps: [StateServiceAbstraction, PlatformUtilsServiceAbstraction, I18nServiceAbstraction],
},
],
})
export class ServicesModule {
}
export class ServicesModule {}

View File

@ -1,64 +1,120 @@
<div class="box">
<div class="box-header">
{{'customFields' | i18n}}
</div>
<div class="box-content">
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
<div class="box-content-row box-content-row-multi box-draggable-row" cdkDrag
*ngFor="let f of cipher.fields; let i = index; trackBy:trackByFunction"
[ngClass]="{'box-content-row-checkbox': f.type === fieldType.Boolean}">
<a href="#" appStopClick (click)="removeField(f)" appA11yTitle="{{'remove' | i18n}}"
role="button">
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
</a>
<label for="fieldName{{i}}" class="sr-only">{{'name' | i18n}}</label>
<label for="fieldValue{{i}}" class="sr-only">{{'value' | i18n}}</label>
<div class="row-main">
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name"
class="row-label" placeholder="{{'name' | i18n}}" appInputVerbatim>
<!-- Text -->
<input id="fieldValue{{i}}" type="text" name="Field.Value{{i}}" [(ngModel)]="f.value"
*ngIf="f.type === fieldType.Text" placeholder="{{'value' | i18n}}" appInputVerbatim>
<!-- Password -->
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}"
name="Field.Value{{i}}" [(ngModel)]="f.value" class="monospaced"
*ngIf="f.type === fieldType.Hidden" placeholder="{{'value' | i18n}}"
[disabled]="!cipher.viewPassword && !f.newField" appInputVerbatim>
<!-- Linked -->
<select id="fieldValue{{i}}" name="Field.Value{{i}}" [(ngModel)]="f.linkedId"
*ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null">
<option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<!-- Boolean -->
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox" [(ngModel)]="f.value"
*ngIf="f.type === fieldType.Boolean" appTrueFalseValue trueValue="true"
falseValue="false">
<div class="action-buttons"
*ngIf="f.type === fieldType.Hidden && (cipher.viewPassword || f.newField)">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue}"></i>
</a>
</div>
<div class="drag-handle" appA11yTitle="{{'dragToSort' | i18n}}" cdkDragHandle>
<i class="fa fa-bars" aria-hidden="true"></i>
</div>
</div>
<div class="box-header">
{{ "customFields" | i18n }}
</div>
<div class="box-content">
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
<div
class="box-content-row box-content-row-multi box-draggable-row"
cdkDrag
*ngFor="let f of cipher.fields; let i = index; trackBy: trackByFunction"
[ngClass]="{ 'box-content-row-checkbox': f.type === fieldType.Boolean }"
>
<a
href="#"
appStopClick
(click)="removeField(f)"
appA11yTitle="{{ 'remove' | i18n }}"
role="button"
>
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
</a>
<label for="fieldName{{ i }}" class="sr-only">{{ "name" | i18n }}</label>
<label for="fieldValue{{ i }}" class="sr-only">{{ "value" | i18n }}</label>
<div class="row-main">
<input
id="fieldName{{ i }}"
type="text"
name="Field.Name{{ i }}"
[(ngModel)]="f.name"
class="row-label"
placeholder="{{ 'name' | i18n }}"
appInputVerbatim
/>
<!-- Text -->
<input
id="fieldValue{{ i }}"
type="text"
name="Field.Value{{ i }}"
[(ngModel)]="f.value"
*ngIf="f.type === fieldType.Text"
placeholder="{{ 'value' | i18n }}"
appInputVerbatim
/>
<!-- Password -->
<input
id="fieldValue{{ i }}"
type="{{ f.showValue ? 'text' : 'password' }}"
name="Field.Value{{ i }}"
[(ngModel)]="f.value"
class="monospaced"
*ngIf="f.type === fieldType.Hidden"
placeholder="{{ 'value' | i18n }}"
[disabled]="!cipher.viewPassword && !f.newField"
appInputVerbatim
/>
<!-- Linked -->
<select
id="fieldValue{{ i }}"
name="Field.Value{{ i }}"
[(ngModel)]="f.linkedId"
*ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null"
>
<option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
<!-- Add new custom field -->
<div class="box-content-row" appBoxRow>
<a href="#" appStopClick (click)="addField()" role="button">
<i class="fa fa-plus-circle fa-fw fa-lg" aria-hidden="true"></i> {{'newCustomField' | i18n}}
</a>
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label>
<select id="addFieldType" name="AddFieldType" [(ngModel)]="addFieldType" class="field-type">
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{o.name}}</option>
<option *ngIf="cipher.linkedFieldOptions != null" [ngValue]="addFieldLinkedTypeOption.value">
{{addFieldLinkedTypeOption.name}}
</option>
</select>
<!-- Boolean -->
<input
id="fieldValue{{ i }}"
name="Field.Value{{ i }}"
type="checkbox"
[(ngModel)]="f.value"
*ngIf="f.type === fieldType.Boolean"
appTrueFalseValue
trueValue="true"
falseValue="false"
/>
<div
class="action-buttons"
*ngIf="f.type === fieldType.Hidden && (cipher.viewPassword || f.newField)"
>
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleFieldValue(f)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue }"
></i>
</a>
</div>
<div class="drag-handle" appA11yTitle="{{ 'dragToSort' | i18n }}" cdkDragHandle>
<i class="fa fa-bars" aria-hidden="true"></i>
</div>
</div>
</div>
<!-- Add new custom field -->
<div class="box-content-row" appBoxRow>
<a href="#" appStopClick (click)="addField()" role="button">
<i class="fa fa-plus-circle fa-fw fa-lg" aria-hidden="true"></i>
{{ "newCustomField" | i18n }}
</a>
<label for="addFieldType" class="sr-only">{{ "type" | i18n }}</label>
<select id="addFieldType" name="AddFieldType" [(ngModel)]="addFieldType" class="field-type">
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{ o.name }}</option>
<option
*ngIf="cipher.linkedFieldOptions != null"
[ngValue]="addFieldLinkedTypeOption.value"
>
{{ addFieldLinkedTypeOption.name }}
</option>
</select>
</div>
</div>
</div>

View File

@ -1,18 +1,16 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import {
AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent
} from 'jslib-angular/components/add-edit-custom-fields.component';
import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "jslib-angular/components/add-edit-custom-fields.component";
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@Component({
selector: 'app-vault-add-edit-custom-fields',
templateUrl: 'add-edit-custom-fields.component.html',
selector: "app-vault-add-edit-custom-fields",
templateUrl: "add-edit-custom-fields.component.html",
})
export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent {
constructor(i18nService: I18nService, eventService: EventService) {
super(i18nService, eventService);
}
constructor(i18nService: I18nService, eventService: EventService) {
super(i18nService, eventService);
}
}

View File

@ -1,343 +1,603 @@
<form #form="ngForm" (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<div class="inner-content" *ngIf="cipher">
<div class="box">
<app-callout type="info" *ngIf="allowOwnershipOptions() && !allowPersonal">
{{'personalOwnershipPolicyInEffect' | i18n}}
</app-callout>
<div class="box-header">
{{title}}
</div>
<div class="box-content">
<div class="box-content-row" *ngIf="!editMode" appBoxRow>
<label for="type">{{'type' | i18n}}</label>
<select id="type" name="Type" [(ngModel)]="cipher.type">
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label>
<input id="name" type="text" name="Name" [(ngModel)]="cipher.name" [appAutofocus]="!editMode">
</div>
<!-- Login -->
<div *ngIf="cipher.type === cipherType.Login">
<div class="box-content-row" appBoxRow>
<label for="loginUsername">{{'username' | i18n}}</label>
<input id="loginUsername" type="text" name="Login.Username"
[(ngModel)]="cipher.login.username" appInputVerbatim>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="loginPassword">{{'password' | i18n}}</label>
<input id="loginPassword" class="monospaced"
type="{{showPassword ? 'text' : 'password'}}" name="Login.Password"
[(ngModel)]="cipher.login.password" [disabled]="!cipher.viewPassword"
appInputVerbatim>
</div>
<div class="action-buttons" *ngIf=cipher.viewPassword>
<button type="button" #checkPasswordBtn class="row-btn btn" appBlurClick
appA11yTitle="{{'checkPassword' | i18n}}" (click)="checkPassword()"
[appApiAction]="checkPasswordPromise" [disabled]="checkPasswordBtn.loading">
<i class="fa fa-lg fa-check-circle" [hidden]="checkPasswordBtn.loading"
aria-hidden="true"></i>
<i class="fa fa-lg fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading"
aria-hidden="true"></i>
</button>
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'generatePassword' | i18n}}" (click)="generatePassword()">
<i class="fa fa-lg fa-refresh" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="loginTotp">{{'authenticatorKeyTotp' | i18n}}</label>
<input id="loginTotp" type="{{cipher.viewPassword ? 'text' : 'password'}}" name="Login.Totp"
class="monospaced" [(ngModel)]="cipher.login.totp" [disabled]="!cipher.viewPassword"
appInputVerbatim>
</div>
</div>
<!-- Card -->
<div *ngIf="cipher.type === cipherType.Card">
<div class="box-content-row" appBoxRow>
<label for="cardCardholderName">{{'cardholderName' | i18n}}</label>
<input id="cardCardholderName" type="text" name="Card.CardCardholderName"
[(ngModel)]="cipher.card.cardholderName">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="cardNumber">{{'number' | i18n}}</label>
<input id="cardNumber" class="monospaced" type="{{showCardNumber ? 'text' : 'password'}}"
name="Card.Number" [(ngModel)]="cipher.card.number" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleCardNumber()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber}"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardBrand">{{'brand' | i18n}}</label>
<select id="cardBrand" name="Card.Brand" [(ngModel)]="cipher.card.brand">
<option *ngFor="let o of cardBrandOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardExpMonth">{{'expirationMonth' | i18n}}</label>
<select id="cardExpMonth" name="Card.ExpMonth" [(ngModel)]="cipher.card.expMonth">
<option *ngFor="let o of cardExpMonthOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardExpYear">{{'expirationYear' | i18n}}</label>
<input id="cardExpYear" type="text" name="Card.ExpYear" [(ngModel)]="cipher.card.expYear"
placeholder="{{'ex' | i18n}} {{currentDate | date: 'yyyy'}}">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="cardCode">{{'securityCode' | i18n}}</label>
<input id="cardCode" class="monospaced" type="{{showCardCode ? 'text' : 'password'}}"
name="Card.Code" [(ngModel)]="cipher.card.code" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleCardCode()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showCardCode, 'fa-eye-slash': showCardCode}"></i>
</a>
</div>
</div>
</div>
<!-- Identity -->
<div *ngIf="cipher.type === cipherType.Identity">
<div class="box-content-row" appBoxRow>
<label for="idTitle">{{'title' | i18n}}</label>
<select id="idTitle" name="Identity.Title" [(ngModel)]="cipher.identity.title">
<option *ngFor="let o of identityTitleOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="idFirstName">{{'firstName' | i18n}}</label>
<input id="idFirstName" type="text" name="Identity.FirstName"
[(ngModel)]="cipher.identity.firstName">
</div>
<div class="box-content-row" appBoxRow>
<label for="idMiddleName">{{'middleName' | i18n}}</label>
<input id="idMiddleName" type="text" name="Identity.MiddleName"
[(ngModel)]="cipher.identity.middleName">
</div>
<div class="box-content-row" appBoxRow>
<label for="idLastName">{{'lastName' | i18n}}</label>
<input id="idLastName" type="text" name="Identity.LastName"
[(ngModel)]="cipher.identity.lastName">
</div>
<div class="box-content-row" appBoxRow>
<label for="idUsername">{{'username' | i18n}}</label>
<input id="idUsername" type="text" name="Identity.Username"
[(ngModel)]="cipher.identity.username" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="idCompany">{{'company' | i18n}}</label>
<input id="idCompany" type="text" name="Identity.Company"
[(ngModel)]="cipher.identity.company">
</div>
<div class="box-content-row" appBoxRow>
<label for="idSsn">{{'ssn' | i18n}}</label>
<input id="idSsn" type="text" name="Identity.SSN" [(ngModel)]="cipher.identity.ssn"
appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="idPassportNumber">{{'passportNumber' | i18n}}</label>
<input id="idPassportNumber" type="text" name="Identity.PassportNumber"
[(ngModel)]="cipher.identity.passportNumber" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="idLicenseNumber">{{'licenseNumber' | i18n}}</label>
<input id="idLicenseNumber" type="text" name="Identity.LicenseNumber"
[(ngModel)]="cipher.identity.licenseNumber" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="idEmail">{{'email' | i18n}}</label>
<input id="idEmail" type="text" name="Identity.Email" [(ngModel)]="cipher.identity.email"
appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="idPhone">{{'phone' | i18n}}</label>
<input id="idPhone" type="text" name="Identity.Phone" [(ngModel)]="cipher.identity.phone">
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress1">{{'address1' | i18n}}</label>
<input id="idAddress1" type="text" name="Identity.Address1"
[(ngModel)]="cipher.identity.address1">
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress2">{{'address2' | i18n}}</label>
<input id="idAddress2" type="text" name="Identity.Address2"
[(ngModel)]="cipher.identity.address2">
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress3">{{'address3' | i18n}}</label>
<input id="idAddress3" type="text" name="Identity.Address3"
[(ngModel)]="cipher.identity.address3">
</div>
<div class="box-content-row" appBoxRow>
<label for="idCity">{{'cityTown' | i18n}}</label>
<input id="idCity" type="text" name="Identity.City" [(ngModel)]="cipher.identity.city">
</div>
<div class="box-content-row" appBoxRow>
<label for="idState">{{'stateProvince' | i18n}}</label>
<input id="idState" type="text" name="Identity.State" [(ngModel)]="cipher.identity.state">
</div>
<div class="box-content-row" appBoxRow>
<label for="idPostalCode">{{'zipPostalCode' | i18n}}</label>
<input id="idPostalCode" type="text" name="Identity.PostalCode"
[(ngModel)]="cipher.identity.postalCode">
</div>
<div class="box-content-row" appBoxRow>
<label for="idCountry">{{'country' | i18n}}</label>
<input id="idCountry" type="text" name="Identity.Country"
[(ngModel)]="cipher.identity.country">
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.type === cipherType.Login">
<div class="box-content">
<ng-container *ngIf="cipher.login.hasUris">
<div class="box-content-row box-content-row-multi" appBoxRow
*ngFor="let u of cipher.login.uris; let i = index; trackBy:trackByFunction">
<a href="#" appStopClick (click)="removeUri(u)" appA11yTitle="{{'remove' | i18n}}">
<i class="fa fa-minus-circle fa-lg" aria-hidden="true" role="button"></i>
</a>
<div class="row-main">
<label for="loginUri{{i}}">{{'uriPosition' | i18n : (i + 1)}}</label>
<input id="loginUri{{i}}" type="text" name="Login.Uris[{{i}}].Uri" [(ngModel)]="u.uri"
placeholder="{{'ex' | i18n}} https://google.com" appInputVerbatim>
<label for="loginUriMatch{{i}}" class="sr-only">
{{'matchDetection' | i18n}} {{(i + 1)}}
</label>
<select id="loginUriMatch{{i}}" name="Login.Uris[{{i}}].Match" [(ngModel)]="u.match"
[hidden]="u.showOptions === false || (u.showOptions == null && u.match == null)"
(change)="loginUriMatchChanged(u)">
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleOptions' | i18n}}" (click)="toggleUriOptions(u)">
<i class="fa fa-lg fa-cog" aria-hidden="true"></i>
</a>
</div>
</div>
</ng-container>
<a href="#" appStopClick appBlurClick (click)="addUri()" class="box-content-row" role="button">
<i class="fa fa-plus-circle fa-fw fa-lg" aria-hidden="true"></i> {{'newUri' | i18n}}
</a>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="folder">{{'folder' | i18n}}</label>
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
<option *ngFor="let f of folders" [ngValue]="f.id">{{f.name}}</option>
</select>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="favorite">{{'favorite' | i18n}}</label>
<input id="favorite" type="checkbox" name="Favorite" [(ngModel)]="cipher.favorite">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="canUseReprompt">
<label for="passwordPrompt">{{'passwordPrompt' | i18n}}
<a href="#" appA11yTitle="{{'learnMore' | i18n}}" (click)="openHelpReprompt()">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</label>
<input id="passwordPrompt" type="checkbox" name="PasswordPrompt" [ngModel]="reprompt"
(change)="repromptChanged()">
</div>
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
(click)="attachments()" *ngIf="editMode && !cloneMode" role="button">
<div class="row-main">{{'attachments' | i18n}}</div>
<i class="fa fa-chevron-right row-sub-icon" aria-hidden="true"></i>
</a>
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
(click)="editCollections()" *ngIf="editMode && !cloneMode && cipher.organizationId"
role="button">
<div class="row-main">{{'collections' | i18n}}</div>
<i class="fa fa-chevron-right row-sub-icon" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<label for="notes">{{'notes' | i18n}}</label>
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes"></textarea>
</div>
</div>
</div>
<app-vault-add-edit-custom-fields [cipher]="cipher" [thisCipherType]="cipher.type" [editMode]="editMode">
</app-vault-add-edit-custom-fields>
<div class="box" *ngIf="allowOwnershipOptions()">
<div class="box-header">
{{'ownership' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="organizationId">{{'whoOwnsThisItem' | i18n}}</label>
<select id="organizationId" class="form-control" name="OrganizationId"
[(ngModel)]="cipher.organizationId" (change)="organizationChanged()">
<option *ngFor="let o of ownershipOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
</div>
</div>
<div class="box" *ngIf="(!editMode || cloneMode) && cipher.organizationId">
<div class="box-header">
{{'collections' | i18n}}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox" *ngFor="let c of collections; let i = index"
appBoxRow>
<label for="collection_{{i}}">{{c.name}}</label>
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked"
name="Collection[{{i}}].Checked">
</div>
</div>
</div>
<div class="content">
<div class="inner-content" *ngIf="cipher">
<div class="box">
<app-callout type="info" *ngIf="allowOwnershipOptions() && !allowPersonal">
{{ "personalOwnershipPolicyInEffect" | i18n }}
</app-callout>
<div class="box-header">
{{ title }}
</div>
</div>
<div class="footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" [disabled]="form.loading">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button appBlurClick type="button" (click)="cancel()">
{{'cancel' | i18n}}
</button>
<div class="right">
<button appBlurClick type="button" (click)="share()" appA11yTitle="{{'moveToOrganization' | i18n}}"
*ngIf="editMode && cipher && !cipher.organizationId && !cloneMode">
<i class="fa fa-arrow-circle-o-right fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode && !cloneMode" [disabled]="deleteBtn.loading"
[appApiAction]="deletePromise">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" aria-hidden="true"></i>
</button>
<div class="box-content">
<div class="box-content-row" *ngIf="!editMode" appBoxRow>
<label for="type">{{ "type" | i18n }}</label>
<select id="type" name="Type" [(ngModel)]="cipher.type">
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
type="text"
name="Name"
[(ngModel)]="cipher.name"
[appAutofocus]="!editMode"
/>
</div>
<!-- Login -->
<div *ngIf="cipher.type === cipherType.Login">
<div class="box-content-row" appBoxRow>
<label for="loginUsername">{{ "username" | i18n }}</label>
<input
id="loginUsername"
type="text"
name="Login.Username"
[(ngModel)]="cipher.login.username"
appInputVerbatim
/>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="loginPassword">{{ "password" | i18n }}</label>
<input
id="loginPassword"
class="monospaced"
type="{{ showPassword ? 'text' : 'password' }}"
name="Login.Password"
[(ngModel)]="cipher.login.password"
[disabled]="!cipher.viewPassword"
appInputVerbatim
/>
</div>
<div class="action-buttons" *ngIf="cipher.viewPassword">
<button
type="button"
#checkPasswordBtn
class="row-btn btn"
appBlurClick
appA11yTitle="{{ 'checkPassword' | i18n }}"
(click)="checkPassword()"
[appApiAction]="checkPasswordPromise"
[disabled]="checkPasswordBtn.loading"
>
<i
class="fa fa-lg fa-check-circle"
[hidden]="checkPasswordBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-lg fa-spinner fa-spin"
[hidden]="!checkPasswordBtn.loading"
aria-hidden="true"
></i>
</button>
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'generatePassword' | i18n }}"
(click)="generatePassword()"
>
<i class="fa fa-lg fa-refresh" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label>
<input
id="loginTotp"
type="{{ cipher.viewPassword ? 'text' : 'password' }}"
name="Login.Totp"
class="monospaced"
[(ngModel)]="cipher.login.totp"
[disabled]="!cipher.viewPassword"
appInputVerbatim
/>
</div>
</div>
<!-- Card -->
<div *ngIf="cipher.type === cipherType.Card">
<div class="box-content-row" appBoxRow>
<label for="cardCardholderName">{{ "cardholderName" | i18n }}</label>
<input
id="cardCardholderName"
type="text"
name="Card.CardCardholderName"
[(ngModel)]="cipher.card.cardholderName"
/>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="cardNumber">{{ "number" | i18n }}</label>
<input
id="cardNumber"
class="monospaced"
type="{{ showCardNumber ? 'text' : 'password' }}"
name="Card.Number"
[(ngModel)]="cipher.card.number"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleCardNumber()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber }"
></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardBrand">{{ "brand" | i18n }}</label>
<select id="cardBrand" name="Card.Brand" [(ngModel)]="cipher.card.brand">
<option *ngFor="let o of cardBrandOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardExpMonth">{{ "expirationMonth" | i18n }}</label>
<select id="cardExpMonth" name="Card.ExpMonth" [(ngModel)]="cipher.card.expMonth">
<option *ngFor="let o of cardExpMonthOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardExpYear">{{ "expirationYear" | i18n }}</label>
<input
id="cardExpYear"
type="text"
name="Card.ExpYear"
[(ngModel)]="cipher.card.expYear"
placeholder="{{ 'ex' | i18n }} {{ currentDate | date: 'yyyy' }}"
/>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="cardCode">{{ "securityCode" | i18n }}</label>
<input
id="cardCode"
class="monospaced"
type="{{ showCardCode ? 'text' : 'password' }}"
name="Card.Code"
[(ngModel)]="cipher.card.code"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleCardCode()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showCardCode, 'fa-eye-slash': showCardCode }"
></i>
</a>
</div>
</div>
</div>
<!-- Identity -->
<div *ngIf="cipher.type === cipherType.Identity">
<div class="box-content-row" appBoxRow>
<label for="idTitle">{{ "title" | i18n }}</label>
<select id="idTitle" name="Identity.Title" [(ngModel)]="cipher.identity.title">
<option *ngFor="let o of identityTitleOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="idFirstName">{{ "firstName" | i18n }}</label>
<input
id="idFirstName"
type="text"
name="Identity.FirstName"
[(ngModel)]="cipher.identity.firstName"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idMiddleName">{{ "middleName" | i18n }}</label>
<input
id="idMiddleName"
type="text"
name="Identity.MiddleName"
[(ngModel)]="cipher.identity.middleName"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idLastName">{{ "lastName" | i18n }}</label>
<input
id="idLastName"
type="text"
name="Identity.LastName"
[(ngModel)]="cipher.identity.lastName"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idUsername">{{ "username" | i18n }}</label>
<input
id="idUsername"
type="text"
name="Identity.Username"
[(ngModel)]="cipher.identity.username"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idCompany">{{ "company" | i18n }}</label>
<input
id="idCompany"
type="text"
name="Identity.Company"
[(ngModel)]="cipher.identity.company"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idSsn">{{ "ssn" | i18n }}</label>
<input
id="idSsn"
type="text"
name="Identity.SSN"
[(ngModel)]="cipher.identity.ssn"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idPassportNumber">{{ "passportNumber" | i18n }}</label>
<input
id="idPassportNumber"
type="text"
name="Identity.PassportNumber"
[(ngModel)]="cipher.identity.passportNumber"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idLicenseNumber">{{ "licenseNumber" | i18n }}</label>
<input
id="idLicenseNumber"
type="text"
name="Identity.LicenseNumber"
[(ngModel)]="cipher.identity.licenseNumber"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idEmail">{{ "email" | i18n }}</label>
<input
id="idEmail"
type="text"
name="Identity.Email"
[(ngModel)]="cipher.identity.email"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idPhone">{{ "phone" | i18n }}</label>
<input
id="idPhone"
type="text"
name="Identity.Phone"
[(ngModel)]="cipher.identity.phone"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress1">{{ "address1" | i18n }}</label>
<input
id="idAddress1"
type="text"
name="Identity.Address1"
[(ngModel)]="cipher.identity.address1"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress2">{{ "address2" | i18n }}</label>
<input
id="idAddress2"
type="text"
name="Identity.Address2"
[(ngModel)]="cipher.identity.address2"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress3">{{ "address3" | i18n }}</label>
<input
id="idAddress3"
type="text"
name="Identity.Address3"
[(ngModel)]="cipher.identity.address3"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idCity">{{ "cityTown" | i18n }}</label>
<input
id="idCity"
type="text"
name="Identity.City"
[(ngModel)]="cipher.identity.city"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idState">{{ "stateProvince" | i18n }}</label>
<input
id="idState"
type="text"
name="Identity.State"
[(ngModel)]="cipher.identity.state"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idPostalCode">{{ "zipPostalCode" | i18n }}</label>
<input
id="idPostalCode"
type="text"
name="Identity.PostalCode"
[(ngModel)]="cipher.identity.postalCode"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idCountry">{{ "country" | i18n }}</label>
<input
id="idCountry"
type="text"
name="Identity.Country"
[(ngModel)]="cipher.identity.country"
/>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.type === cipherType.Login">
<div class="box-content">
<ng-container *ngIf="cipher.login.hasUris">
<div
class="box-content-row box-content-row-multi"
appBoxRow
*ngFor="let u of cipher.login.uris; let i = index; trackBy: trackByFunction"
>
<a href="#" appStopClick (click)="removeUri(u)" appA11yTitle="{{ 'remove' | i18n }}">
<i class="fa fa-minus-circle fa-lg" aria-hidden="true" role="button"></i>
</a>
<div class="row-main">
<label for="loginUri{{ i }}">{{ "uriPosition" | i18n: i + 1 }}</label>
<input
id="loginUri{{ i }}"
type="text"
name="Login.Uris[{{ i }}].Uri"
[(ngModel)]="u.uri"
placeholder="{{ 'ex' | i18n }} https://google.com"
appInputVerbatim
/>
<label for="loginUriMatch{{ i }}" class="sr-only">
{{ "matchDetection" | i18n }} {{ i + 1 }}
</label>
<select
id="loginUriMatch{{ i }}"
name="Login.Uris[{{ i }}].Match"
[(ngModel)]="u.match"
[hidden]="u.showOptions === false || (u.showOptions == null && u.match == null)"
(change)="loginUriMatchChanged(u)"
>
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleOptions' | i18n }}"
(click)="toggleUriOptions(u)"
>
<i class="fa fa-lg fa-cog" aria-hidden="true"></i>
</a>
</div>
</div>
</ng-container>
<a
href="#"
appStopClick
appBlurClick
(click)="addUri()"
class="box-content-row"
role="button"
>
<i class="fa fa-plus-circle fa-fw fa-lg" aria-hidden="true"></i> {{ "newUri" | i18n }}
</a>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="folder">{{ "folder" | i18n }}</label>
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option>
</select>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="favorite">{{ "favorite" | i18n }}</label>
<input id="favorite" type="checkbox" name="Favorite" [(ngModel)]="cipher.favorite" />
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="canUseReprompt">
<label for="passwordPrompt"
>{{ "passwordPrompt" | i18n }}
<a href="#" appA11yTitle="{{ 'learnMore' | i18n }}" (click)="openHelpReprompt()">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</label>
<input
id="passwordPrompt"
type="checkbox"
name="PasswordPrompt"
[ngModel]="reprompt"
(change)="repromptChanged()"
/>
</div>
<a
class="box-content-row box-content-row-flex text-default"
href="#"
appStopClick
appBlurClick
(click)="attachments()"
*ngIf="editMode && !cloneMode"
role="button"
>
<div class="row-main">{{ "attachments" | i18n }}</div>
<i class="fa fa-chevron-right row-sub-icon" aria-hidden="true"></i>
</a>
<a
class="box-content-row box-content-row-flex text-default"
href="#"
appStopClick
appBlurClick
(click)="editCollections()"
*ngIf="editMode && !cloneMode && cipher.organizationId"
role="button"
>
<div class="row-main">{{ "collections" | i18n }}</div>
<i class="fa fa-chevron-right row-sub-icon" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<label for="notes">{{ "notes" | i18n }}</label>
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes"></textarea>
</div>
</div>
</div>
<app-vault-add-edit-custom-fields
[cipher]="cipher"
[thisCipherType]="cipher.type"
[editMode]="editMode"
>
</app-vault-add-edit-custom-fields>
<div class="box" *ngIf="allowOwnershipOptions()">
<div class="box-header">
{{ "ownership" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="organizationId">{{ "whoOwnsThisItem" | i18n }}</label>
<select
id="organizationId"
class="form-control"
name="OrganizationId"
[(ngModel)]="cipher.organizationId"
(change)="organizationChanged()"
>
<option *ngFor="let o of ownershipOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
</div>
</div>
<div class="box" *ngIf="(!editMode || cloneMode) && cipher.organizationId">
<div class="box-header">
{{ "collections" | i18n }}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div
class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index"
appBoxRow
>
<label for="collection_{{ i }}">{{ c.name }}</label>
<input
id="collection_{{ i }}"
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
/>
</div>
</div>
</div>
</div>
</div>
<div class="footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button appBlurClick type="button" (click)="cancel()">
{{ "cancel" | i18n }}
</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="share()"
appA11yTitle="{{ 'moveToOrganization' | i18n }}"
*ngIf="editMode && cipher && !cipher.organizationId && !cloneMode"
>
<i class="fa fa-arrow-circle-o-right fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button
#deleteBtn
appBlurClick
type="button"
(click)="delete()"
class="danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode && !cloneMode"
[disabled]="deleteBtn.loading"
[appApiAction]="deletePromise"
>
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!deleteBtn.loading"
aria-hidden="true"
></i>
</button>
</div>
</div>
</form>

View File

@ -1,103 +1,125 @@
import {
Component,
NgZone,
OnChanges,
OnDestroy,
ViewChild
} from '@angular/core';
import { NgForm } from '@angular/forms';
import { Component, NgZone, OnChanges, OnDestroy, ViewChild } from "@angular/core";
import { NgForm } from "@angular/forms";
import { AuditService } from 'jslib-common/abstractions/audit.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { AuditService } from "jslib-common/abstractions/audit.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/add-edit.component';
import { AddEditComponent as BaseAddEditComponent } from "jslib-angular/components/add-edit.component";
const BroadcasterSubscriptionId = 'AddEditComponent';
const BroadcasterSubscriptionId = "AddEditComponent";
@Component({
selector: 'app-vault-add-edit',
templateUrl: 'add-edit.component.html',
selector: "app-vault-add-edit",
templateUrl: "add-edit.component.html",
})
export class AddEditComponent extends BaseAddEditComponent implements OnChanges, OnDestroy {
@ViewChild('form')
private form: NgForm;
constructor(cipherService: CipherService, folderService: FolderService,
i18nService: I18nService, platformUtilsService: PlatformUtilsService,
auditService: AuditService, stateService: StateService,
collectionService: CollectionService, messagingService: MessagingService,
eventService: EventService, policyService: PolicyService,
passwordRepromptService: PasswordRepromptService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, logService: LogService,
organizationService: OrganizationService) {
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
collectionService, messagingService, eventService, policyService, logService,
passwordRepromptService, organizationService);
}
@ViewChild("form")
private form: NgForm;
constructor(
cipherService: CipherService,
folderService: FolderService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
auditService: AuditService,
stateService: StateService,
collectionService: CollectionService,
messagingService: MessagingService,
eventService: EventService,
policyService: PolicyService,
passwordRepromptService: PasswordRepromptService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
logService: LogService,
organizationService: OrganizationService
) {
super(
cipherService,
folderService,
i18nService,
platformUtilsService,
auditService,
stateService,
collectionService,
messagingService,
eventService,
policyService,
logService,
passwordRepromptService,
organizationService
);
}
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
// We use ngOnChanges for everything else instead.
}
async ngOnChanges() {
await super.init();
await this.load();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async load() {
if (document.querySelectorAll('app-vault-add-edit .ng-dirty').length === 0 ||
(this.cipher != null && this.cipherId !== this.cipher.id)) {
this.cipher = null;
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
super.load();
}
});
});
// We use ngOnChanges for everything else instead.
}
onWindowHidden() {
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach(field => {
field.showValue = false;
});
}
}
async ngOnChanges() {
await super.init();
await this.load();
}
allowOwnershipOptions(): boolean {
return (!this.editMode || this.cloneMode) && this.ownershipOptions
&& (this.ownershipOptions.length > 1 || !this.allowPersonal);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
markPasswordAsDirty() {
this.form.controls['Login.Password'].markAsDirty();
async load() {
if (
document.querySelectorAll("app-vault-add-edit .ng-dirty").length === 0 ||
(this.cipher != null && this.cipherId !== this.cipher.id)
) {
this.cipher = null;
}
super.load();
}
openHelpReprompt() {
this.platformUtilsService.launchUri('https://bitwarden.com/help/article/managing-items/#protect-individual-items');
onWindowHidden() {
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach((field) => {
field.showValue = false;
});
}
}
allowOwnershipOptions(): boolean {
return (
(!this.editMode || this.cloneMode) &&
this.ownershipOptions &&
(this.ownershipOptions.length > 1 || !this.allowPersonal)
);
}
markPasswordAsDirty() {
this.form.controls["Login.Password"].markAsDirty();
}
openHelpReprompt() {
this.platformUtilsService.launchUri(
"https://bitwarden.com/help/article/managing-items/#protect-individual-items"
);
}
}

View File

@ -1,53 +1,76 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="attachmentsTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box" *ngIf="cipher && cipher.hasAttachments">
<div class="box-header" id="attachmentsTitle">
{{'attachments' | i18n}}
</div>
<div class="box-content no-hover">
<div class="box-content-row box-content-row-flex" *ngFor="let a of cipher.attachments">
<div class="row-main">
{{a.fileName}}
</div>
<small class="row-sub-label">{{a.sizeName}}</small>
<div class="action-buttons no-pad">
<button class="row-btn btn" type="button" appStopClick appBlurClick
appA11yTitle="{{'delete' | i18n}}" (click)="delete(a)" #deleteBtn
[appApiAction]="deletePromises[a.id]" [disabled]="deleteBtn.loading">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"
aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'newAttachment' | i18n}}
</div>
<div class="box-content no-hover">
<div class="box-content-row">
<label for="file">{{'file' | i18n}}</label>
<input type="file" id="file" name="file" required>
</div>
</div>
<div class="box-footer">
{{'maxFileSize' | i18n}}
</div>
</div>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}"
[disabled]="form.loading">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box" *ngIf="cipher && cipher.hasAttachments">
<div class="box-header" id="attachmentsTitle">
{{ "attachments" | i18n }}
</div>
<div class="box-content no-hover">
<div class="box-content-row box-content-row-flex" *ngFor="let a of cipher.attachments">
<div class="row-main">
{{ a.fileName }}
</div>
<small class="row-sub-label">{{ a.sizeName }}</small>
<div class="action-buttons no-pad">
<button
class="row-btn btn"
type="button"
appStopClick
appBlurClick
appA11yTitle="{{ 'delete' | i18n }}"
(click)="delete(a)"
#deleteBtn
[appApiAction]="deletePromises[a.id]"
[disabled]="deleteBtn.loading"
>
<i
class="fa fa-trash-o fa-lg fa-fw"
[hidden]="deleteBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!deleteBtn.loading"
aria-hidden="true"
></i>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{ "newAttachment" | i18n }}
</div>
<div class="box-content no-hover">
<div class="box-content-row">
<label for="file">{{ "file" | i18n }}</label>
<input type="file" id="file" name="file" required />
</div>
</div>
<div class="box-footer">
{{ "maxFileSize" | i18n }}
</div>
</div>
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@ -1,25 +1,38 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib-angular/components/attachments.component';
import { AttachmentsComponent as BaseAttachmentsComponent } from "jslib-angular/components/attachments.component";
@Component({
selector: 'app-vault-attachments',
templateUrl: 'attachments.component.html',
selector: "app-vault-attachments",
templateUrl: "attachments.component.html",
})
export class AttachmentsComponent extends BaseAttachmentsComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, logService: LogService,
stateService: StateService) {
super(cipherService, i18nService, cryptoService, platformUtilsService,
apiService, window, logService, stateService);
}
constructor(
cipherService: CipherService,
i18nService: I18nService,
cryptoService: CryptoService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService,
stateService: StateService
) {
super(
cipherService,
i18nService,
cryptoService,
platformUtilsService,
apiService,
window,
logService,
stateService
);
}
}

View File

@ -1,39 +1,61 @@
<div class="content">
<cdk-virtual-scroll-viewport itemSize="42" minBufferPx="400" maxBufferPx="600" *ngIf="ciphers.length">
<div class="list">
<a *cdkVirtualFor="let c of ciphers; trackBy: trackByFn" appStopClick (click)="selectCipher(c)"
(contextmenu)="rightClickCipher(c)" href="#" title="{{'viewItem' | i18n}}"
[ngClass]="{'active': c.id === activeCipherId}" class="flex-list-item virtual-scroll-item">
<app-vault-icon [cipher]="c"></app-vault-icon>
<div class="flex-cipher-list-item">
<span class="text">
{{c.name}}
<ng-container *ngIf="c.organizationId">
<i class="fa fa-cube text-muted" title="{{'shared' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'shared' | i18n}}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i class="fa fa-paperclip text-muted" title="{{'attachments' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'attachments' | i18n}}</span>
</ng-container>
</span>
<span *ngIf="c.subTitle" class="detail">{{c.subTitle}}</span>
</div>
</a>
<cdk-virtual-scroll-viewport
itemSize="42"
minBufferPx="400"
maxBufferPx="600"
*ngIf="ciphers.length"
>
<div class="list">
<a
*cdkVirtualFor="let c of ciphers; trackBy: trackByFn"
appStopClick
(click)="selectCipher(c)"
(contextmenu)="rightClickCipher(c)"
href="#"
title="{{ 'viewItem' | i18n }}"
[ngClass]="{ active: c.id === activeCipherId }"
class="flex-list-item virtual-scroll-item"
>
<app-vault-icon [cipher]="c"></app-vault-icon>
<div class="flex-cipher-list-item">
<span class="text">
{{ c.name }}
<ng-container *ngIf="c.organizationId">
<i class="fa fa-cube text-muted" title="{{ 'shared' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="fa fa-paperclip text-muted"
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
</span>
<span *ngIf="c.subTitle" class="detail">{{ c.subTitle }}</span>
</div>
</cdk-virtual-scroll-viewport>
<div class="no-items" *ngIf="!ciphers.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
<p>{{'noItemsInList' | i18n}}</p>
<button (click)="addCipher()" class="btn block primary link">{{'addItem' | i18n}}</button>
</ng-container>
</a>
</div>
</cdk-virtual-scroll-viewport>
<div class="no-items" *ngIf="!ciphers.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
<p>{{ "noItemsInList" | i18n }}</p>
<button (click)="addCipher()" class="btn block primary link">{{ "addItem" | i18n }}</button>
</ng-container>
</div>
</div>
<div class="footer">
<button appBlurClick (click)="addCipher()" (contextmenu)="addCipherOptions()"
class="block primary" appA11yTitle="{{'addItem' | i18n}}" [disabled]="deleted">
<i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
<button
appBlurClick
(click)="addCipher()"
(contextmenu)="addCipherOptions()"
class="block primary"
appA11yTitle="{{ 'addItem' | i18n }}"
[disabled]="deleted"
>
<i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
</div>

View File

@ -1,28 +1,27 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { CiphersComponent as BaseCiphersComponent } from 'jslib-angular/components/ciphers.component';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { CiphersComponent as BaseCiphersComponent } from "jslib-angular/components/ciphers.component";
import { SearchService } from "jslib-common/abstractions/search.service";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { SearchBarService } from '../layout/search/search-bar.service';
import { SearchBarService } from "../layout/search/search-bar.service";
@Component({
selector: 'app-vault-ciphers',
templateUrl: 'ciphers.component.html',
selector: "app-vault-ciphers",
templateUrl: "ciphers.component.html",
})
export class CiphersComponent extends BaseCiphersComponent {
constructor(searchService: SearchService, searchBarService: SearchBarService) {
super(searchService);
constructor(searchService: SearchService, searchBarService: SearchBarService) {
super(searchService);
searchBarService.searchText.subscribe((searchText) => {
this.searchText = searchText;
this.search(200);
});
}
searchBarService.searchText.subscribe(searchText => {
this.searchText = searchText;
this.search(200);
});
}
trackByFn(index: number, c: CipherView) {
return c.id;
}
trackByFn(index: number, c: CipherView) {
return c.id;
}
}

View File

@ -1,32 +1,48 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="collectionsTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="collectionsTitle">
{{'collections' | i18n}}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index" appBoxRow>
<label for="collection_{{i}}">{{c.name}}</label>
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked"
name="Collection[{{i}}].Checked">
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="collectionsTitle">
{{ "collections" | i18n }}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div
class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index"
appBoxRow
>
<label for="collection_{{ i }}">{{ c.name }}</label>
<input
id="collection_{{ i }}"
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
/>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}"
[disabled]="form.loading">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button>
<button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@ -1,21 +1,25 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { CollectionsComponent as BaseCollectionsComponent } from 'jslib-angular/components/collections.component';
import { CollectionsComponent as BaseCollectionsComponent } from "jslib-angular/components/collections.component";
@Component({
selector: 'app-vault-collections',
templateUrl: 'collections.component.html',
selector: "app-vault-collections",
templateUrl: "collections.component.html",
})
export class CollectionsComponent extends BaseCollectionsComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
logService: LogService) {
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
}
constructor(
cipherService: CipherService,
i18nService: I18nService,
collectionService: CollectionService,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
}
}

View File

@ -1,36 +1,45 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="exportTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [formGroup]="exportForm">
<div class="modal-body">
<app-callout type="warning" title="{{'vaultExportDisabled' | i18n}}" *ngIf="disabledByPolicy">
{{'personalVaultExportPolicyInEffect' | i18n}}
</app-callout>
<div class="box">
<div class="box-header" id="exportTitle">
{{'exportVault' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="format">{{'fileFormat' | i18n}}</label>
<select class="form-control" id="format" name="Format" formControlName="format">
<option *ngFor="let f of formatOptions" [value]="f.value">{{f.name}}</option>
</select>
</div>
<app-verify-master-password ngDefaultControl formControlName="secret" name="secret">
</app-verify-master-password>
</div>
<div class="box-footer">
<p>{{'confirmIdentity' | i18n}}</p>
</div>
</div>
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [formGroup]="exportForm">
<div class="modal-body">
<app-callout
type="warning"
title="{{ 'vaultExportDisabled' | i18n }}"
*ngIf="disabledByPolicy"
>
{{ "personalVaultExportPolicyInEffect" | i18n }}
</app-callout>
<div class="box">
<div class="box-header" id="exportTitle">
{{ "exportVault" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="format">{{ "fileFormat" | i18n }}</label>
<select class="form-control" id="format" name="Format" formControlName="format">
<option *ngFor="let f of formatOptions" [value]="f.value">{{ f.name }}</option>
</select>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'submit' | i18n}}"
[disabled]="disabledByPolicy">
<i class="fa fa-download fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button>
</div>
</form>
</div>
<app-verify-master-password ngDefaultControl formControlName="secret" name="secret">
</app-verify-master-password>
</div>
<div class="box-footer">
<p>{{ "confirmIdentity" | i18n }}</p>
</div>
</div>
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'submit' | i18n }}"
[disabled]="disabledByPolicy"
>
<i class="fa fa-download fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@ -1,57 +1,78 @@
import {
Component,
OnInit,
} from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import * as os from 'os';
import * as os from "os";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { ExportService } from 'jslib-common/abstractions/export.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { ExportService } from "jslib-common/abstractions/export.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
import { ExportComponent as BaseExportComponent } from 'jslib-angular/components/export.component';
import { ExportComponent as BaseExportComponent } from "jslib-angular/components/export.component";
const BroadcasterSubscriptionId = 'ExportComponent';
const BroadcasterSubscriptionId = "ExportComponent";
@Component({
selector: 'app-export',
templateUrl: 'export.component.html',
selector: "app-export",
templateUrl: "export.component.html",
})
export class ExportComponent extends BaseExportComponent implements OnInit {
constructor(cryptoService: CryptoService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, exportService: ExportService,
eventService: EventService, policyService: PolicyService,
userVerificationService: UserVerificationService, fb: FormBuilder,
private broadcasterService: BroadcasterService, logService: LogService) {
super(cryptoService, i18nService, platformUtilsService, exportService, eventService,
policyService, window, logService, userVerificationService, fb);
}
constructor(
cryptoService: CryptoService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
exportService: ExportService,
eventService: EventService,
policyService: PolicyService,
userVerificationService: UserVerificationService,
fb: FormBuilder,
private broadcasterService: BroadcasterService,
logService: LogService
) {
super(
cryptoService,
i18nService,
platformUtilsService,
exportService,
eventService,
policyService,
window,
logService,
userVerificationService,
fb
);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async warningDialog() {
if (this.encryptedFormat) {
return await this.platformUtilsService.showDialog(
this.i18nService.t('encExportKeyWarningDesc') +
os.EOL + os.EOL +
this.i18nService.t('encExportAccountWarningDesc'),
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'),
this.i18nService.t('cancel'), 'warning',
true);
} else {
return await this.platformUtilsService.showDialog(
this.i18nService.t('exportWarningDesc'),
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'),
this.i18nService.t('cancel'), 'warning');
}
async warningDialog() {
if (this.encryptedFormat) {
return await this.platformUtilsService.showDialog(
this.i18nService.t("encExportKeyWarningDesc") +
os.EOL +
os.EOL +
this.i18nService.t("encExportAccountWarningDesc"),
this.i18nService.t("confirmVaultExport"),
this.i18nService.t("exportVault"),
this.i18nService.t("cancel"),
"warning",
true
);
} else {
return await this.platformUtilsService.showDialog(
this.i18nService.t("exportWarningDesc"),
this.i18nService.t("confirmVaultExport"),
this.i18nService.t("exportVault"),
this.i18nService.t("cancel"),
"warning"
);
}
}
}

View File

@ -1,37 +1,66 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="folderAddEditTitle">
<div class="modal-dialog modal-sm" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="folderAddEditTitle">
{{title}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label>
<input id="name" type="text" name="Name" [(ngModel)]="folder.name"
[appAutofocus]="!editMode">
</div>
</div>
</div>
<div class="modal-dialog modal-sm" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="folderAddEditTitle">
{{ title }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
type="text"
name="Name"
[(ngModel)]="folder.name"
[appAutofocus]="!editMode"
/>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}"
[disabled]="form.loading">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button>
<div class="right">
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
[appApiAction]="deletePromise">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
aria-hidden="true"></i>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button>
<button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
<div class="right">
<button
#deleteBtn
appBlurClick
type="button"
(click)="delete()"
class="danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="deleteBtn.loading"
[appApiAction]="deletePromise"
>
<i
class="fa fa-trash-o fa-lg fa-fw"
[hidden]="deleteBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!deleteBtn.loading"
aria-hidden="true"
></i>
</button>
</div>
</div>
</form>
</div>
</div>

View File

@ -1,21 +1,23 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
FolderAddEditComponent as BaseFolderAddEditComponent,
} from 'jslib-angular/components/folder-add-edit.component';
import { FolderAddEditComponent as BaseFolderAddEditComponent } from "jslib-angular/components/folder-add-edit.component";
@Component({
selector: 'app-folder-add-edit',
templateUrl: 'folder-add-edit.component.html',
selector: "app-folder-add-edit",
templateUrl: "folder-add-edit.component.html",
})
export class FolderAddEditComponent extends BaseFolderAddEditComponent {
constructor(folderService: FolderService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, logService: LogService) {
super(folderService, i18nService, platformUtilsService, logService);
}
constructor(
folderService: FolderService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(folderService, i18nService, platformUtilsService, logService);
}
}

View File

@ -1,104 +1,146 @@
<div class="mac-bar"></div>
<div class="content">
<div class="inner-content">
<h2 class="sr-only">{{'filters' | i18n}}</h2>
<ul>
<li [ngClass]="{active: selectedAll}">
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{'allItems' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedFavorites}">
<a href="#" appStopClick appBlurClick (click)="selectFavorites()">
<i class="fa fa-fw fa-star" aria-hidden="true"></i>&nbsp;{{'favorites' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedTrash}">
<a href="#" appStopClick appBlurClick (click)="selectTrash()">
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>&nbsp;{{'trash' | i18n}}
</a>
</li>
</ul>
<h2>{{'types' | i18n}}</h2>
<ul>
<li [ngClass]="{active: selectedType === cipherType.Login}">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Login)">
<i class="fa fa-fw fa-globe" aria-hidden="true"></i>&nbsp;{{'typeLogin' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedType === cipherType.Card}">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Card)">
<i class="fa fa-fw fa-credit-card" aria-hidden="true"></i>&nbsp;{{'typeCard' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedType === cipherType.Identity}">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Identity)">
<i class="fa fa-fw fa-id-card-o" aria-hidden="true"></i>&nbsp;{{'typeIdentity' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedType === cipherType.SecureNote}">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.SecureNote)">
<i class="fa fa-fw fa-sticky-note-o" aria-hidden="true"></i>&nbsp;{{'typeSecureNote' | i18n}}
</a>
</li>
</ul>
<p *ngIf="!loaded" class="text-muted">{{'loading' | i18n}}</p>
<ng-container *ngIf="loaded">
<div class="heading">
<h2>{{'folders' | i18n}}</h2>
<button appBlurClick (click)="addFolder()" appA11yTitle="{{'addFolder' | i18n}}">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
</button>
</div>
<ul>
<ng-template #recursiveFolders let-folders>
<li *ngFor="let f of folders"
[ngClass]="{active: selectedFolder && f.node.id === selectedFolderId}">
<a href="#" appStopClick appBlurClick (click)="selectFolder(f.node)">
<i *ngIf="f.children.length" class="fa-fw fa" title="{{'toggleCollapse' | i18n}}" aria-hidden="true"
[ngClass]="{'fa-caret-right': isCollapsed(f.node), 'fa-caret-down': !isCollapsed(f.node)}"
(click)="collapse(f.node)" appStopProp></i>
<i *ngIf="f.children.length === 0" class="fa-fw fa fa-folder-o" aria-hidden="true"></i>
&nbsp;{{f.node.name}}
<span appStopProp appStopClick (click)="editFolder(f.node)" role="button"
appA11yTitle="{{'editFolder' | i18n}}" *ngIf="f.node.id">
<i class="fa fa-pencil fa-fw" aria-hidden="true"></i>
</span>
</a>
<ul class="fa-ul" *ngIf="f.children.length && !isCollapsed(f.node)">
<ng-container *ngTemplateOutlet="recursiveFolders; context:{ $implicit: f.children }">
</ng-container>
</ul>
</li>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveFolders; context:{ $implicit: nestedFolders }"></ng-container>
<div class="inner-content">
<h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedAll }">
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{ "allItems" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedFavorites }">
<a href="#" appStopClick appBlurClick (click)="selectFavorites()">
<i class="fa fa-fw fa-star" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedTrash }">
<a href="#" appStopClick appBlurClick (click)="selectTrash()">
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>&nbsp;{{ "trash" | i18n }}
</a>
</li>
</ul>
<h2>{{ "types" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedType === cipherType.Login }">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Login)">
<i class="fa fa-fw fa-globe" aria-hidden="true"></i>&nbsp;{{ "typeLogin" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.Card }">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Card)">
<i class="fa fa-fw fa-credit-card" aria-hidden="true"></i>&nbsp;{{ "typeCard" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.Identity }">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Identity)">
<i class="fa fa-fw fa-id-card-o" aria-hidden="true"></i>&nbsp;{{ "typeIdentity" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.SecureNote }">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.SecureNote)">
<i class="fa fa-fw fa-sticky-note-o" aria-hidden="true"></i>&nbsp;{{
"typeSecureNote" | i18n
}}
</a>
</li>
</ul>
<p *ngIf="!loaded" class="text-muted">{{ "loading" | i18n }}</p>
<ng-container *ngIf="loaded">
<div class="heading">
<h2>{{ "folders" | i18n }}</h2>
<button appBlurClick (click)="addFolder()" appA11yTitle="{{ 'addFolder' | i18n }}">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
</button>
</div>
<ul>
<ng-template #recursiveFolders let-folders>
<li
*ngFor="let f of folders"
[ngClass]="{ active: selectedFolder && f.node.id === selectedFolderId }"
>
<a href="#" appStopClick appBlurClick (click)="selectFolder(f.node)">
<i
*ngIf="f.children.length"
class="fa-fw fa"
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'fa-caret-right': isCollapsed(f.node),
'fa-caret-down': !isCollapsed(f.node)
}"
(click)="collapse(f.node)"
appStopProp
></i>
<i
*ngIf="f.children.length === 0"
class="fa-fw fa fa-folder-o"
aria-hidden="true"
></i>
&nbsp;{{ f.node.name }}
<span
appStopProp
appStopClick
(click)="editFolder(f.node)"
role="button"
appA11yTitle="{{ 'editFolder' | i18n }}"
*ngIf="f.node.id"
>
<i class="fa fa-pencil fa-fw" aria-hidden="true"></i>
</span>
</a>
<ul class="fa-ul" *ngIf="f.children.length && !isCollapsed(f.node)">
<ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }"
>
</ng-container>
</ul>
<div *ngIf="collections && collections.length">
<h2>{{'collections' | i18n}}</h2>
<ul>
<ng-template #recursiveCollections let-collections>
<li *ngFor="let c of collections" [ngClass]="{active: c.node.id === selectedCollectionId}">
<a href="#" appStopClick appBlurClick (click)="selectCollection(c.node)">
<i *ngIf="c.children.length" class="fa-fw fa" title="{{'toggleCollapse' | i18n}}" aria-hidden="true"
[ngClass]="{'fa-caret-right': isCollapsed(c.node), 'fa-caret-down': !isCollapsed(c.node)}"
(click)="collapse(c.node)" appStopProp></i>
<i *ngIf="c.children.length === 0" class="fa-fw fa fa-cube" aria-hidden="true"></i>
&nbsp;{{c.node.name}}
</a>
<ul class="fa-ul" *ngIf="c.children.length && !isCollapsed(c.node)">
<ng-container
*ngTemplateOutlet="recursiveCollections; context:{ $implicit: c.children }">
</ng-container>
</ul>
</li>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveCollections; context:{ $implicit: nestedCollections }">
</ng-container>
</ul>
</div>
</ng-container>
</div>
<div class="footer">
<app-nav class="nav"></app-nav>
</div>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }"
></ng-container>
</ul>
<div *ngIf="collections && collections.length">
<h2>{{ "collections" | i18n }}</h2>
<ul>
<ng-template #recursiveCollections let-collections>
<li
*ngFor="let c of collections"
[ngClass]="{ active: c.node.id === selectedCollectionId }"
>
<a href="#" appStopClick appBlurClick (click)="selectCollection(c.node)">
<i
*ngIf="c.children.length"
class="fa-fw fa"
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'fa-caret-right': isCollapsed(c.node),
'fa-caret-down': !isCollapsed(c.node)
}"
(click)="collapse(c.node)"
appStopProp
></i>
<i *ngIf="c.children.length === 0" class="fa-fw fa fa-cube" aria-hidden="true"></i>
&nbsp;{{ c.node.name }}
</a>
<ul class="fa-ul" *ngIf="c.children.length && !isCollapsed(c.node)">
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"
>
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: nestedCollections }"
>
</ng-container>
</ul>
</div>
</ng-container>
</div>
<div class="footer">
<app-nav class="nav"></app-nav>
</div>
</div>

View File

@ -1,18 +1,21 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/components/groupings.component';
import { GroupingsComponent as BaseGroupingsComponent } from "jslib-angular/components/groupings.component";
@Component({
selector: 'app-vault-groupings',
templateUrl: 'groupings.component.html',
selector: "app-vault-groupings",
templateUrl: "groupings.component.html",
})
export class GroupingsComponent extends BaseGroupingsComponent {
constructor(collectionService: CollectionService, folderService: FolderService,
stateService: StateService) {
super(collectionService, folderService, stateService);
}
constructor(
collectionService: CollectionService,
folderService: FolderService,
stateService: StateService
) {
super(collectionService, folderService, stateService);
}
}

View File

@ -1,40 +1,54 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="passwordGenHistoryTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="passwordGenHistoryTitle">
{{'passwordHistory' | i18n}}
</div>
<div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<div class="password-wrapper monospaced" appSelectCopy
[innerHTML]="h.password | colorPassword"></div>
<span class="detail">{{h.date | date:'medium'}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}"
(click)="copy(h.password)" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row" *ngIf="!history.length">
{{'noPasswordsInList' | i18n}}
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="passwordGenHistoryTitle">
{{ "passwordHistory" | i18n }}
</div>
<div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<div
class="password-wrapper monospaced"
appSelectCopy
[innerHTML]="h.password | colorPassword"
></div>
<span class="detail">{{ h.date | date: "medium" }}</span>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(h.password)"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
<div class="right">
<button appBlurClick type="button" (click)="clear()" class="danger"
appA11yTitle="{{'clear' | i18n}}">
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
</button>
</div>
<div class="box-content-row" *ngIf="!history.length">
{{ "noPasswordsInList" | i18n }}
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="clear()"
class="danger"
appA11yTitle="{{ 'clear' | i18n }}"
>
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,20 +1,21 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent,
} from 'jslib-angular/components/password-generator-history.component';
import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "jslib-angular/components/password-generator-history.component";
@Component({
selector: 'app-password-generator-history',
templateUrl: 'password-generator-history.component.html',
selector: "app-password-generator-history",
templateUrl: "password-generator-history.component.html",
})
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
constructor(
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService
) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
}

View File

@ -1,130 +1,230 @@
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{'generatePassword' | i18n}}">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-body">
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
{{'passwordGeneratorPolicyInEffect' | i18n}}
</app-callout>
<div class="password-block">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
</div>
<div class="box">
<div class="box-content condensed">
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="regenerate()">
<i class="fa fa-fw fa-refresh" aria-hidden="true"></i> {{'regeneratePassword' | i18n}}
</a>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="copy()">
<i class="fa fa-fw fa-clone" aria-hidden="true"></i> {{'copyPassword' | i18n}}
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<button type="button" (click)="toggleOptions()" appA11yTitle="{{'toggleVisibility' | i18n}}">
<i class="fa fa-plus-square-o" aria-hidden="true" [hidden]="showOptions"></i>
<i class="fa fa-minus-square-o" aria-hidden="true" [hidden]="!showOptions"></i>
{{'options' | i18n}}
</button>
</div>
<div class="box-content condensed" [hidden]="!showOptions">
<div class="box-content-row box-content-row-radio">
<label class="sr-only radio-header">{{'type' | i18n}}</label>
<div class="radio-group text-default" appBoxRow name="PassTypeOptions"
*ngFor="let o of passTypeOptions">
<input type="radio" class="radio" [(ngModel)]="options.type" name="Type_{{o.value}}"
id="type_{{o.value}}" [value]="o.value" (change)="saveOptions()"
[checked]="options.type === o.value">
<label class="unstyled" for="type_{{o.value}}">
{{o.name}}
</label>
</div>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions" *ngIf="options.type === 'passphrase'">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{'numWords' | i18n}}</label>
<input id="num-words" type="number" min="3" max="20" (blur)="saveOptions()"
[(ngModel)]="options.numWords">
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{'wordSeparator' | i18n}}</label>
<input id="word-separator" type="text" maxlength="1" (input)="saveOptions()"
[(ngModel)]="options.wordSeparator">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{'capitalize' | i18n}}</label>
<input id="capitalize" type="checkbox" (change)="saveOptions()"
[(ngModel)]="options.capitalize" [disabled]="enforcedPolicyOptions?.capitalize">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{'includeNumber' | i18n}}</label>
<input id="include-number" type="checkbox" (change)="saveOptions()"
[(ngModel)]="options.includeNumber" [disabled]="enforcedPolicyOptions?.includeNumber">
</div>
</div>
</div>
<ng-container *ngIf="options.type === 'password'">
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{'length' | i18n}}</label>
<input id="length" type="number" min="5" max="128" [(ngModel)]="options.length"
(blur)="saveOptions()">
<input id="lengthRange" type="range" min="5" max="128" step="1"
[(ngModel)]="options.length" (change)="sliderChanged()" (input)="sliderInput()">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label>
<input id="uppercase" type="checkbox" (change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useUppercase" [(ngModel)]="options.uppercase">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<input id="lowercase" type="checkbox" (change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useLowercase" [(ngModel)]="options.lowercase">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label>
<input id="numbers" type="checkbox" (change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useNumbers" [(ngModel)]="options.number">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label>
<input id="special" type="checkbox" (change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useSpecial" [(ngModel)]="options.special">
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{'minNumbers' | i18n}}</label>
<input id="min-number" type="number" min="0" max="9" (blur)="saveOptions()"
[(ngModel)]="options.minNumber">
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{'minSpecial' | i18n}}</label>
<input id="min-special" type="number" min="0" max="9" (blur)="saveOptions()"
[(ngModel)]="options.minSpecial">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{'ambiguous' | i18n}}</label>
<input id="ambiguous" type="checkbox" (change)="saveOptions()"
[(ngModel)]="avoidAmbiguous">
</div>
</div>
</div>
</ng-container>
</div>
<div class="modal-footer">
<button type="button" class="primary" appBlurClick *ngIf="showSelect" (click)="select()"
appA11yTitle="{{'select' | i18n}}">
<i class="fa fa-lg fa-fw fa-check" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{(showSelect ? 'cancel' : 'close') | i18n}}</button>
</div>
<div
class="modal fade"
role="dialog"
aria-modal="true"
attr.aria-label="{{ 'generatePassword' | i18n }}"
>
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-body">
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="password-block">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
</div>
<div class="box">
<div class="box-content condensed">
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="regenerate()">
<i class="fa fa-fw fa-refresh" aria-hidden="true"></i>
{{ "regeneratePassword" | i18n }}
</a>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="copy()">
<i class="fa fa-fw fa-clone" aria-hidden="true"></i> {{ "copyPassword" | i18n }}
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<button
type="button"
(click)="toggleOptions()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
>
<i class="fa fa-plus-square-o" aria-hidden="true" [hidden]="showOptions"></i>
<i class="fa fa-minus-square-o" aria-hidden="true" [hidden]="!showOptions"></i>
{{ "options" | i18n }}
</button>
</div>
<div class="box-content condensed" [hidden]="!showOptions">
<div class="box-content-row box-content-row-radio">
<label class="sr-only radio-header">{{ "type" | i18n }}</label>
<div
class="radio-group text-default"
appBoxRow
name="PassTypeOptions"
*ngFor="let o of passTypeOptions"
>
<input
type="radio"
class="radio"
[(ngModel)]="options.type"
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="saveOptions()"
[checked]="options.type === o.value"
/>
<label class="unstyled" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions" *ngIf="options.type === 'passphrase'">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="num-words"
type="number"
min="3"
max="20"
(blur)="saveOptions()"
[(ngModel)]="options.numWords"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
type="text"
maxlength="1"
(input)="saveOptions()"
[(ngModel)]="options.wordSeparator"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{ "capitalize" | i18n }}</label>
<input
id="capitalize"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.capitalize"
[disabled]="enforcedPolicyOptions?.capitalize"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{ "includeNumber" | i18n }}</label>
<input
id="include-number"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.includeNumber"
[disabled]="enforcedPolicyOptions?.includeNumber"
/>
</div>
</div>
</div>
<ng-container *ngIf="options.type === 'password'">
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
type="number"
min="5"
max="128"
[(ngModel)]="options.length"
(blur)="saveOptions()"
/>
<input
id="lengthRange"
type="range"
min="5"
max="128"
step="1"
[(ngModel)]="options.length"
(change)="sliderChanged()"
(input)="sliderInput()"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label>
<input
id="uppercase"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useUppercase"
[(ngModel)]="options.uppercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<input
id="lowercase"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useLowercase"
[(ngModel)]="options.lowercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label>
<input
id="numbers"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useNumbers"
[(ngModel)]="options.number"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label>
<input
id="special"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useSpecial"
[(ngModel)]="options.special"
/>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
type="number"
min="0"
max="9"
(blur)="saveOptions()"
[(ngModel)]="options.minNumber"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
type="number"
min="0"
max="9"
(blur)="saveOptions()"
[(ngModel)]="options.minSpecial"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{ "ambiguous" | i18n }}</label>
<input
id="ambiguous"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="avoidAmbiguous"
/>
</div>
</div>
</div>
</ng-container>
</div>
<div class="modal-footer">
<button
type="button"
class="primary"
appBlurClick
*ngIf="showSelect"
(click)="select()"
appA11yTitle="{{ 'select' | i18n }}"
>
<i class="fa fa-lg fa-fw fa-check" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">
{{ (showSelect ? "cancel" : "close") | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@ -1,20 +1,21 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
PasswordGeneratorComponent as BasePasswordGeneratorComponent,
} from 'jslib-angular/components/password-generator.component';
import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
@Component({
selector: 'app-password-generator',
templateUrl: 'password-generator.component.html',
selector: "app-password-generator",
templateUrl: "password-generator.component.html",
})
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
constructor(
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService
) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
}

View File

@ -1,35 +1,41 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="passwordHistoryTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="passwordHistoryTitle">
{{'passwordHistory' | i18n}}
</div>
<div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<span class="text monospaced">
{{h.password}}
</span>
<span class="detail">{{h.lastUsedDate | date:'medium'}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}"
(click)="copy(h.password)" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row" *ngIf="!history.length">
{{'noPasswordsInList' | i18n}}
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="passwordHistoryTitle">
{{ "passwordHistory" | i18n }}
</div>
<div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<span class="text monospaced">
{{ h.password }}
</span>
<span class="detail">{{ h.lastUsedDate | date: "medium" }}</span>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(h.password)"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
<div class="box-content-row" *ngIf="!history.length">
{{ "noPasswordsInList" | i18n }}
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div>
</div>
</div>

View File

@ -1,20 +1,21 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
PasswordHistoryComponent as BasePasswordHistoryComponent,
} from 'jslib-angular/components/password-history.component';
import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "jslib-angular/components/password-history.component";
@Component({
selector: 'app-password-history',
templateUrl: 'password-history.component.html',
selector: "app-password-history",
templateUrl: "password-history.component.html",
})
export class PasswordHistoryComponent extends BasePasswordHistoryComponent {
constructor(cipherService: CipherService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService) {
super(cipherService, platformUtilsService, i18nService, window);
}
constructor(
cipherService: CipherService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService
) {
super(cipherService, platformUtilsService, i18nService, window);
}
}

View File

@ -1,54 +1,75 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="moveToOrgTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="moveToOrgTitle">
{{'moveToOrganization' | i18n}}
</div>
<div class="box-content" *ngIf="!organizations || !organizations.length">
<div class="box-content-row">
{{'noOrganizationsList' | i18n}}
</div>
</div>
<div class="box-content" *ngIf="organizations && organizations.length">
<div class="box-content-row" appBoxRow>
<label for="organization">{{'organization' | i18n}}</label>
<select id="organization" name="OrganizationId" [(ngModel)]="organizationId"
(change)="filterCollections()">
<option *ngFor="let o of organizations" [ngValue]="o.id">{{o.name}}</option>
</select>
</div>
</div>
<div class="box-footer">
{{'moveToOrgDesc' | i18n}}
</div>
</div>
<div class="box" *ngIf="organizations && organizations.length">
<div class="box-header">
{{'collections' | i18n}}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index" appBoxRow>
<label for="collection_{{i}}">{{c.name}}</label>
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked"
name="Collection[{{i}}].Checked">
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="moveToOrgTitle">
{{ "moveToOrganization" | i18n }}
</div>
<div class="box-content" *ngIf="!organizations || !organizations.length">
<div class="box-content-row">
{{ "noOrganizationsList" | i18n }}
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}"
[disabled]="form.loading || !canSave" *ngIf="organizations && organizations.length">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button>
</div>
<div class="box-content" *ngIf="organizations && organizations.length">
<div class="box-content-row" appBoxRow>
<label for="organization">{{ "organization" | i18n }}</label>
<select
id="organization"
name="OrganizationId"
[(ngModel)]="organizationId"
(change)="filterCollections()"
>
<option *ngFor="let o of organizations" [ngValue]="o.id">{{ o.name }}</option>
</select>
</div>
</form>
</div>
</div>
<div class="box-footer">
{{ "moveToOrgDesc" | i18n }}
</div>
</div>
<div class="box" *ngIf="organizations && organizations.length">
<div class="box-header">
{{ "collections" | i18n }}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div
class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index"
appBoxRow
>
<label for="collection_{{ i }}">{{ c.name }}</label>
<input
id="collection_{{ i }}"
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
/>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading || !canSave"
*ngIf="organizations && organizations.length"
>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button>
<button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@ -1,23 +1,34 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ShareComponent as BaseShareComponent } from 'jslib-angular/components/share.component';
import { ShareComponent as BaseShareComponent } from "jslib-angular/components/share.component";
@Component({
selector: 'app-vault-share',
templateUrl: 'share.component.html',
selector: "app-vault-share",
templateUrl: "share.component.html",
})
export class ShareComponent extends BaseShareComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
logService: LogService, organizationService: OrganizationService) {
super(collectionService, platformUtilsService, i18nService, cipherService,
logService, organizationService);
}
constructor(
cipherService: CipherService,
i18nService: I18nService,
collectionService: CollectionService,
platformUtilsService: PlatformUtilsService,
logService: LogService,
organizationService: OrganizationService
) {
super(
collectionService,
platformUtilsService,
i18nService,
cipherService,
logService,
organizationService
);
}
}

View File

@ -1,36 +1,69 @@
<div id="vault" class="vault" attr.aria-hidden="{{showingModal}}">
<app-vault-groupings id="groupings" class="groupings" (onAllClicked)="clearGroupingFilters()" (onFavoritesClicked)="filterFavorites()"
(onCipherTypeClicked)="filterCipherType($event)" (onFolderClicked)="filterFolder($event.id)"
(onAddFolder)="addFolder()" (onEditFolder)="editFolder($event.id)"
(onCollectionClicked)="filterCollection($event.id)" (onTrashClicked)="filterDeleted()">
</app-vault-groupings>
<app-vault-ciphers id="items" class="items" [activeCipherId]="cipherId" (onCipherClicked)="viewCipher($event)"
(onCipherRightClicked)="viewCipherMenu($event)" (onAddCipher)="addCipher($event)"
(onAddCipherOptions)="addCipherOptions()">
</app-vault-ciphers>
<app-vault-view id="details" class="details" *ngIf="cipherId && action === 'view'" [cipherId]="cipherId"
(onCloneCipher)="cloneCipherWithoutPasswordPrompt($event)" (onEditCipher)="editCipherWithoutPasswordPrompt($event)"
(onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)" (onRestoredCipher)="restoredCipher($event)"
(onDeletedCipher)="deletedCipher($event)">
</app-vault-view>
<app-vault-add-edit id="addEdit" class="details" *ngIf="action === 'add' || action === 'edit' || action === 'clone'"
[cloneMode]="action === 'clone'"
[folderId]="action === 'add' && folderId !== 'none' ? folderId : null"
[organizationId]="action === 'add' ? addOrganizationId : null"
[collectionIds]="action === 'add' ? addCollectionIds : null"
[type]="action === 'add' ? (addType ? addType : type) : null" [cipherId]="(action === 'edit' || action === 'clone') ? cipherId : null"
(onSavedCipher)="savedCipher($event)" (onDeletedCipher)="deletedCipher($event)"
(onEditAttachments)="editCipherAttachments($event)" (onCancelled)="cancelledAddEdit($event)"
(onShareCipher)="shareCipher($event)" (onEditCollections)="cipherCollections($event)"
(onGeneratePassword)="openPasswordGenerator(true)">
</app-vault-add-edit>
<div id="logo" class="logo" *ngIf="action !== 'add' && action !== 'edit' && action !== 'view' && action !== 'clone'">
<div class="content">
<div class="inner-content">
<img class="logo-image" alt="Bitwarden" aria-hidden="true" />
</div>
</div>
<div id="vault" class="vault" attr.aria-hidden="{{ showingModal }}">
<app-vault-groupings
id="groupings"
class="groupings"
(onAllClicked)="clearGroupingFilters()"
(onFavoritesClicked)="filterFavorites()"
(onCipherTypeClicked)="filterCipherType($event)"
(onFolderClicked)="filterFolder($event.id)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event.id)"
(onCollectionClicked)="filterCollection($event.id)"
(onTrashClicked)="filterDeleted()"
>
</app-vault-groupings>
<app-vault-ciphers
id="items"
class="items"
[activeCipherId]="cipherId"
(onCipherClicked)="viewCipher($event)"
(onCipherRightClicked)="viewCipherMenu($event)"
(onAddCipher)="addCipher($event)"
(onAddCipherOptions)="addCipherOptions()"
>
</app-vault-ciphers>
<app-vault-view
id="details"
class="details"
*ngIf="cipherId && action === 'view'"
[cipherId]="cipherId"
(onCloneCipher)="cloneCipherWithoutPasswordPrompt($event)"
(onEditCipher)="editCipherWithoutPasswordPrompt($event)"
(onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)"
(onRestoredCipher)="restoredCipher($event)"
(onDeletedCipher)="deletedCipher($event)"
>
</app-vault-view>
<app-vault-add-edit
id="addEdit"
class="details"
*ngIf="action === 'add' || action === 'edit' || action === 'clone'"
[cloneMode]="action === 'clone'"
[folderId]="action === 'add' && folderId !== 'none' ? folderId : null"
[organizationId]="action === 'add' ? addOrganizationId : null"
[collectionIds]="action === 'add' ? addCollectionIds : null"
[type]="action === 'add' ? (addType ? addType : type) : null"
[cipherId]="action === 'edit' || action === 'clone' ? cipherId : null"
(onSavedCipher)="savedCipher($event)"
(onDeletedCipher)="deletedCipher($event)"
(onEditAttachments)="editCipherAttachments($event)"
(onCancelled)="cancelledAddEdit($event)"
(onShareCipher)="shareCipher($event)"
(onEditCollections)="cipherCollections($event)"
(onGeneratePassword)="openPasswordGenerator(true)"
>
</app-vault-add-edit>
<div
id="logo"
class="logo"
*ngIf="action !== 'add' && action !== 'edit' && action !== 'view' && action !== 'clone'"
>
<div class="content">
<div class="inner-content">
<img class="logo-image" alt="Bitwarden" aria-hidden="true" />
</div>
</div>
</div>
</div>
<ng-template #passwordGenerator></ng-template>
<ng-template #attachments></ng-template>

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +1,66 @@
<div class="box" >
<div class="box-header">
{{'customFields' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let field of cipher.fields">
<div class="row-main">
<span class="row-label">{{field.name}}</span>
<div *ngIf="field.type === fieldType.Text">
{{field.value || '&nbsp;'}}
</div>
<div *ngIf="field.type === fieldType.Hidden">
<span *ngIf="field.showValue" class="monospaced show-whitespace">{{field.value}}</span>
<span *ngIf="!field.showValue" class="monospaced">{{field.maskedValue}}</span>
</div>
<div *ngIf="field.type === fieldType.Boolean">
<i class="fa fa-check-square-o" *ngIf="field.value === 'true'" aria-hidden="true"></i>
<i class="fa fa-square-o" *ngIf="field.value !== 'true'" aria-hidden="true"></i>
<span class="sr-only">{{field.value}}</span>
</div>
<div *ngIf="field.type === fieldType.Linked" class="box-content-row-flex">
<div class="icon icon-small">
<i class="fa fa-link" aria-hidden="true" appA11yTitle="{{'linkedValue' | i18n}}"></i>
<span class="sr-only">{{'linkedValue' | i18n}}</span>
</div>
<span>{{cipher.linkedFieldI18nKey(field.linkedId) | i18n}}</span>
</div>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
*ngIf="field.type === fieldType.Hidden && cipher.viewPassword"
(click)="toggleFieldValue(field)" role="button">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !field.showValue, 'fa-eye-slash': field.showValue}"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyValue' | i18n}}"
*ngIf="field.value && field.type !== fieldType.Boolean && field.type !== fieldType.Linked &&
!(field.type === fieldType.Hidden && !cipher.viewPassword)"
(click)="copy(field.value, 'value', field.type === fieldType.Hidden ? 'H_Field' : 'Field')"
role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
<div class="box">
<div class="box-header">
{{ "customFields" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let field of cipher.fields">
<div class="row-main">
<span class="row-label">{{ field.name }}</span>
<div *ngIf="field.type === fieldType.Text">
{{ field.value || "&nbsp;" }}
</div>
<div *ngIf="field.type === fieldType.Hidden">
<span *ngIf="field.showValue" class="monospaced show-whitespace">{{ field.value }}</span>
<span *ngIf="!field.showValue" class="monospaced">{{ field.maskedValue }}</span>
</div>
<div *ngIf="field.type === fieldType.Boolean">
<i class="fa fa-check-square-o" *ngIf="field.value === 'true'" aria-hidden="true"></i>
<i class="fa fa-square-o" *ngIf="field.value !== 'true'" aria-hidden="true"></i>
<span class="sr-only">{{ field.value }}</span>
</div>
<div *ngIf="field.type === fieldType.Linked" class="box-content-row-flex">
<div class="icon icon-small">
<i class="fa fa-link" aria-hidden="true" appA11yTitle="{{ 'linkedValue' | i18n }}"></i>
<span class="sr-only">{{ "linkedValue" | i18n }}</span>
</div>
<span>{{ cipher.linkedFieldI18nKey(field.linkedId) | i18n }}</span>
</div>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
*ngIf="field.type === fieldType.Hidden && cipher.viewPassword"
(click)="toggleFieldValue(field)"
role="button"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !field.showValue, 'fa-eye-slash': field.showValue }"
></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyValue' | i18n }}"
*ngIf="
field.value &&
field.type !== fieldType.Boolean &&
field.type !== fieldType.Linked &&
!(field.type === fieldType.Hidden && !cipher.viewPassword)
"
(click)="
copy(field.value, 'value', field.type === fieldType.Hidden ? 'H_Field' : 'Field')
"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
</div>

View File

@ -1,19 +1,15 @@
import {
Component,
} from '@angular/core';
import { Component } from "@angular/core";
import { EventService } from 'jslib-common/abstractions/event.service';
import { EventService } from "jslib-common/abstractions/event.service";
import {
ViewCustomFieldsComponent as BaseViewCustomFieldsComponent
} from 'jslib-angular/components/view-custom-fields.component';
import { ViewCustomFieldsComponent as BaseViewCustomFieldsComponent } from "jslib-angular/components/view-custom-fields.component";
@Component({
selector: 'app-vault-view-custom-fields',
templateUrl: 'view-custom-fields.component.html',
selector: "app-vault-view-custom-fields",
templateUrl: "view-custom-fields.component.html",
})
export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent {
constructor(eventService: EventService) {
super(eventService);
}
constructor(eventService: EventService) {
super(eventService);
}
}

View File

@ -1,265 +1,422 @@
<div class="content">
<div class="inner-content" *ngIf="cipher">
<div class="box">
<div class="box-header">
{{'itemInformation' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row">
<span class="row-label">{{'name' | i18n}}</span>
{{cipher.name}}
</div>
<!-- Login -->
<div *ngIf="cipher.login">
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.username">
<div class="row-main">
<span class="row-label draggable" draggable="true"
(dragstart)="setTextDataOnDrag($event, cipher.login.username)">{{'username' | i18n}}</span>
{{cipher.login.username}}
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyUsername' | i18n}}"
(click)="copy(cipher.login.username, 'username', 'Username')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</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 *ngIf="!showPassword" class="monospaced">
{{cipher.login.maskedPassword}}</div>
<div *ngIf="showPassword" class="monospaced password-wrapper" appSelectCopy
[innerHTML]="cipher.login.password | colorPassword"></div>
</div>
<div class="action-buttons" *ngIf=cipher.viewPassword>
<button type="button" #checkPasswordBtn class="row-btn btn" appBlurClick
appA11yTitle="{{'checkPassword' | i18n}}" (click)="checkPassword()"
[appApiAction]="checkPasswordPromise" [disabled]="checkPasswordBtn.loading">
<i class="fa fa-lg fa-check-circle" [hidden]="checkPasswordBtn.loading"
aria-hidden="true"></i>
<i class="fa fa-lg fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading"
aria-hidden="true"></i>
</button>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="togglePassword()" role="button">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}"
(click)="copy(cipher.login.password, 'password', 'Password')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</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">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyValue' | i18n}}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</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 *ngIf="!showCardNumber" class="monospaced">{{cipher.card.maskedNumber}}</span>
<span *ngIf="showCardNumber" class="monospaced">{{cipher.card.number}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="toggleCardNumber()" role="button">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber}"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyNumber' | i18n}}"
(click)="copy(cipher.card.number, 'number', 'Card Number')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</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 *ngIf="!showCardCode" class="monospaced">{{cipher.card.maskedCode}}</span>
<span *ngIf="showCardCode" class="monospaced">{{cipher.card.code}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="toggleCardCode()" role="button">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showCardCode, 'fa-eye-slash': showCardCode}"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copySecurityCode' | i18n}}"
(click)="copy(cipher.card.code, 'securityCode', 'Security Code')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</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 class="inner-content" *ngIf="cipher">
<div class="box">
<div class="box-header">
{{ "itemInformation" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row">
<span class="row-label">{{ "name" | i18n }}</span>
{{ cipher.name }}
</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">
<span class="row-label" *ngIf="!u.isWebsite">{{'uri' | i18n}}</span>
<span class="row-label" *ngIf="u.isWebsite">{{'website' | i18n}}</span>
<span title="{{u.uri}}">{{u.hostOrUri}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'launch' | i18n}}" *ngIf="u.canLaunch"
(click)="launch(u)" role="button">
<i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyUri' | i18n}}"
(click)="copy(u.uri, u.isWebsite ? 'website' : 'uri', 'URI')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<!-- Login -->
<div *ngIf="cipher.login">
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.username">
<div class="row-main">
<span
class="row-label draggable"
draggable="true"
(dragstart)="setTextDataOnDrag($event, cipher.login.username)"
>{{ "username" | i18n }}</span
>
{{ cipher.login.username }}
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyUsername' | i18n }}"
(click)="copy(cipher.login.username, 'username', 'Username')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</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 *ngIf="!showPassword" class="monospaced">
{{ cipher.login.maskedPassword }}
</div>
<div
*ngIf="showPassword"
class="monospaced password-wrapper"
appSelectCopy
[innerHTML]="cipher.login.password | colorPassword"
></div>
</div>
<div class="action-buttons" *ngIf="cipher.viewPassword">
<button
type="button"
#checkPasswordBtn
class="row-btn btn"
appBlurClick
appA11yTitle="{{ 'checkPassword' | i18n }}"
(click)="checkPassword()"
[appApiAction]="checkPasswordPromise"
[disabled]="checkPasswordBtn.loading"
>
<i
class="fa fa-lg fa-check-circle"
[hidden]="checkPasswordBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-lg fa-spinner fa-spin"
[hidden]="!checkPasswordBtn.loading"
aria-hidden="true"
></i>
</button>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
role="button"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(cipher.login.password, 'password', 'Password')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</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">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.notes">
<div class="box-header">
{{'notes' | i18n}}
<!-- 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 *ngIf="!showCardNumber" class="monospaced">{{ cipher.card.maskedNumber }}</span>
<span *ngIf="showCardNumber" class="monospaced">{{ cipher.card.number }}</span>
</div>
<div class="box-content">
<div class="box-content-row pre-wrap">{{cipher.notes}}</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleCardNumber()"
role="button"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber }"
></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyNumber' | i18n }}"
(click)="copy(cipher.card.number, 'number', 'Card Number')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</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 *ngIf="!showCardCode" class="monospaced">{{ cipher.card.maskedCode }}</span>
<span *ngIf="showCardCode" class="monospaced">{{ cipher.card.code }}</span>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleCardCode()"
role="button"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showCardCode, 'fa-eye-slash': showCardCode }"
></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copySecurityCode' | i18n }}"
(click)="copy(cipher.card.code, 'securityCode', 'Security Code')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<app-vault-view-custom-fields *ngIf="cipher.hasFields" [cipher]="cipher"
[promptPassword]="promptPassword.bind(this)" [copy]="copy.bind(this)">
</app-vault-view-custom-fields>
<div class="box" *ngIf="cipher.hasAttachments && (canAccessPremium || cipher.organizationId)">
<div class="box-header">
{{'attachments' | i18n}}
</div>
<div class="box-content">
<a class="box-content-row box-content-row-flex text-default"
*ngFor="let attachment of cipher.attachments" href="#" appStopClick appBlurCLick
(click)="downloadAttachment(attachment)">
<span class="row-main">{{attachment.fileName}}</span>
<small class="row-sub-label">{{attachment.sizeName}}</small>
<i class="fa fa-download fa-fw row-sub-icon" *ngIf="!attachment.downloading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-fw fa-spin row-sub-icon" *ngIf="attachment.downloading"
aria-hidden="true"></i>
</a>
</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 href="#" (click)="viewHistory()" appStopClick role="button"
appA11yTitle="{{'passwordHistory' | i18n}}, {{cipher.passwordHistory.length}}">
<span aria-hidden="true">{{cipher.passwordHistory.length}}</span>
</a>
</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">
<span class="row-label" *ngIf="!u.isWebsite">{{ "uri" | i18n }}</span>
<span class="row-label" *ngIf="u.isWebsite">{{ "website" | i18n }}</span>
<span title="{{ u.uri }}">{{ u.hostOrUri }}</span>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'launch' | i18n }}"
*ngIf="u.canLaunch"
(click)="launch(u)"
role="button"
>
<i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyUri' | i18n }}"
(click)="copy(u.uri, u.isWebsite ? 'website' : 'uri', 'URI')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.notes">
<div class="box-header">
{{ "notes" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row pre-wrap">{{ cipher.notes }}</div>
</div>
</div>
<app-vault-view-custom-fields
*ngIf="cipher.hasFields"
[cipher]="cipher"
[promptPassword]="promptPassword.bind(this)"
[copy]="copy.bind(this)"
>
</app-vault-view-custom-fields>
<div class="box" *ngIf="cipher.hasAttachments && (canAccessPremium || cipher.organizationId)">
<div class="box-header">
{{ "attachments" | i18n }}
</div>
<div class="box-content">
<a
class="box-content-row box-content-row-flex text-default"
*ngFor="let attachment of cipher.attachments"
href="#"
appStopClick
appBlurCLick
(click)="downloadAttachment(attachment)"
>
<span class="row-main">{{ attachment.fileName }}</span>
<small class="row-sub-label">{{ attachment.sizeName }}</small>
<i
class="fa fa-download fa-fw row-sub-icon"
*ngIf="!attachment.downloading"
aria-hidden="true"
></i>
<i
class="fa fa-spinner fa-fw fa-spin row-sub-icon"
*ngIf="attachment.downloading"
aria-hidden="true"
></i>
</a>
</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
href="#"
(click)="viewHistory()"
appStopClick
role="button"
appA11yTitle="{{ 'passwordHistory' | i18n }}, {{ cipher.passwordHistory.length }}"
>
<span aria-hidden="true">{{ cipher.passwordHistory.length }}</span>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="footer" *ngIf="cipher">
<button appBlurClick class="primary" (click)="edit()" appA11yTitle="{{'edit' | i18n}}" *ngIf="!cipher.isDeleted">
<i class="fa fa-pencil fa-fw fa-lg" aria-hidden="true"></i>
<button
appBlurClick
class="primary"
(click)="edit()"
appA11yTitle="{{ 'edit' | i18n }}"
*ngIf="!cipher.isDeleted"
>
<i class="fa fa-pencil fa-fw fa-lg" aria-hidden="true"></i>
</button>
<button
appBlurClick
class="primary"
(click)="restore()"
appA11yTitle="{{ 'restore' | i18n }}"
*ngIf="cipher.isDeleted"
>
<i class="fa fa-undo fa-fw fa-lg" aria-hidden="true"></i>
</button>
<button
appBlurClick
class="primary"
*ngIf="!cipher?.organizationId && !cipher.isDeleted"
(click)="clone()"
appA11yTitle="{{ 'clone' | i18n }}"
>
<i class="fa fa-files-o fa-fw fa-lg" aria-hidden="true"></i>
</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="delete()"
class="danger"
appA11yTitle="{{ (cipher.isDeleted ? 'permanentlyDelete' : 'delete') | i18n }}"
>
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button appBlurClick class="primary" (click)="restore()" appA11yTitle="{{'restore' | i18n}}"
*ngIf="cipher.isDeleted">
<i class="fa fa-undo fa-fw fa-lg" aria-hidden="true"></i>
</button>
<button appBlurClick class="primary" *ngIf="!cipher?.organizationId && !cipher.isDeleted" (click)="clone()"
appA11yTitle="{{'clone' | i18n}}">
<i class="fa fa-files-o fa-fw fa-lg" aria-hidden="true"></i>
</button>
<div class="right">
<button appBlurClick type="button" (click)="delete()" class="danger"
appA11yTitle="{{(cipher.isDeleted ? 'permanentlyDelete' : 'delete') | i18n}}">
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
</button>
</div>
</div>
</div>

View File

@ -1,92 +1,117 @@
import {
ChangeDetectorRef,
Component,
EventEmitter,
NgZone,
OnChanges,
Output,
} from '@angular/core';
ChangeDetectorRef,
Component,
EventEmitter,
NgZone,
OnChanges,
Output,
} from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuditService } from 'jslib-common/abstractions/audit.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { TotpService } from 'jslib-common/abstractions/totp.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuditService } from "jslib-common/abstractions/audit.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { TotpService } from "jslib-common/abstractions/totp.service";
import { ViewComponent as BaseViewComponent } from 'jslib-angular/components/view.component';
import { ViewComponent as BaseViewComponent } from "jslib-angular/components/view.component";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
const BroadcasterSubscriptionId = 'ViewComponent';
const BroadcasterSubscriptionId = "ViewComponent";
@Component({
selector: 'app-vault-view',
templateUrl: 'view.component.html',
selector: "app-vault-view",
templateUrl: "view.component.html",
})
export class ViewComponent extends BaseViewComponent implements OnChanges {
@Output() onViewCipherPasswordHistory = new EventEmitter<CipherView>();
@Output() onViewCipherPasswordHistory = new EventEmitter<CipherView>();
constructor(cipherService: CipherService, totpService: TotpService,
tokenService: TokenService, i18nService: I18nService,
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
auditService: AuditService, broadcasterService: BroadcasterService,
ngZone: NgZone, changeDetectorRef: ChangeDetectorRef,
eventService: EventService, apiService: ApiService,
private messagingService: MessagingService, passwordRepromptService: PasswordRepromptService,
logService: LogService, stateService: StateService) {
super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService,
auditService, window, broadcasterService, ngZone, changeDetectorRef, eventService,
apiService, passwordRepromptService, logService, stateService);
}
ngOnInit() {
super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
super.ngOnDestroy();
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async ngOnChanges() {
await super.load();
}
viewHistory() {
this.onViewCipherPasswordHistory.emit(this.cipher);
}
async copy(value: string, typeI18nKey: string, aType: string) {
super.copy(value, typeI18nKey, aType);
this.messagingService.send('minimizeOnCopy');
}
onWindowHidden() {
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach(field => {
field.showValue = false;
});
constructor(
cipherService: CipherService,
totpService: TotpService,
tokenService: TokenService,
i18nService: I18nService,
cryptoService: CryptoService,
platformUtilsService: PlatformUtilsService,
auditService: AuditService,
broadcasterService: BroadcasterService,
ngZone: NgZone,
changeDetectorRef: ChangeDetectorRef,
eventService: EventService,
apiService: ApiService,
private messagingService: MessagingService,
passwordRepromptService: PasswordRepromptService,
logService: LogService,
stateService: StateService
) {
super(
cipherService,
totpService,
tokenService,
i18nService,
cryptoService,
platformUtilsService,
auditService,
window,
broadcasterService,
ngZone,
changeDetectorRef,
eventService,
apiService,
passwordRepromptService,
logService,
stateService
);
}
ngOnInit() {
super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
super.ngOnDestroy();
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async ngOnChanges() {
await super.load();
}
viewHistory() {
this.onViewCipherPasswordHistory.emit(this.cipher);
}
async copy(value: string, typeI18nKey: string, aType: string) {
super.copy(value, typeI18nKey, aType);
this.messagingService.send("minimizeOnCopy");
}
onWindowHidden() {
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach((field) => {
field.showValue = false;
});
}
}
}

View File

@ -1,4 +1,4 @@
import { NativeMessagingProxy } from './proxy/native-messaging-proxy';
import { NativeMessagingProxy } from "./proxy/native-messaging-proxy";
// We need to import the other dependencies using `require` since `import` will
// generate `Error: Cannot find module 'electron'`. The cause of this error is
@ -6,28 +6,30 @@ import { NativeMessagingProxy } from './proxy/native-messaging-proxy';
// which removes the electron module. This flag is needed for stdin/out to work
// properly on Windows.
if (process.argv.some(arg => arg.indexOf('chrome-extension://') !== -1 || arg.indexOf('{') !== -1)) {
if (process.platform === 'darwin') {
// tslint:disable-next-line
const app = require('electron').app;
app.on('ready', () => {
app.dock.hide();
});
}
process.stdout.on('error', e => {
if (e.code === 'EPIPE') {
process.exit(0);
}
});
const proxy = new NativeMessagingProxy();
proxy.run();
} else {
if (
process.argv.some((arg) => arg.indexOf("chrome-extension://") !== -1 || arg.indexOf("{") !== -1)
) {
if (process.platform === "darwin") {
// tslint:disable-next-line
const Main = require('./main').Main;
const app = require("electron").app;
const main = new Main();
main.bootstrap();
app.on("ready", () => {
app.dock.hide();
});
}
process.stdout.on("error", (e) => {
if (e.code === "EPIPE") {
process.exit(0);
}
});
const proxy = new NativeMessagingProxy();
proxy.run();
} else {
// tslint:disable-next-line
const Main = require("./main").Main;
const main = new Main();
main.bootstrap();
}

2
src/global.d.ts vendored
View File

@ -1 +1 @@
declare module 'forcefocus';
declare module "forcefocus";

View File

@ -1,16 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline';
img-src 'self' data: *; child-src *; frame-src *; connect-src *;">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'self' 'unsafe-inline';
img-src 'self' data: *; child-src *; frame-src *; connect-src *;"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bitwarden</title>
<base href="">
</head>
<body class="layout_frontend">
<base href="" />
</head>
<body class="layout_frontend">
<app-root>
<div id="loading"><i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i></div>
<div id="loading"><i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i></div>
</app-root>
</body>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More