1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-22 11:45:59 +01:00

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: attributes:
value: | value: |
Thanks for taking the time to fill out this bug report! 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. 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 - type: textarea
id: reproduce id: reproduce

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@ jobs:
fi fi
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Get Package Version - name: Get Package Version
id: retrieve-version id: retrieve-version
@ -58,34 +58,34 @@ jobs:
branch: ${{ steps.branch.outputs.branch-name }} branch: ${{ steps.branch.outputs.branch-name }}
- name: Create release - name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5 uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
env: env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }} PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
with: with:
artifacts: "Bitwarden-${{ env.PKG_VERSION }}-amd64.deb, artifacts: "Bitwarden-${{ env.PKG_VERSION }}-amd64.deb,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm, Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm,
Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd, Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd,
bitwarden_${{ env.PKG_VERSION }}_amd64.snap, bitwarden_${{ env.PKG_VERSION }}_amd64.snap,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage, Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage,
latest-linux.yml, latest-linux.yml,
Bitwarden-Portable-${{ env.PKG_VERSION }}.exe, Bitwarden-Portable-${{ env.PKG_VERSION }}.exe,
Bitwarden-Installer-${{ env.PKG_VERSION }}.exe, Bitwarden-Installer-${{ env.PKG_VERSION }}.exe,
Bitwarden-${{ env.PKG_VERSION }}-ia32-store.appx, Bitwarden-${{ env.PKG_VERSION }}-ia32-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.appx, Bitwarden-${{ env.PKG_VERSION }}-ia32.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.nsis.7z, Bitwarden-${{ env.PKG_VERSION }}-ia32.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-x64-store.appx, Bitwarden-${{ env.PKG_VERSION }}-x64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.appx, Bitwarden-${{ env.PKG_VERSION }}-x64.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.nsis.7z, Bitwarden-${{ env.PKG_VERSION }}-x64.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-arm64-store.appx, Bitwarden-${{ env.PKG_VERSION }}-arm64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.appx, Bitwarden-${{ env.PKG_VERSION }}-arm64.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.nsis.7z, Bitwarden-${{ env.PKG_VERSION }}-arm64.nsis.7z,
bitwarden.${{ env.PKG_VERSION }}.nupkg, bitwarden.${{ env.PKG_VERSION }}.nupkg,
latest.yml, latest.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal-mac.zip, Bitwarden-${{ env.PKG_VERSION }}-universal-mac.zip,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg, Bitwarden-${{ env.PKG_VERSION }}-universal.dmg,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg.blockmap, Bitwarden-${{ env.PKG_VERSION }}-universal.dmg.blockmap,
latest-mac.yml, latest-mac.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal.pkg" Bitwarden-${{ env.PKG_VERSION }}-universal.pkg"
commit: ${{ github.sha }} commit: ${{ github.sha }}
tag: v${{ env.PKG_VERSION }} tag: v${{ env.PKG_VERSION }}
name: Version ${{ env.PKG_VERSION }} name: Version ${{ env.PKG_VERSION }}
@ -93,7 +93,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
draft: true draft: true
snap: snap:
name: Deploy Snap name: Deploy Snap
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@ -102,10 +101,10 @@ jobs:
_PKG_VERSION: ${{ needs.setup.outputs.package_version }} _PKG_VERSION: ${{ needs.setup.outputs.package_version }}
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Install Snap - name: Install Snap
uses: samuelmeuli/action-snapcraft@10d7d0a84d9d86098b19f872257df314b0bd8e2d # v1.2.0 uses: samuelmeuli/action-snapcraft@10d7d0a84d9d86098b19f872257df314b0bd8e2d # v1.2.0
with: with:
snapcraft_token: ${{ secrets.SNAP_TOKEN }} snapcraft_token: ${{ secrets.SNAP_TOKEN }}
@ -129,7 +128,6 @@ jobs:
snapcraft upload dist/bitwarden_${{ env._PKG_VERSION }}_amd64.snap --release stable snapcraft upload dist/bitwarden_${{ env._PKG_VERSION }}_amd64.snap --release stable
snapcraft logout snapcraft logout
choco: choco:
name: Deploy Choco name: Deploy Choco
runs-on: windows-2019 runs-on: windows-2019
@ -138,7 +136,7 @@ jobs:
_PKG_VERSION: ${{ needs.setup.outputs.package_version }} _PKG_VERSION: ${{ needs.setup.outputs.package_version }}
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Setup Chocolatey - name: Setup Chocolatey
run: choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/ 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": { "windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" "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: 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 - **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
* **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)
* **Report a bug or submit a bugfix:** Use Github issues and pull requests - **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
* **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 ## Contributor Agreement
@ -24,9 +19,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/deskt
## Pull Request Guidelines ## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request - use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch - commit any pull requests against the `master` branch
* include a link to your Community Forums post - include a link to your Community Forums post
# Localization (l10n) # 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 - [Node.js](https://nodejs.org) v16.13.1 (LTS) or greater
- NPM v8 - 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** **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 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 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 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 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. 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 - 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. 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 - 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 - 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 degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder. account holder.

View File

@ -27,7 +27,7 @@
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib", "symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
"symlink:mac": "npm run symlink:lin", "symlink:mac": "npm run symlink:lin",
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib", "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", "lint:fix": "tslint 'src/**/*.ts' --fix",
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"", "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\"", "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(); require("dotenv").config();
const path = require('path'); const path = require("path");
const fse = require('fs-extra'); const fse = require("fs-extra");
const { notarize } = require('electron-notarize'); const { notarize } = require("electron-notarize");
const { deepAssign } = require('builder-util'); const { deepAssign } = require("builder-util");
exports.default = run; exports.default = run;
async function run(context) { async function run(context) {
console.log('## After sign'); console.log("## After sign");
// console.log(context); // console.log(context);
const appName = context.packager.appInfo.productFilename; const appName = context.packager.appInfo.productFilename;
const appPath = `${context.appOutDir}/${appName}.app`; const appPath = `${context.appOutDir}/${appName}.app`;
const macBuild = context.electronPlatformName === 'darwin'; const macBuild = context.electronPlatformName === "darwin";
const copyPlugIn = ['darwin', 'mas'].includes(context.electronPlatformName); const copyPlugIn = ["darwin", "mas"].includes(context.electronPlatformName);
if (copyPlugIn) { if (copyPlugIn) {
// Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552 // Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552
const plugIn = path.join(__dirname, '../PlugIns'); const plugIn = path.join(__dirname, "../PlugIns");
if (fse.existsSync(plugIn)) { if (fse.existsSync(plugIn)) {
fse.mkdirSync(path.join(appPath, 'Contents/PlugIns')); fse.mkdirSync(path.join(appPath, "Contents/PlugIns"));
fse.copySync(path.join(plugIn, 'safari.appex'), path.join(appPath, 'Contents/PlugIns/safari.appex')); fse.copySync(
path.join(plugIn, "safari.appex"),
path.join(appPath, "Contents/PlugIns/safari.appex")
);
// Resign to sign safari extension // Resign to sign safari extension
if (context.electronPlatformName === 'mas') { if (context.electronPlatformName === "mas") {
const masBuildOptions = deepAssign({}, context.packager.platformSpecificBuildOptions, context.packager.config.mas); const masBuildOptions = deepAssign(
if (context.targets.some(e => e.name === 'mas-dev')) { {},
deepAssign(masBuildOptions, { context.packager.platformSpecificBuildOptions,
type: 'development', context.packager.config.mas
}); );
} if (context.targets.some((e) => e.name === "mas-dev")) {
if (context.packager.packagerOptions.prepackaged == null) { deepAssign(masBuildOptions, {
await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch); type: "development",
} });
} else {
await context.packager.signApp(context, true);
}
} }
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) { if (macBuild) {
console.log('### Notarizing ' + appPath); console.log("### Notarizing " + appPath);
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID; const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`; const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
return await notarize({ return await notarize({
appBundleId: 'com.bitwarden.desktop', appBundleId: "com.bitwarden.desktop",
appPath: appPath, appPath: appPath,
appleId: appleId, appleId: appleId,
appleIdPassword: appleIdPassword, appleIdPassword: appleIdPassword,
}); });
} }
} }

29
sign.js
View File

@ -1,22 +1,19 @@
exports.default = async function(configuration) { exports.default = async function (configuration) {
if ( if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") {
parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && console.log(`[*] Signing file: ${configuration.path}`);
configuration.path.slice(-4) == ".exe"
) {
console.log(`[*] Signing file: ${configuration.path}`)
require("child_process").execSync( require("child_process").execSync(
`azuresigntool sign -v ` + `azuresigntool sign -v ` +
`-kvu ${process.env.SIGNING_VAULT_URL} ` + `-kvu ${process.env.SIGNING_VAULT_URL} ` +
`-kvi ${process.env.SIGNING_CLIENT_ID} ` + `-kvi ${process.env.SIGNING_CLIENT_ID} ` +
`-kvt ${process.env.SIGNING_TENANT_ID} ` + `-kvt ${process.env.SIGNING_TENANT_ID} ` +
`-kvs ${process.env.SIGNING_CLIENT_SECRET} ` + `-kvs ${process.env.SIGNING_CLIENT_SECRET} ` +
`-kvc ${process.env.SIGNING_CERT_NAME} ` + `-kvc ${process.env.SIGNING_CERT_NAME} ` +
`-fd ${configuration.hash} ` + `-fd ${configuration.hash} ` +
`-du ${configuration.site} ` + `-du ${configuration.site} ` +
`-tr http://timestamp.digicert.com ` + `-tr http://timestamp.digicert.com ` +
`${configuration.path}`, `${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 fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<form class="modal-content" (ngSubmit)="submit()"> <form class="modal-content" (ngSubmit)="submit()">
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
{{'selfHostedEnvironment' | i18n}} {{ "selfHostedEnvironment" | i18n }}
</div> </div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="baseUrl">{{'baseUrl' | i18n}}</label> <label for="baseUrl">{{ "baseUrl" | i18n }}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl" <input
placeholder="{{'ex' | i18n}} https://bitwarden.company.com" appInputVerbatim> id="baseUrl"
</div> type="text"
</div> name="BaseUrl"
<div class="box-footer"> [(ngModel)]="baseUrl"
{{'selfHostedEnvironmentFooter' | i18n}} placeholder="{{ 'ex' | i18n }} https://bitwarden.company.com"
</div> appInputVerbatim
</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> </div>
<div class="modal-footer"> </div>
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}"> <div class="box-footer">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i> {{ "selfHostedEnvironmentFooter" | i18n }}
</button> </div>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> </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>
</form> <div class="box-content-row" appBoxRow>
</div> <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> </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 { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({ @Component({
selector: 'app-environment', selector: "app-environment",
templateUrl: 'environment.component.html', templateUrl: "environment.component.html",
}) })
export class EnvironmentComponent extends BaseEnvironmentComponent { export class EnvironmentComponent extends BaseEnvironmentComponent {
constructor(platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, constructor(
i18nService: I18nService) { platformUtilsService: PlatformUtilsService,
super(platformUtilsService, environmentService, i18nService); environmentService: EnvironmentService,
} i18nService: I18nService
) {
super(platformUtilsService, environmentService, i18nService);
}
} }

View File

@ -1,24 +1,31 @@
<form id="hint-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form id="hint-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content"> <div class="content">
<h1>{{'passwordHint' | i18n}}</h1> <h1>{{ "passwordHint" | i18n }}</h1>
<div class="box last"> <div class="box last">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label> <label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required appAutofocus <input
appInputVerbatim> id="email"
</div> type="text"
</div> name="Email"
<div class="box-footer"> [(ngModel)]="email"
{{'enterEmailToGetHint' | i18n}} required
</div> appAutofocus
</div> appInputVerbatim
<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>
<div class="box-footer">
{{ "enterEmailToGetHint" | i18n }}
</div>
</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> </form>

View File

@ -1,20 +1,25 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({ @Component({
selector: 'app-hint', selector: "app-hint",
templateUrl: 'hint.component.html', templateUrl: "hint.component.html",
}) })
export class HintComponent extends BaseHintComponent { export class HintComponent extends BaseHintComponent {
constructor(router: Router, platformUtilsService: PlatformUtilsService, constructor(
i18nService: I18nService, apiService: ApiService, logService: LogService) { router: Router,
super(router, i18nService, apiService, platformUtilsService, logService); 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()"> <form id="lock-page" (ngSubmit)="submit()">
<div class="content"> <div class="content">
<p aria-hidden="true"><i class="fa fa-lock fa-4x text-muted"></i></p> <p aria-hidden="true"><i class="fa fa-lock fa-4x text-muted"></i></p>
<p>{{'yourVaultIsLocked' | i18n}}</p> <p>{{ "yourVaultIsLocked" | i18n }}</p>
<div class="box last"> <div class="box last">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput"> <div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock"> <div class="row-main" *ngIf="pinLock">
<label for="pin">{{'pin' | i18n}}</label> <label for="pin">{{ "pin" | i18n }}</label>
<input id="pin" type="{{showPassword ? 'text' : 'password'}}" name="PIN" class="monospaced" <input
[(ngModel)]="pin" required appInputVerbatim> id="pin"
</div> type="{{ showPassword ? 'text' : 'password' }}"
<div class="row-main" *ngIf="!pinLock"> name="PIN"
<label for="masterPassword">{{'masterPass' | i18n}}</label> class="monospaced"
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword" [(ngModel)]="pin"
class="monospaced" [(ngModel)]="masterPassword" required appInputVerbatim> required
</div> appInputVerbatim
<div class="action-buttons"> />
<a class="row-btn" href="#" appStopClick appBlurClick role="button" </div>
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()"> <div class="row-main" *ngIf="!pinLock">
<i class="fa fa-lg" aria-hidden="true" <label for="masterPassword">{{ "masterPass" | i18n }}</label>
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> <input
</a> id="masterPassword"
</div> type="{{ showPassword ? 'text' : 'password' }}"
</div> name="MasterPassword"
</div> class="monospaced"
<div class="box-footer"> [(ngModel)]="masterPassword"
{{'loggedInAsOn' | i18n : email : webVaultHostname}} required
</div> appInputVerbatim
</div> />
<div class="buttons with-rows"> </div>
<div class="buttons-row" *ngIf="supportsBiometric && biometricLock"> <div class="action-buttons">
<button type="button" class="btn block" [ngClass]="{'primary font-weight-bold': hideInput}" <a
appBlurClick (click)="unlockBiometric()"> class="row-btn"
{{biometricText | i18n}} href="#"
</button> appStopClick
</div> appBlurClick
<div class="buttons-row"> role="button"
<button type="submit" class="btn primary block" appBlurClick *ngIf="!hideInput"> appA11yTitle="{{ 'toggleVisibility' | i18n }}"
<i class="fa fa-unlock-alt" aria-hidden="true"></i> <b>{{'unlock' | i18n}}</b> (click)="togglePassword()"
</button> >
<button type="button" class="btn block" appBlurClick (click)="logOut()"> <i
{{'logOut' | i18n}} class="fa fa-lg"
</button> aria-hidden="true"
</div> [ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div> </div>
</div>
<div class="box-footer">
{{ "loggedInAsOn" | i18n: email:webVaultHostname }}
</div>
</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> </form>

View File

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

View File

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

View File

@ -1,112 +1,129 @@
import { import { Component, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
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 { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from 'jslib-common/abstractions/sync.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({ @Component({
selector: 'app-login', selector: "app-login",
templateUrl: 'login.component.html', templateUrl: "login.component.html",
}) })
export class LoginComponent extends BaseLoginComponent implements OnDestroy { 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, constructor(
syncService: SyncService, private modalService: ModalService, authService: AuthService,
platformUtilsService: PlatformUtilsService, stateService: StateService, router: Router,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService, i18nService: I18nService,
cryptoFunctionService: CryptoFunctionService, private broadcasterService: BroadcasterService, syncService: SyncService,
ngZone: NgZone, private messagingService: MessagingService, private modalService: ModalService,
logService: LogService) { platformUtilsService: PlatformUtilsService,
super(authService, router, platformUtilsService, i18nService, stateService, environmentService, stateService: StateService,
passwordGenerationService, cryptoFunctionService, logService, ngZone); environmentService: EnvironmentService,
super.onSuccessfulLogin = () => { passwordGenerationService: PasswordGenerationService,
return syncService.fullSync(true); 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() { async ngOnInit() {
await super.ngOnInit(); await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => { this.ngZone.run(() => {
switch (message.command) { switch (message.command) {
case 'windowHidden': case "windowHidden":
this.onWindowHidden(); this.onWindowHidden();
break; break;
case 'windowIsFocused': case "windowIsFocused":
if (this.deferFocus === null) { if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused; this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) { if (!this.deferFocus) {
this.focusInput(); this.focusInput();
} }
} else if (this.deferFocus && message.windowIsFocused) { } else if (this.deferFocus && message.windowIsFocused) {
this.focusInput(); this.focusInput();
this.deferFocus = false; this.deferFocus = false;
} }
break; break;
default: 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');
} }
});
});
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 fade" role="dialog" aria-modal="true" aria-labelledby="premiumTitle">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="premiumTitle"> <div class="box-header" id="premiumTitle">
{{'premiumMembership' | i18n}} {{ "premiumMembership" | i18n }}
</div> </div>
<div class="box-content box-content-padded"> <div class="box-content box-content-padded">
<div *ngIf="!isPremium"> <div *ngIf="!isPremium">
<p class="text-center lead">{{'premiumNotCurrentMember' | i18n}}</p> <p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p>
<p>{{'premiumSignUpAndGet' | i18n}}</p> <p>{{ "premiumSignUpAndGet" | i18n }}</p>
<ul class="fa-ul"> <ul class="fa-ul">
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpStorage' | i18n}} {{ "premiumSignUpStorage" | i18n }}
</li> </li>
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTwoStep' | i18n}} {{ "premiumSignUpTwoStep" | i18n }}
</li> </li>
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpReports' | i18n}} {{ "premiumSignUpReports" | i18n }}
</li> </li>
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTotp' | i18n}} {{ "premiumSignUpTotp" | i18n }}
</li> </li>
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpSupport' | i18n}} {{ "premiumSignUpSupport" | i18n }}
</li> </li>
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpFuture' | i18n}} {{ "premiumSignUpFuture" | i18n }}
</li> </li>
</ul> </ul>
<p class="text-center lead no-margin"> <p class="text-center lead no-margin">
{{'premiumPrice' | i18n : (price | currency:'$')}} {{ "premiumPrice" | i18n: (price | currency: "$") }}
</p> </p>
</div>
<div *ngIf="isPremium">
<p class="text-center lead">{{'premiumCurrentMember' | i18n}}</p>
<p class="text-center">{{'premiumCurrentMemberThanks' | i18n}}</p>
</div>
</div>
</div>
</div> </div>
<div class="modal-footer"> <div *ngIf="isPremium">
<button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium"> <p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p>
<b>{{'premiumManage' | i18n}}</b> <p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p>
</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> </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>
</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 { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.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({ @Component({
selector: 'app-premium', selector: "app-premium",
templateUrl: 'premium.component.html', templateUrl: "premium.component.html",
}) })
export class PremiumComponent extends BasePremiumComponent { export class PremiumComponent extends BasePremiumComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, constructor(
apiService: ApiService, logService: LogService, i18nService: I18nService,
stateService: StateService) { platformUtilsService: PlatformUtilsService,
super(i18nService, platformUtilsService, apiService, logService, stateService); 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"> <form id="register-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content"> <div class="content">
<h1>{{'createAccount' | i18n}}</h1> <h1>{{ "createAccount" | i18n }}</h1>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label> <label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required <input
[appAutofocus]="email === ''" appInputVerbatim> id="email"
</div> type="text"
<div class="box-content-row" appBoxRow> name="Email"
<div class="box-content-row-flex"> [(ngModel)]="email"
<div class="row-main"> required
<label for="masterPassword"> [appAutofocus]="email === ''"
{{'masterPass' | i18n}} appInputVerbatim
<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> </div>
<div class="box"> <div class="box-content-row" appBoxRow>
<div class="box-content"> <div class="box-content-row-flex">
<div class="box-content-row box-content-row-flex" appBoxRow> <div class="row-main">
<div class="row-main"> <label for="masterPassword">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label> {{ "masterPass" | i18n }}
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}" <strong
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="confirmMasterPassword" required class="sub-label text-{{ masterPasswordScoreColor }}"
appInputVerbatim> *ngIf="masterPasswordScoreText"
</div> >
<div class="action-buttons"> {{ masterPasswordScoreText }}
<a class="row-btn" href="#" appStopClick appBlurClick role="button" </strong>
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)"> </label>
<i class="fa fa-lg" aria-hidden="true" <input
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> id="masterPassword"
</a> type="{{ showPassword ? 'text' : 'password' }}"
</div> name="MasterPassword"
</div> class="monospaced"
<div class="box-content-row" appBoxRow> [(ngModel)]="masterPassword"
<label for="hint">{{'masterPassHint' | i18n}}</label> required
<input id="hint" type="text" name="Hint" [(ngModel)]="hint"> [appAutofocus]="email !== ''"
</div> (input)="updatePasswordStrength()"
<div class="box-content-row" [hidden]="!showCaptcha()"> appInputVerbatim
<iframe id="hcaptcha_iframe" height="80"></iframe> />
</div>
</div> </div>
<div class="box-footer"> <div class="action-buttons">
{{'masterPassHintDesc' | i18n}} <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>
<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 last" *ngIf="showTerms"> </div>
<div class="box-footer checkbox"> <div class="box-footer">
<input type="checkbox" id="acceptPolicies" [(ngModel)]="acceptPolicies" name="AcceptPolicies"> {{ "masterPassDesc" | i18n }}
<label for="acceptPolicies"> </div>
{{'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>
<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> </form>

View File

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

View File

@ -1,18 +1,28 @@
<div id="remove-password-page" *ngIf="!loading"> <div id="remove-password-page" *ngIf="!loading">
<div class="content"> <div class="content">
<h1>{{'removeMasterPassword' | i18n}}</h1> <h1>{{ "removeMasterPassword" | i18n }}</h1>
<p>{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}</p> <p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
<div class="buttons"> <div class="buttons">
<button type="submit" class="btn primary block" [disabled]="actionPromise" appBlurClick <button
(click)="convert()"> type="submit"
<b [hidden]="continuing">{{'removeMasterPassword' | i18n}}</b> class="btn primary block"
<i class="fa fa-spinner fa-spin" [hidden]="!continuing" aria-hidden="true"></i> [disabled]="actionPromise"
</button> appBlurClick
<button type="button" class="btn secondary block" [disabled]="actionPromise" appBlurClick (click)="convert()"
(click)="leave()"> >
<b [hidden]="leaving">{{'leaveOrganization' | i18n}}</b> <b [hidden]="continuing">{{ "removeMasterPassword" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!leaving" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin" [hidden]="!continuing" aria-hidden="true"></i>
</button> </button>
</div> <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>
</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({ @Component({
selector: 'app-remove-password', selector: "app-remove-password",
templateUrl: 'remove-password.component.html', 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> <form id="set-password-page" #form>
<div class="content"> <div class="content">
<img class="logo-image" alt="Bitwarden"> <img class="logo-image" alt="Bitwarden" />
<p class="lead">{{'setMasterPassword' | i18n}}</p> <p class="lead">{{ "setMasterPassword" | i18n }}</p>
<div class="box text-center" *ngIf="syncLoading"> <div class="box text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{'loading' | i18n}} {{ "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> </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> </form>

View File

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

View File

@ -1,195 +1,285 @@
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{'settings' | i18n}}"> <div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body form"> <div class="modal-body form">
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
{{'security' | i18n}} {{ "security" | i18n }}
</div> </div>
<div class="box-content box-content-padded"> <div class="box-content box-content-padded">
<app-vault-timeout-input [vaultTimeouts]="vaultTimeouts" [formControl]="vaultTimeout" ngDefaultControl></app-vault-timeout-input> <app-vault-timeout-input
<div class="form-group"> [vaultTimeouts]="vaultTimeouts"
<label>{{'vaultTimeoutAction' | i18n}}</label> [formControl]="vaultTimeout"
<div class="radio radio-mt-2"> ngDefaultControl
<label for="vaultTimeoutActionLock"> ></app-vault-timeout-input>
<input type="radio" name="VaultTimeoutAction" id="vaultTimeoutActionLock" <div class="form-group">
value="lock" [(ngModel)]="vaultTimeoutAction" <label>{{ "vaultTimeoutAction" | i18n }}</label>
(change)="saveVaultTimeoutOptions()"> <div class="radio radio-mt-2">
{{'lock' | i18n}} <label for="vaultTimeoutActionLock">
</label> <input
</div> type="radio"
<small class="help-block">{{'vaultTimeoutActionLockDesc' | i18n}}</small> name="VaultTimeoutAction"
<div class="radio"> id="vaultTimeoutActionLock"
<label for="vaultTimeoutActionLogOut"> value="lock"
<input type="radio" name="VaultTimeoutAction" id="vaultTimeoutActionLogOut" [(ngModel)]="vaultTimeoutAction"
value="logOut" [(ngModel)]="vaultTimeoutAction" (change)="saveVaultTimeoutOptions()"
(change)="saveVaultTimeoutOptions()"> />
{{'logOut' | i18n}} {{ "lock" | i18n }}
</label> </label>
</div> </div>
<small class="help-block">{{'vaultTimeoutActionLogOutDesc' | i18n}}</small> <small class="help-block">{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
</div> <div class="radio">
<div class="form-group"> <label for="vaultTimeoutActionLogOut">
<div class="checkbox"> <input
<label for="pin"> type="radio"
<input id="pin" type="checkbox" name="PIN" [(ngModel)]="pin" (change)="updatePin()"> name="VaultTimeoutAction"
{{'unlockWithPin' | i18n}} id="vaultTimeoutActionLogOut"
</label> value="logOut"
</div> [(ngModel)]="vaultTimeoutAction"
</div> (change)="saveVaultTimeoutOptions()"
<div class="form-group" *ngIf="supportsBiometric"> />
<div class="checkbox"> {{ "logOut" | i18n }}
<label for="biometric"> </label>
<input id="biometric" type="checkbox" name="biometric" [checked]="biometric" (change)="updateBiometric()"> </div>
{{biometricText | i18n}} <small class="help-block">{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
</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>
<div class="modal-footer"> <div class="form-group">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> <div class="checkbox">
<label for="pin">
<input
id="pin"
type="checkbox"
name="PIN"
[(ngModel)]="pin"
(change)="updatePin()"
/>
{{ "unlockWithPin" | i18n }}
</label>
</div>
</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>
<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>
</div> </div>

View File

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

View File

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

View File

@ -1,40 +1,56 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from 'jslib-common/abstractions/sync.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({ @Component({
selector: 'app-sso', selector: "app-sso",
templateUrl: 'sso.component.html', templateUrl: "sso.component.html",
}) })
export class SsoComponent extends BaseSsoComponent { export class SsoComponent extends BaseSsoComponent {
constructor(authService: AuthService, router: Router, constructor(
i18nService: I18nService, syncService: SyncService, route: ActivatedRoute, authService: AuthService,
stateService: StateService, platformUtilsService: PlatformUtilsService, router: Router,
apiService: ApiService, cryptoFunctionService: CryptoFunctionService, i18nService: I18nService,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService, syncService: SyncService,
logService: LogService) { route: ActivatedRoute,
super(authService, router, i18nService, route, stateService, platformUtilsService, stateService: StateService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService); platformUtilsService: PlatformUtilsService,
super.onSuccessfulLogin = () => { apiService: ApiService,
return syncService.fullSync(true); cryptoFunctionService: CryptoFunctionService,
}; environmentService: EnvironmentService,
this.redirectUri = 'bitwarden://sso-callback'; passwordGenerationService: PasswordGenerationService,
this.clientId = 'desktop'; 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 fade" role="dialog" aria-modal="true" aria-labelledby="twoStepTitle">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="twoStepTitle"> <div class="box-header" id="twoStepTitle">
{{'twoStepOptions' | i18n}} {{ "twoStepOptions" | i18n }}
</div> </div>
<div class="box-content"> <div class="box-content">
<a href="#" appStopClick *ngFor="let p of providers" class="box-content-row" <a
(click)="choose(p)"> href="#"
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right"> appStopClick
<span class="text">{{p.name}}</span> *ngFor="let p of providers"
<span class="detail">{{p.description}}</span> class="box-content-row"
</a> (click)="choose(p)"
<a href="#" appStopClick class="box-content-row" (click)="recover()"> >
<span class="text">{{'recoveryCodeTitle' | i18n}}</span> <img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right" />
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span> <span class="text">{{ p.name }}</span>
</a> <span class="detail">{{ p.description }}</span>
</div> </a>
</div> <a href="#" appStopClick class="box-content-row" (click)="recover()">
</div> <span class="text">{{ "recoveryCodeTitle" | i18n }}</span>
<div class="modal-footer"> <span class="detail">{{ "recoveryCodeDesc" | i18n }}</span>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> </a>
</div> </div>
</div> </div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div> </div>
</div>
</div> </div>

View File

@ -1,21 +1,23 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib-angular/components/two-factor-options.component';
@Component({ @Component({
selector: 'app-two-factor-options', selector: "app-two-factor-options",
templateUrl: 'two-factor-options.component.html', templateUrl: "two-factor-options.component.html",
}) })
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent { export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
constructor(authService: AuthService, router: Router, constructor(
i18nService: I18nService, platformUtilsService: PlatformUtilsService) { authService: AuthService,
super(authService, router, i18nService, platformUtilsService, window); 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}}"> <form
<div class="content"> id="two-factor-page"
<h1>{{title}}</h1> #form
<p *ngIf="selectedProviderType === providerType.Authenticator">{{'enterVerificationCodeApp' | i18n}}</p> (ngSubmit)="submit()"
<p *ngIf="selectedProviderType === providerType.Email"> [appApiAction]="formPromise"
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}} attr.aria-hidden="{{ showingModal }}"
</p> >
<div class="box last" <div class="content">
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator"> <h1>{{ title }}</h1>
<div class="box-content"> <p *ngIf="selectedProviderType === providerType.Authenticator">
<div class="box-content-row" appBoxRow> {{ "enterVerificationCodeApp" | i18n }}
<label for="code">{{'verificationCode' | i18n}}</label> </p>
<input id="code" type="text" name="Code" [(ngModel)]="token" required appAutofocus appInputVerbatim> <p *ngIf="selectedProviderType === providerType.Email">
</div> {{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
<div class="box-content-row box-content-row-checkbox" appBoxRow> </p>
<label for="remember">{{'rememberMe' | i18n}}</label> <div
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember"> class="box last"
</div> *ngIf="
</div> 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>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey"> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<p>{{'insertYubiKey' | i18n}}</p> <label for="remember">{{ "rememberMe" | i18n }}</label>
<img src="../../images/yubikey.jpg" alt=""> <input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
<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>
</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> </form>
<ng-template #twoFactorOptions></ng-template> <ng-template #twoFactorOptions></ng-template>

View File

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

View File

@ -1,80 +1,124 @@
<form id="update-temp-password-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form id="update-temp-password-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content"> <div class="content">
<app-callout type="warning" title="{{'updateMasterPassword' | i18n}}"> <app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{'updateMasterPasswordWarning' | i18n}} {{ "updateMasterPasswordWarning" | i18n }}
</app-callout> </app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions"> <app-callout
</app-callout> type="info"
<div class="box"> [enforcedPolicyOptions]="enforcedPolicyOptions"
<div class="box-content"> *ngIf="enforcedPolicyOptions"
<div class="box-content-row" appBoxRow> >
<div class="box-content-row-flex"> </app-callout>
<div class="row-main"> <div class="box">
<label for="masterPassword"> <div class="box-content">
{{'masterPass' | i18n}} <div class="box-content-row" appBoxRow>
<strong class="sub-label text-{{masterPasswordScoreStyle.Color}}" <div class="box-content-row-flex">
*ngIf="masterPasswordScoreStyle.Text"> <div class="row-main">
{{masterPasswordScoreStyle.Text}} <label for="masterPassword">
</strong> {{ "masterPass" | i18n }}
</label> <strong
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" class="sub-label text-{{ masterPasswordScoreStyle.Color }}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required *ngIf="masterPasswordScoreStyle.Text"
[appAutofocus]="masterPassword === ''" (input)="updatePasswordStrength()" >
appInputVerbatim> {{ masterPasswordScoreStyle.Text }}
</div> </strong>
<div class="action-buttons"> </label>
<a class="row-btn" href="#" appStopClick appBlurClick role="button" <input
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)"> id="masterPassword"
<i class="fa fa-lg" aria-hidden="true" type="{{ showPassword ? 'text' : 'password' }}"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> name="MasterPassword"
</a> class="monospaced"
</div> [(ngModel)]="masterPassword"
</div> required
<div class="progress"> [appAutofocus]="masterPassword === ''"
<div class="progress-bar bg-{{masterPasswordScoreStyle.Color}}" role="progressbar" (input)="updatePasswordStrength()"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" appInputVerbatim
[ngStyle]="{width: (masterPasswordScoreStyle.Width + '%')}" />
attr.aria-valuenow="{{masterPasswordScoreStyle.Width}}"></div>
</div>
</div>
</div> </div>
</div> <div class="action-buttons">
<div class="box"> <a
<div class="box-content"> class="row-btn"
<div class="box-content-row box-content-row-flex" appBoxRow> href="#"
<div class="row-main"> appStopClick
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label> appBlurClick
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}" role="button"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="masterPasswordRetype" required appA11yTitle="{{ 'toggleVisibility' | i18n }}"
appInputVerbatim> (click)="togglePassword(false)"
</div> >
<div class="action-buttons"> <i
<a class="row-btn" href="#" appStopClick appBlurClick role="button" class="fa fa-lg"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)"> aria-hidden="true"
<i class="fa fa-lg" aria-hidden="true" [ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> ></i>
</a> </a>
</div>
</div>
</div> </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>
<div class="box"> </div>
<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> </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 { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from 'jslib-common/abstractions/sync.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 { interface MasterPasswordScore {
Color: string; Color: string;
Text: string; Text: string;
Width: number; Width: number;
} }
@Component({ @Component({
selector: 'app-update-temp-password', selector: "app-update-temp-password",
templateUrl: 'update-temp-password.component.html', templateUrl: "update-temp-password.component.html",
}) })
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent { export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
get masterPasswordScoreStyle(): MasterPasswordScore { get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20; const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) { switch (this.masterPasswordScore) {
case 4: case 4:
return { return {
Color: 'bg-success', Color: "bg-success",
Text: 'strong', Text: "strong",
Width: scoreWidth, Width: scoreWidth,
}; };
case 3: case 3:
return { return {
Color: 'bg-primary', Color: "bg-primary",
Text: 'good', Text: "good",
Width: scoreWidth, Width: scoreWidth,
}; };
case 2: case 2:
return { return {
Color: 'bg-warning', Color: "bg-warning",
Text: 'weak', Text: "weak",
Width: scoreWidth, Width: scoreWidth,
}; };
default: default:
return { return {
Color: 'bg-danger', Color: "bg-danger",
Text: 'weak', Text: "weak",
Width: scoreWidth, 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);
} }
}
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"> <app-callout type="info" *ngIf="vaultTimeoutPolicy">
{{'vaultTimeoutPolicyInEffect' | i18n : vaultTimeoutPolicyHours : vaultTimeoutPolicyMinutes}} {{ "vaultTimeoutPolicyInEffect" | i18n: vaultTimeoutPolicyHours:vaultTimeoutPolicyMinutes }}
</app-callout> </app-callout>
<div [formGroup]="form"> <div [formGroup]="form">
<div class="form-group"> <div class="form-group">
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label> <label for="vaultTimeout">{{ "vaultTimeout" | i18n }}</label>
<select id="vaultTimeout" name="VaultTimeout" formControlName="vaultTimeout" class="form-control" appAutofocus> <select
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option> id="vaultTimeout"
</select> name="VaultTimeout"
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small> 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>
<div class="form-group row" *ngIf="showCustom" formGroupName="custom"> <div class="col">
<div class="col"> <label for="minutes">{{ "minutes" | i18n }}</label>
<label for="hours">{{'hours' | i18n}}</label> <input
<input id="hours" class="form-control" type="number" min="0" name="hours" formControlName="hours"> id="minutes"
</div> class="form-control"
<div class="col"> type="number"
<label for="minutes">{{'minutes' | i18n}}</label> min="0"
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes" formControlName="minutes"> max="59"
</div> name="minutes"
formControlName="minutes"
/>
</div> </div>
<div class="form-group"></div> <!-- Styling fix --> </div>
<div class="form-group"></div>
<!-- Styling fix -->
</div> </div>

View File

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

View File

@ -1,72 +1,71 @@
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { import { RouterModule, Routes } from "@angular/router";
RouterModule,
Routes,
} from '@angular/router';
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service'; import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { LockGuardService } from 'jslib-angular/services/lock-guard.service'; import { LockGuardService } from "jslib-angular/services/lock-guard.service";
import { LoginGuardService } from '../services/loginGuard.service'; import { LoginGuardService } from "../services/loginGuard.service";
import { HintComponent } from './accounts/hint.component'; import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from './accounts/lock.component'; import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from './accounts/login.component'; import { LoginComponent } from "./accounts/login.component";
import { RegisterComponent } from './accounts/register.component'; import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from './accounts/remove-password.component'; import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from './accounts/set-password.component'; import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from './accounts/sso.component'; import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorComponent } from './accounts/two-factor.component'; import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.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 = [ const routes: Routes = [
{ path: '', redirectTo: '/vault', pathMatch: 'full' }, { path: "", redirectTo: "/vault", pathMatch: "full" },
{ {
path: 'lock', path: "lock",
component: LockComponent, component: LockComponent,
canActivate: [LockGuardService], canActivate: [LockGuardService],
}, },
{ {
path: 'login', path: "login",
component: LoginComponent, component: LoginComponent,
canActivate: [LoginGuardService], canActivate: [LoginGuardService],
}, },
{ path: '2fa', component: TwoFactorComponent }, { path: "2fa", component: TwoFactorComponent },
{ path: 'register', component: RegisterComponent }, { path: "register", component: RegisterComponent },
{ {
path: 'vault', path: "vault",
component: VaultComponent, component: VaultComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
}, },
{ path: 'hint', component: HintComponent }, { path: "hint", component: HintComponent },
{ path: 'set-password', component: SetPasswordComponent }, { path: "set-password", component: SetPasswordComponent },
{ path: 'sso', component: SsoComponent }, { path: "sso", component: SsoComponent },
{ {
path: 'send', path: "send",
component: SendComponent, component: SendComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
}, },
{ {
path: 'update-temp-password', path: "update-temp-password",
component: UpdateTempPasswordComponent, component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
}, },
{ {
path: 'remove-password', path: "remove-password",
component: RemovePasswordComponent, component: RemovePasswordComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { titleId: 'removeMasterPassword' }, data: { titleId: "removeMasterPassword" },
}, },
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes, { imports: [
useHash: true, RouterModule.forRoot(routes, {
/*enableTracing: true,*/ useHash: true,
})], /*enableTracing: true,*/
exports: [RouterModule], }),
],
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 { A11yModule } from "@angular/cdk/a11y";
import { DragDropModule } from '@angular/cdk/drag-drop'; import { DragDropModule } from "@angular/cdk/drag-drop";
import { OverlayModule } from '@angular/cdk/overlay'; import { OverlayModule } from "@angular/cdk/overlay";
import { ScrollingModule } from '@angular/cdk/scrolling'; import { ScrollingModule } from "@angular/cdk/scrolling";
import { DatePipe } from '@angular/common'; import { DatePipe } from "@angular/common";
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import 'zone.js/dist/zone'; import "zone.js/dist/zone";
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from "./app-routing.module";
import { ServicesModule } from './services.module'; import { ServicesModule } from "./services.module";
import { AppComponent } from './app.component'; import { AppComponent } from "./app.component";
import { EnvironmentComponent } from './accounts/environment.component'; import { EnvironmentComponent } from "./accounts/environment.component";
import { HintComponent } from './accounts/hint.component'; import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from './accounts/lock.component'; import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from './accounts/login.component'; import { LoginComponent } from "./accounts/login.component";
import { PremiumComponent } from './accounts/premium.component'; import { PremiumComponent } from "./accounts/premium.component";
import { RegisterComponent } from './accounts/register.component'; import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from './accounts/remove-password.component'; import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from './accounts/set-password.component'; import { SetPasswordComponent } from "./accounts/set-password.component";
import { SettingsComponent } from './accounts/settings.component'; import { SettingsComponent } from "./accounts/settings.component";
import { SsoComponent } from './accounts/sso.component'; import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from './accounts/two-factor.component'; import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component'; import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { VaultTimeoutInputComponent } from './accounts/vault-timeout-input.component'; import { VaultTimeoutInputComponent } from "./accounts/vault-timeout-input.component";
import { AvatarComponent } from 'jslib-angular/components/avatar.component'; import { AvatarComponent } from "jslib-angular/components/avatar.component";
import { CalloutComponent } from 'jslib-angular/components/callout.component'; import { CalloutComponent } from "jslib-angular/components/callout.component";
import { IconComponent } from 'jslib-angular/components/icon.component'; import { IconComponent } from "jslib-angular/components/icon.component";
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component'; import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive'; import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive'; import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive'; import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive'; import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive'; import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
import { CipherListVirtualScroll } from 'jslib-angular/directives/cipherListVirtualScroll.directive'; import { CipherListVirtualScroll } from "jslib-angular/directives/cipherListVirtualScroll.directive";
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive'; import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
import { InputVerbatimDirective } from 'jslib-angular/directives/input-verbatim.directive'; import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive";
import { SelectCopyDirective } from 'jslib-angular/directives/select-copy.directive'; import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive'; import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive'; import { StopPropDirective } from "jslib-angular/directives/stop-prop.directive";
import { TrueFalseValueDirective } from 'jslib-angular/directives/true-false-value.directive'; import { TrueFalseValueDirective } from "jslib-angular/directives/true-false-value.directive";
import { ColorPasswordPipe } from 'jslib-angular/pipes/color-password.pipe'; import { ColorPasswordPipe } from "jslib-angular/pipes/color-password.pipe";
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe'; import { I18nPipe } from "jslib-angular/pipes/i18n.pipe";
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe'; import { SearchCiphersPipe } from "jslib-angular/pipes/search-ciphers.pipe";
import { AddEditCustomFieldsComponent } from './vault/add-edit-custom-fields.component'; import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from './vault/add-edit.component'; import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from './vault/attachments.component'; import { AttachmentsComponent } from "./vault/attachments.component";
import { CiphersComponent } from './vault/ciphers.component'; import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from './vault/collections.component'; import { CollectionsComponent } from "./vault/collections.component";
import { ExportComponent } from './vault/export.component'; import { ExportComponent } from "./vault/export.component";
import { FolderAddEditComponent } from './vault/folder-add-edit.component'; import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
import { GroupingsComponent } from './vault/groupings.component'; import { GroupingsComponent } from "./vault/groupings.component";
import { PasswordGeneratorHistoryComponent } from './vault/password-generator-history.component'; import { PasswordGeneratorHistoryComponent } from "./vault/password-generator-history.component";
import { PasswordGeneratorComponent } from './vault/password-generator.component'; import { PasswordGeneratorComponent } from "./vault/password-generator.component";
import { PasswordHistoryComponent } from './vault/password-history.component'; import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from './vault/share.component'; import { ShareComponent } from "./vault/share.component";
import { VaultComponent } from './vault/vault.component'; import { VaultComponent } from "./vault/vault.component";
import { ViewCustomFieldsComponent } from './vault/view-custom-fields.component'; import { ViewCustomFieldsComponent } from "./vault/view-custom-fields.component";
import { ViewComponent } from './vault/view.component'; import { ViewComponent } from "./vault/view.component";
import { AddEditComponent as SendAddEditComponent } from './send/add-edit.component'; import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from './send/efflux-dates.component'; import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
import { SendComponent } from './send/send.component'; import { SendComponent } from "./send/send.component";
import { AccountSwitcherComponent } from './layout/account-switcher.component'; import { AccountSwitcherComponent } from "./layout/account-switcher.component";
import { HeaderComponent } from './layout/header.component'; import { HeaderComponent } from "./layout/header.component";
import { NavComponent } from './layout/nav.component'; import { NavComponent } from "./layout/nav.component";
import { SearchComponent } from './layout/search/search.component'; import { SearchComponent } from "./layout/search/search.component";
import { PasswordRepromptComponent } from './components/password-reprompt.component'; import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { SetPinComponent } from './components/set-pin.component'; import { SetPinComponent } from "./components/set-pin.component";
import { VerifyMasterPasswordComponent } from './components/verify-master-password.component'; import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component";
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from "@angular/common";
import localeAf from '@angular/common/locales/af'; import localeAf from "@angular/common/locales/af";
import localeAz from '@angular/common/locales/az'; import localeAz from "@angular/common/locales/az";
import localeBe from '@angular/common/locales/be'; import localeBe from "@angular/common/locales/be";
import localeBg from '@angular/common/locales/bg'; import localeBg from "@angular/common/locales/bg";
import localeBn from '@angular/common/locales/bn'; import localeBn from "@angular/common/locales/bn";
import localeCa from '@angular/common/locales/ca'; import localeCa from "@angular/common/locales/ca";
import localeCs from '@angular/common/locales/cs'; import localeCs from "@angular/common/locales/cs";
import localeDa from '@angular/common/locales/da'; import localeDa from "@angular/common/locales/da";
import localeDe from '@angular/common/locales/de'; import localeDe from "@angular/common/locales/de";
import localeEl from '@angular/common/locales/el'; import localeEl from "@angular/common/locales/el";
import localeEnGb from '@angular/common/locales/en-GB'; import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from '@angular/common/locales/en-IN'; import localeEnIn from "@angular/common/locales/en-IN";
import localeEs from '@angular/common/locales/es'; import localeEs from "@angular/common/locales/es";
import localeEt from '@angular/common/locales/et'; import localeEt from "@angular/common/locales/et";
import localeFa from '@angular/common/locales/fa'; import localeFa from "@angular/common/locales/fa";
import localeFi from '@angular/common/locales/fi'; import localeFi from "@angular/common/locales/fi";
import localeFr from '@angular/common/locales/fr'; import localeFr from "@angular/common/locales/fr";
import localeHe from '@angular/common/locales/he'; import localeHe from "@angular/common/locales/he";
import localeHr from '@angular/common/locales/hr'; import localeHr from "@angular/common/locales/hr";
import localeHu from '@angular/common/locales/hu'; import localeHu from "@angular/common/locales/hu";
import localeId from '@angular/common/locales/id'; import localeId from "@angular/common/locales/id";
import localeIt from '@angular/common/locales/it'; import localeIt from "@angular/common/locales/it";
import localeJa from '@angular/common/locales/ja'; import localeJa from "@angular/common/locales/ja";
import localeKn from '@angular/common/locales/kn'; import localeKn from "@angular/common/locales/kn";
import localeKo from '@angular/common/locales/ko'; import localeKo from "@angular/common/locales/ko";
import localeLv from '@angular/common/locales/lv'; import localeLv from "@angular/common/locales/lv";
import localeMl from '@angular/common/locales/ml'; import localeMl from "@angular/common/locales/ml";
import localeNb from '@angular/common/locales/nb'; import localeNb from "@angular/common/locales/nb";
import localeNl from '@angular/common/locales/nl'; import localeNl from "@angular/common/locales/nl";
import localePl from '@angular/common/locales/pl'; import localePl from "@angular/common/locales/pl";
import localePtBr from '@angular/common/locales/pt'; import localePtBr from "@angular/common/locales/pt";
import localePtPt from '@angular/common/locales/pt-PT'; import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from '@angular/common/locales/ro'; import localeRo from "@angular/common/locales/ro";
import localeRu from '@angular/common/locales/ru'; import localeRu from "@angular/common/locales/ru";
import localeSk from '@angular/common/locales/sk'; import localeSk from "@angular/common/locales/sk";
import localeSr from '@angular/common/locales/sr'; import localeSr from "@angular/common/locales/sr";
import localeMe from '@angular/common/locales/sr-Latn-ME'; import localeMe from "@angular/common/locales/sr-Latn-ME";
import localeSv from '@angular/common/locales/sv'; import localeSv from "@angular/common/locales/sv";
import localeTh from '@angular/common/locales/th'; import localeTh from "@angular/common/locales/th";
import localeTr from '@angular/common/locales/tr'; import localeTr from "@angular/common/locales/tr";
import localeUk from '@angular/common/locales/uk'; import localeUk from "@angular/common/locales/uk";
import localeVi from '@angular/common/locales/vi'; import localeVi from "@angular/common/locales/vi";
import localeZhCn from '@angular/common/locales/zh-Hans'; import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from '@angular/common/locales/zh-Hant'; import localeZhTw from "@angular/common/locales/zh-Hant";
registerLocaleData(localeAf, 'af'); registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, 'az'); registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, 'be'); registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, 'bg'); registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, 'bn'); registerLocaleData(localeBn, "bn");
registerLocaleData(localeCa, 'ca'); registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, 'cs'); registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, 'da'); registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, 'de'); registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, 'el'); registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, 'en-GB'); registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, 'en-IN'); registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEs, 'es'); registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, 'et'); registerLocaleData(localeEt, "et");
registerLocaleData(localeFa, 'fa'); registerLocaleData(localeFa, "fa");
registerLocaleData(localeFi, 'fi'); registerLocaleData(localeFi, "fi");
registerLocaleData(localeFr, 'fr'); registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, 'he'); registerLocaleData(localeHe, "he");
registerLocaleData(localeHr, 'hr'); registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, 'hu'); registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, 'id'); registerLocaleData(localeId, "id");
registerLocaleData(localeIt, 'it'); registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, 'ja'); registerLocaleData(localeJa, "ja");
registerLocaleData(localeKn, 'kn'); registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, 'ko'); registerLocaleData(localeKo, "ko");
registerLocaleData(localeLv, 'lv'); registerLocaleData(localeLv, "lv");
registerLocaleData(localeMe, 'me'); registerLocaleData(localeMe, "me");
registerLocaleData(localeMl, 'ml'); registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, 'nb'); registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, 'nl'); registerLocaleData(localeNl, "nl");
registerLocaleData(localePl, 'pl'); registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, 'pt-BR'); registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, 'pt-PT'); registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, 'ro'); registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, 'ru'); registerLocaleData(localeRu, "ru");
registerLocaleData(localeSk, 'sk'); registerLocaleData(localeSk, "sk");
registerLocaleData(localeSr, 'sr'); registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, 'sv'); registerLocaleData(localeSv, "sv");
registerLocaleData(localeTh, 'th'); registerLocaleData(localeTh, "th");
registerLocaleData(localeTr, 'tr'); registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, 'uk'); registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, 'vi'); registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, 'zh-CN'); registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, 'zh-TW'); registerLocaleData(localeZhTw, "zh-TW");
@NgModule({ @NgModule({
imports: [ imports: [
AppRoutingModule, AppRoutingModule,
BrowserAnimationsModule, BrowserAnimationsModule,
BrowserModule, BrowserModule,
DragDropModule, DragDropModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
ServicesModule, ServicesModule,
BitwardenToastModule.forRoot({ BitwardenToastModule.forRoot({
maxOpened: 5, maxOpened: 5,
autoDismiss: true, autoDismiss: true,
closeButton: true, closeButton: true,
}), }),
ScrollingModule, ScrollingModule,
A11yModule, A11yModule,
OverlayModule, OverlayModule,
], ],
declarations: [ declarations: [
A11yTitleDirective, A11yTitleDirective,
AddEditComponent, AddEditComponent,
AddEditCustomFieldsComponent, AddEditCustomFieldsComponent,
ApiActionDirective, ApiActionDirective,
AppComponent, AppComponent,
AttachmentsComponent, AttachmentsComponent,
AutofocusDirective, AutofocusDirective,
BlurClickDirective, BlurClickDirective,
BoxRowDirective, BoxRowDirective,
CalloutComponent, CalloutComponent,
CipherListVirtualScroll, CipherListVirtualScroll,
CiphersComponent, CiphersComponent,
CollectionsComponent, CollectionsComponent,
ColorPasswordPipe, ColorPasswordPipe,
EnvironmentComponent, EnvironmentComponent,
ExportComponent, ExportComponent,
FallbackSrcDirective, FallbackSrcDirective,
FolderAddEditComponent, FolderAddEditComponent,
GroupingsComponent, GroupingsComponent,
HintComponent, HintComponent,
I18nPipe, I18nPipe,
IconComponent, IconComponent,
InputVerbatimDirective, InputVerbatimDirective,
LockComponent, LockComponent,
LoginComponent, LoginComponent,
NavComponent, NavComponent,
PasswordGeneratorComponent, PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent, PasswordGeneratorHistoryComponent,
PasswordHistoryComponent, PasswordHistoryComponent,
PasswordRepromptComponent, PasswordRepromptComponent,
PremiumComponent, PremiumComponent,
RegisterComponent, RegisterComponent,
RemovePasswordComponent, RemovePasswordComponent,
SearchCiphersPipe, SearchCiphersPipe,
SelectCopyDirective, SelectCopyDirective,
SendAddEditComponent, SendAddEditComponent,
SendComponent, SendComponent,
SendEffluxDatesComponent, SendEffluxDatesComponent,
SetPasswordComponent, SetPasswordComponent,
SetPinComponent, SetPinComponent,
SettingsComponent, SettingsComponent,
ShareComponent, ShareComponent,
SsoComponent, SsoComponent,
StopClickDirective, StopClickDirective,
StopPropDirective, StopPropDirective,
TrueFalseValueDirective, TrueFalseValueDirective,
TwoFactorComponent, TwoFactorComponent,
TwoFactorOptionsComponent, TwoFactorOptionsComponent,
UpdateTempPasswordComponent, UpdateTempPasswordComponent,
VaultComponent, VaultComponent,
VaultTimeoutInputComponent, VaultTimeoutInputComponent,
VerifyMasterPasswordComponent, VerifyMasterPasswordComponent,
ViewComponent, ViewComponent,
ViewCustomFieldsComponent, ViewCustomFieldsComponent,
HeaderComponent, HeaderComponent,
AccountSwitcherComponent, AccountSwitcherComponent,
AvatarComponent, AvatarComponent,
SearchComponent, SearchComponent,
], ],
providers: [DatePipe], providers: [DatePipe],
bootstrap: [AppComponent], 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 fade" role="dialog" aria-modal="true">
<div class="modal-dialog modal-dialog-scrollable" role="document"> <div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()"> <form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header">{{'passwordConfirmation' | i18n}}</div> <div class="box-header">{{ "passwordConfirmation" | i18n }}</div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow> <div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main"> <div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword" <input
class="monospaced" [(ngModel)]="masterPassword" required appAutofocus> id="masterPassword"
</div> type="{{ showPassword ? 'text' : 'password' }}"
<div class="action-buttons"> name="MasterPassword"
<a class="row-btn" href="#" appStopClick appBlurClick role="button" class="monospaced"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()"> [(ngModel)]="masterPassword"
<i class="fa fa-lg" aria-hidden="true" required
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> appAutofocus
</a> />
</div> </div>
</div> <div class="action-buttons">
</div> <a
<div class="box-footer"> class="row-btn"
{{'passwordConfirmationDesc' | i18n}} href="#"
</div> appStopClick
</div> 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="modal-footer"> </div>
<button type="submit" class="btn btn-primary btn-submit" appBlurClick> <div class="box-footer">
<span>{{'ok' | i18n}}</span> {{ "passwordConfirmationDesc" | i18n }}
</button> </div>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal"> </div>
{{'cancel' | i18n}} </div>
</button> <div class="modal-footer">
</div> <button type="submit" class="btn btn-primary btn-submit" appBlurClick>
</form> <span>{{ "ok" | i18n }}</span>
</div> </button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</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({ @Component({
templateUrl: 'password-reprompt.component.html', templateUrl: "password-reprompt.component.html",
}) })
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {} export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}

View File

@ -1,44 +1,65 @@
<div class="modal fade" role="dialog" aria-modal="true"> <div class="modal fade" role="dialog" aria-modal="true">
<div class="modal-dialog modal-dialog-scrollable set-pin-modal" role="document"> <div class="modal-dialog modal-dialog-scrollable set-pin-modal" role="document">
<form class="modal-content" #form (ngSubmit)="submit()"> <form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body"> <div class="modal-body">
<div> <div>
{{'setYourPinCode' | i18n}} {{ "setYourPinCode" | i18n }}
</div> </div>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow> <div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main"> <div class="row-main">
<label for="pin">{{'pin' | i18n}}</label> <label for="pin">{{ "pin" | i18n }}</label>
<input id="pin" type="{{showPin ? 'text' : 'password'}}" name="Pin" class="monospaced" <input
[(ngModel)]="pin" required appInputVerbatim appAutofocus> id="pin"
</div> type="{{ showPin ? 'text' : 'password' }}"
<div class="action-buttons"> name="Pin"
<a class="row-btn" href="#" appStopClick appBlurClick class="monospaced"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleVisibility()"> [(ngModel)]="pin"
<i class="fa fa-lg" aria-hidden="true" required
[ngClass]="{'fa-eye': !showPin, 'fa-eye-slash': showPin}"></i> appInputVerbatim
</a> appAutofocus
</div> />
</div> </div>
</div> <div class="action-buttons">
</div> <a
<div class="checkbox" *ngIf="showMasterPassOnRestart"> class="row-btn"
<label for="masterPasswordOnRestart"> href="#"
<input type="checkbox" id="masterPasswordOnRestart" name="MasterPasswordOnRestart" appStopClick
[(ngModel)]="masterPassOnRestart"> appBlurClick
<span>{{'lockWithMasterPassOnRestart' | i18n}}</span> appA11yTitle="{{ 'toggleVisibility' | i18n }}"
</label> (click)="toggleVisibility()"
</div> >
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPin, 'fa-eye-slash': showPin }"
></i>
</a>
</div>
</div> </div>
<div class="modal-footer"> </div>
<button type="submit" class="btn btn-primary btn-submit" appBlurClick> </div>
<span>{{'ok' | i18n}}</span> <div class="checkbox" *ngIf="showMasterPassOnRestart">
</button> <label for="masterPasswordOnRestart">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal"> <input
{{'cancel' | i18n}} type="checkbox"
</button> id="masterPasswordOnRestart"
</div> name="MasterPasswordOnRestart"
</form> [(ngModel)]="masterPassOnRestart"
</div> />
<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> </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({ @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"> <ng-container *ngIf="!usesKeyConnector">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control" <input
[formControl]="secret" required appAutofocus appInputVerbatim> id="masterPassword"
</div> type="password"
name="MasterPasswordHash"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div>
</ng-container> </ng-container>
<ng-container *ngIf="usesKeyConnector"> <ng-container *ngIf="usesKeyConnector">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label class="d-block">{{'sendVerificationCode' | i18n}}</label> <label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
<button type="button" class="btn btn-outline-secondary" (click)="requestOTP()" [disabled]="disableRequestOTP"> <button
{{'sendCode' | i18n}} type="button"
</button> class="btn btn-outline-secondary"
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode"> (click)="requestOTP()"
<i class="fa fa-check-circle-o" aria-hidden="true"></i> [disabled]="disableRequestOTP"
{{'codeSent' | i18n}} >
</span> {{ "sendCode" | i18n }}
</div> </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> <div class="box-content-row" appBoxRow>
<label for="verificationCode">{{'verificationCode' | i18n}}</label> <label for="verificationCode">{{ "verificationCode" | i18n }}</label>
<input id="verificationCode" type="input" name="verificationCode" class="form-control" <input
[formControl]="secret" required appAutofocus appInputVerbatim> id="verificationCode"
</div> type="input"
name="verificationCode"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div>
</ng-container> </ng-container>

View File

@ -1,31 +1,23 @@
import { import { animate, style, transition, trigger } from "@angular/animations";
animate, import { Component } from "@angular/core";
style, import { NG_VALUE_ACCESSOR } from "@angular/forms";
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({ @Component({
selector: 'app-verify-master-password', selector: "app-verify-master-password",
templateUrl: 'verify-master-password.component.html', templateUrl: "verify-master-password.component.html",
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
multi: true, multi: true,
useExisting: VerifyMasterPasswordComponent, useExisting: VerifyMasterPasswordComponent,
}, },
], ],
animations: [ animations: [
trigger('sent', [ trigger("sent", [
transition(':enter', [ transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
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"> <a
<app-avatar [data]="activeAccountEmail" size="25" [circle]="true" [fontSize]="14" [dynamic]="true" *ngIf="activeAccountEmail != null"></app-avatar> class="account-switcher"
<span>{{activeAccountEmail}}</span> (click)="toggle()"
<i class="fa" aria-hidden="true" [ngClass]="{'fa-chevron-down': !isOpen, 'fa-chevron-up': isOpen}"></i> 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> </a>
<ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOpen]="isOpen" cdkConnectedOverlayMinWidth="250px"> <ng-template
<div class="account-switcher-dropdown" [@transformPanel]="'open'"> cdkConnectedOverlay
<div class="accounts"> [cdkConnectedOverlayOrigin]="trigger"
<a *ngFor="let a of accounts | keyvalue" class="account" [ngClass]="{'active': a.value.profile.authenticationStatus == 'active'}" [cdkConnectedOverlayOpen]="isOpen"
href="#" appStopClick (click)="switch(a.key)"> cdkConnectedOverlayMinWidth="250px"
<span class="email">{{a.value.profile.email}}</span> >
<span class="server">{{a.value.settings.environmentUrls.server}}</span> <div class="account-switcher-dropdown" [@transformPanel]="'open'">
<span class="status">{{a.value.profile.authenticationStatus}}</span> <div class="accounts">
</a> <a
</div> *ngFor="let a of accounts | keyvalue"
<div class="border"></div> class="account"
<a class="add" routerLink="/login" (click)="toggle()"> [ngClass]="{ active: a.value.profile.authenticationStatus == 'active' }"
<i class="fa fa-plus" aria-hidden="true"></i> {{'addAccount' | i18n}} href="#"
</a> 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>
<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> </ng-template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,176 +1,297 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content"> <div class="content">
<div class="inner-content" *ngIf="send"> <div class="inner-content" *ngIf="send">
<div class="box"> <div class="box">
<app-callout *ngIf="disableSend"> <app-callout *ngIf="disableSend">
<span>{{'sendDisabledWarning' | i18n}}</span> <span>{{ "sendDisabledWarning" | i18n }}</span>
</app-callout> </app-callout>
<app-callout type="info" *ngIf="disableHideEmail && !disableSend"> <app-callout type="info" *ngIf="disableHideEmail && !disableSend">
{{'sendOptionsPolicyInEffect' | i18n}} {{ "sendOptionsPolicyInEffect" | i18n }}
</app-callout> </app-callout>
</div> </div>
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
{{title}} {{ 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> </div>
</div> <div class="box-content">
<div class="footer"> <div class="box-content-row" appBoxRow>
<button appBlurClick type="submit" class="primary btn-submit" appA11yTitle="{{'save' | i18n}}" [disabled]="form.loading" *ngIf="!disableSend"> <label for="name">{{ "name" | i18n }}</label>
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <input
<span><i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i></span> id="name"
</button> type="text"
<button appBlurClick type="button" (click)="cancel()" [disabled]="form.loading"> name="Name"
{{'cancel' | i18n}} [(ngModel)]="send.name"
</button> appAutofocus
<div class="right"> [readOnly]="disableSend"
<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> </div>
</button> <div class="box-content-row box-content-row-radio" *ngIf="!editMode">
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger" <label class="radio-header">{{ "whatTypeOfSend" | i18n }}</label>
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode"> <div class="item" *ngFor="let o of typeOptions">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i> <input
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" aria-hidden="true"></i> type="radio"
</button> 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>
<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>
<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> </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 { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SendService } from 'jslib-common/abstractions/send.service'; import { SendService } from "jslib-common/abstractions/send.service";
import { StateService } from 'jslib-common/abstractions/state.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({ @Component({
selector: 'app-send-add-edit', selector: "app-send-add-edit",
templateUrl: 'add-edit.component.html', templateUrl: "add-edit.component.html",
}) })
export class AddEditComponent extends BaseAddEditComponent { export class AddEditComponent extends BaseAddEditComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, constructor(
environmentService: EnvironmentService, datePipe: DatePipe, i18nService: I18nService,
sendService: SendService, stateService: StateService, platformUtilsService: PlatformUtilsService,
messagingService: MessagingService, policyService: PolicyService, environmentService: EnvironmentService,
logService: LogService) { datePipe: DatePipe,
super(i18nService, platformUtilsService, environmentService, sendService: SendService,
datePipe, sendService, messagingService, policyService, stateService: StateService,
logService, stateService); messagingService: MessagingService,
} policyService: PolicyService,
logService: LogService
) {
super(
i18nService,
platformUtilsService,
environmentService,
datePipe,
sendService,
messagingService,
policyService,
logService,
stateService
);
}
async refresh() { async refresh() {
this.password = null; this.password = null;
const send = await this.loadSend(); const send = await this.loadSend();
this.send = await send.decrypt(); this.send = await send.decrypt();
this.hasPassword = this.send.password != null && this.send.password.trim() !== ''; this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
} }
cancel() { cancel() {
this.onCancelled.emit(this.send); this.onCancelled.emit(this.send);
} }
async copyLinkToClipboard(link: string) { async copyLinkToClipboard(link: string) {
super.copyLinkToClipboard(link); super.copyLinkToClipboard(link);
this.platformUtilsService.showToast('success', null, this.platformUtilsService.showToast(
this.i18nService.t('valueCopied', this.i18nService.t('sendLink'))); "success",
} null,
this.i18nService.t("valueCopied", this.i18nService.t("sendLink"))
);
}
} }

View File

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

View File

@ -1,38 +1,41 @@
import { DatePipe } from '@angular/common'; import { DatePipe } from "@angular/common";
import { import { Component, OnChanges } from "@angular/core";
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 { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({ @Component({
selector: 'app-send-efflux-dates', selector: "app-send-efflux-dates",
templateUrl: 'efflux-dates.component.html', templateUrl: "efflux-dates.component.html",
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
}) })
export class EffluxDatesComponent extends BaseEffluxDatesComponent implements OnChanges { export class EffluxDatesComponent extends BaseEffluxDatesComponent implements OnChanges {
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, constructor(
protected datePipe: DatePipe) { protected i18nService: I18nService,
super(i18nService, platformUtilsService, datePipe); 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. // We reuse the same form on desktop and just swap content, so need to watch these to maintin proper values.
ngOnChanges() { ngOnChanges() {
this.selectedExpirationDatePreset.setValue(0); this.selectedExpirationDatePreset.setValue(0);
this.selectedDeletionDatePreset.setValue(0); this.selectedDeletionDatePreset.setValue(0);
this.defaultDeletionDateTime.setValue(this.datePipe.transform(new Date(this.initialDeletionDate), 'yyyy-MM-ddTHH:mm')); this.defaultDeletionDateTime.setValue(
if (this.initialExpirationDate) { this.datePipe.transform(new Date(this.initialDeletionDate), "yyyy-MM-ddTHH:mm")
this.defaultExpirationDateTime.setValue( );
this.datePipe.transform(new Date(this.initialExpirationDate), 'yyyy-MM-ddTHH:mm')); if (this.initialExpirationDate) {
} else { this.defaultExpirationDateTime.setValue(
this.defaultExpirationDateTime.setValue(null); 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 id="sends" class="vault">
<div class="groupings"> <div class="groupings">
<div class="mac-bar"></div> <div class="mac-bar"></div>
<div class="content"> <div class="content">
<div class="inner-content"> <div class="inner-content">
<h2 class="sr-only">{{'filters' | i18n}}</h2> <h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul> <ul>
<li [ngClass]="{active: selectedAll}"> <li [ngClass]="{ active: selectedAll }">
<a href="#" appStopClick appBlurClick (click)="selectAll()"> <a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{'allSends' | i18n}} <i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{ "allSends" | i18n }}
</a> </a>
</li> </li>
</ul> </ul>
<h2>{{'types' | i18n}}</h2> <h2>{{ "types" | i18n }}</h2>
<ul> <ul>
<li [ngClass]="{active: selectedType === sendType.Text}"> <li [ngClass]="{ active: selectedType === sendType.Text }">
<a href="#" appStopClick appBlurClick (click)="selectType(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}} <i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>&nbsp;{{
</a> "sendTypeText" | i18n
</li> }}
<li [ngClass]="{active: selectedType === sendType.File}"> </a>
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.File)"> </li>
<i class="fa fa-fw fa-file-o" aria-hidden="true"></i>&nbsp;{{'sendTypeFile' | i18n}} <li [ngClass]="{ active: selectedType === sendType.File }">
</a> <a href="#" appStopClick appBlurClick (click)="selectType(sendType.File)">
</li> <i class="fa fa-fw fa-file-o" aria-hidden="true"></i>&nbsp;{{ "sendTypeFile" | i18n }}
</ul> </a>
</div> </li>
<div class="footer"> </ul>
<app-nav class="nav"></app-nav> </div>
</div> <div class="footer">
</div> <app-nav class="nav"></app-nav>
</div>
</div> </div>
<div id="items" class="items"> </div>
<div class="content"> <div id="items" class="items">
<div class="list" *ngIf="filteredSends.length"> <div class="content">
<a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s.id)" <div class="list" *ngIf="filteredSends.length">
title="{{'viewItem' | i18n}}" (contextmenu)="viewSendMenu(s)" <a
[ngClass]="{'active': s.id === sendId}" class="flex-list-item"> *ngFor="let s of filteredSends"
<div class="item-icon" aria-hidden="true"> appStopClick
<i class="fa fa-fw fa-lg" [ngClass]="s.type == 0 ? 'fa-file-text-o' : 'fa-file-o'"></i> (click)="selectSend(s.id)"
</div> title="{{ 'viewItem' | i18n }}"
<div class="item-content"> (contextmenu)="viewSendMenu(s)"
<div class="item-title"> [ngClass]="{ active: s.id === sendId }"
{{s.name}} class="flex-list-item"
<span class="title-badges"> >
<ng-container *ngIf="s.disabled"> <div class="item-icon" aria-hidden="true">
<i class="fa fa-warning" appStopProp title="{{'disabled' | i18n}}" aria-hidden="true"></i> <i class="fa fa-fw fa-lg" [ngClass]="s.type == 0 ? 'fa-file-text-o' : 'fa-file-o'"></i>
<span class="sr-only">{{'disabled' | i18n}}</span> </div>
</ng-container> <div class="item-content">
<ng-container *ngIf="s.password"> <div class="item-title">
<i class="fa fa-key" appStopProp title="{{'password' | i18n}}" aria-hidden="true"></i> {{ s.name }}
<span class="sr-only">{{'password' | i18n}}</span> <span class="title-badges">
</ng-container> <ng-container *ngIf="s.disabled">
<ng-container *ngIf="s.maxAccessCountReached"> <i
<i class="fa fa-ban" appStopProp title="{{'maxAccessCountReached' | i18n}}" class="fa fa-warning"
aria-hidden="true"></i> appStopProp
<span class="sr-only">{{'maxAccessCountReached' | i18n}}</span> title="{{ 'disabled' | i18n }}"
</ng-container> aria-hidden="true"
<ng-container *ngIf="s.expired"> ></i>
<i class="fa fa-clock-o" appStopProp title="{{'expired' | i18n}}" aria-hidden="true"></i> <span class="sr-only">{{ "disabled" | i18n }}</span>
<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>
</ng-container> </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> <span class="item-details">{{ s.deletionDate | date }}</span>
<div class="footer"> </div>
<button appBlurClick (click)="addSend()" class="block primary" appA11yTitle="{{'addItem' | i18n}}"> </a>
<i class="fa fa-plus fa-lg" aria-hidden="true"></i> </div>
</button> <div class="no-items" *ngIf="!filteredSends.length">
</div> <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> </div>
<app-send-add-edit id="addEdit" class="details" *ngIf="action == 'add' || action == 'edit'" [sendId]="sendId" [type]="selectedSendType" <div class="footer">
(onSavedSend)="savedSend($event)" (onCancelled)="cancel($event)" (onDeletedSend)="deletedSend($event)"></app-send-add-edit> <button
<div class="logo" *ngIf="!action"> appBlurClick
<div class="content"> (click)="addSend()"
<div class="inner-content"> class="block primary"
<img class="logo-image" alt="Bitwarden" aria-hidden="true" /> appA11yTitle="{{ 'addItem' | i18n }}"
</div> >
</div> <i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
</div> </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> </div>

View File

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

View File

@ -1,179 +1,173 @@
import { import { APP_INITIALIZER, NgModule } from "@angular/core";
APP_INITIALIZER,
NgModule,
} from '@angular/core';
import { ElectronLogService } from 'jslib-electron/services/electronLog.service'; import { ElectronLogService } from "jslib-electron/services/electronLog.service";
import { ElectronPlatformUtilsService } from 'jslib-electron/services/electronPlatformUtils.service'; import { ElectronPlatformUtilsService } from "jslib-electron/services/electronPlatformUtils.service";
import { ElectronRendererMessagingService } from 'jslib-electron/services/electronRendererMessaging.service'; import { ElectronRendererMessagingService } from "jslib-electron/services/electronRendererMessaging.service";
import { ElectronRendererSecureStorageService } from 'jslib-electron/services/electronRendererSecureStorage.service'; import { ElectronRendererSecureStorageService } from "jslib-electron/services/electronRendererSecureStorage.service";
import { ElectronRendererStorageService } from 'jslib-electron/services/electronRendererStorage.service'; import { ElectronRendererStorageService } from "jslib-electron/services/electronRendererStorage.service";
import { I18nService } from '../services/i18n.service'; import { I18nService } from "../services/i18n.service";
import { LoginGuardService } from '../services/loginGuard.service'; import { LoginGuardService } from "../services/loginGuard.service";
import { NativeMessagingService } from '../services/nativeMessaging.service'; import { NativeMessagingService } from "../services/nativeMessaging.service";
import { PasswordRepromptService } from '../services/passwordReprompt.service'; import { PasswordRepromptService } from "../services/passwordReprompt.service";
import { SearchBarService } from './layout/search/search-bar.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 { AuthService } from "jslib-common/services/auth.service";
import { ContainerService } from 'jslib-common/services/container.service'; import { ContainerService } from "jslib-common/services/container.service";
import { EventService } from 'jslib-common/services/event.service'; import { EventService } from "jslib-common/services/event.service";
import { SystemService } from 'jslib-common/services/system.service'; import { SystemService } from "jslib-common/services/system.service";
import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.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 { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service'; import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service'; import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service'; import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service'; import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service'; import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service'; import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service'; import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service'; import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service'; import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service'; import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
import { SystemService as SystemServiceAbstraction } from 'jslib-common/abstractions/system.service'; import { SystemService as SystemServiceAbstraction } from "jslib-common/abstractions/system.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.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, export function initFactory(
syncService: SyncServiceAbstraction, vaultTimeoutService: VaultTimeoutService, window: Window,
i18nService: I18nService, eventService: EventService, environmentService: EnvironmentServiceAbstraction,
authService: AuthService, notificationsService: NotificationsServiceAbstraction, syncService: SyncServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction, stateService: StateServiceAbstraction, vaultTimeoutService: VaultTimeoutService,
cryptoService: CryptoServiceAbstraction): Function { 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 () => { let installAction = null;
await stateService.init(); const installedVersion = await stateService.getInstalledVersion();
await environmentService.setUrlsFromStorage(); const currentVersion = await platformUtilsService.getApplicationVersion();
syncService.fullSync(true); if (installedVersion == null) {
await vaultTimeoutService.init(true); installAction = "install";
const locale = await stateService.getLocale(); } else if (installedVersion !== currentVersion) {
await i18nService.init(locale); installAction = "update";
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; if (installAction != null) {
const installedVersion = await stateService.getInstalledVersion(); await stateService.setInstalledVersion(currentVersion);
const currentVersion = await platformUtilsService.getApplicationVersion(); }
if (installedVersion == null) {
installAction = 'install';
} else if (installedVersion !== currentVersion) {
installAction = 'update';
}
if (installAction != null) { const containerService = new ContainerService(cryptoService);
await stateService.setInstalledVersion(currentVersion); containerService.attachToGlobal(window);
} };
const containerService = new ContainerService(cryptoService);
containerService.attachToGlobal(window);
};
} }
@NgModule({ @NgModule({
imports: [ imports: [JslibServicesModule],
JslibServicesModule, declarations: [],
], providers: [
declarations: [], {
providers: [ provide: APP_INITIALIZER,
{ useFactory: initFactory,
provide: APP_INITIALIZER, deps: [
useFactory: initFactory, "WINDOW",
deps: [ EnvironmentServiceAbstraction,
'WINDOW', SyncServiceAbstraction,
EnvironmentServiceAbstraction, VaultTimeoutServiceAbstraction,
SyncServiceAbstraction, I18nServiceAbstraction,
VaultTimeoutServiceAbstraction, EventServiceAbstraction,
I18nServiceAbstraction, AuthServiceAbstraction,
EventServiceAbstraction, NotificationsServiceAbstraction,
AuthServiceAbstraction, PlatformUtilsServiceAbstraction,
NotificationsServiceAbstraction, StateServiceAbstraction,
PlatformUtilsServiceAbstraction, CryptoServiceAbstraction,
StateServiceAbstraction, ],
CryptoServiceAbstraction, multi: true,
], },
multi: true, { provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
}, {
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] }, provide: PlatformUtilsServiceAbstraction,
{ useFactory: (
provide: PlatformUtilsServiceAbstraction, i18nService: I18nServiceAbstraction,
useFactory: (i18nService: I18nServiceAbstraction, messagingService: MessagingServiceAbstraction, messagingService: MessagingServiceAbstraction,
stateService: StateServiceAbstraction) => new ElectronPlatformUtilsService(i18nService, stateService: StateServiceAbstraction
messagingService, true, stateService), ) => new ElectronPlatformUtilsService(i18nService, messagingService, true, stateService),
deps: [ deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StateServiceAbstraction],
I18nServiceAbstraction, },
MessagingServiceAbstraction, {
StateServiceAbstraction, provide: I18nServiceAbstraction,
], useFactory: (window: Window) => new I18nService(window.navigator.language, "./locales"),
}, deps: ["WINDOW"],
{ },
provide: I18nServiceAbstraction, {
useFactory: (window: Window) => new I18nService(window.navigator.language, './locales'), provide: MessagingServiceAbstraction,
deps: [ 'WINDOW' ], useClass: ElectronRendererMessagingService,
}, deps: [BroadcasterServiceAbstraction],
{ },
provide: MessagingServiceAbstraction, { provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
useClass: ElectronRendererMessagingService, { provide: "SECURE_STORAGE", useClass: ElectronRendererSecureStorageService },
deps: [ BroadcasterServiceAbstraction ], {
}, provide: CryptoServiceAbstraction,
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService }, useClass: ElectronCryptoService,
{ provide: 'SECURE_STORAGE', useClass: ElectronRendererSecureStorageService }, deps: [
{ CryptoFunctionServiceAbstraction,
provide: CryptoServiceAbstraction, PlatformUtilsServiceAbstraction,
useClass: ElectronCryptoService, LogServiceAbstraction,
deps: [ StateServiceAbstraction,
CryptoFunctionServiceAbstraction, ],
PlatformUtilsServiceAbstraction, },
LogServiceAbstraction, {
StateServiceAbstraction, provide: SystemServiceAbstraction,
], useClass: SystemService,
}, deps: [
{ VaultTimeoutServiceAbstraction,
provide: SystemServiceAbstraction, MessagingServiceAbstraction,
useClass: SystemService, PlatformUtilsServiceAbstraction,
deps: [ StateServiceAbstraction,
VaultTimeoutServiceAbstraction, ],
MessagingServiceAbstraction, },
PlatformUtilsServiceAbstraction, { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
StateServiceAbstraction, NativeMessagingService,
], SearchBarService,
}, {
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, provide: LoginGuardService,
NativeMessagingService, useClass: LoginGuardService,
SearchBarService, deps: [StateServiceAbstraction, PlatformUtilsServiceAbstraction, I18nServiceAbstraction],
{ },
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">
<div class="box-header"> <div class="box-header">
{{'customFields' | i18n}} {{ "customFields" | i18n }}
</div> </div>
<div class="box-content"> <div class="box-content">
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields"> <div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
<div class="box-content-row box-content-row-multi box-draggable-row" cdkDrag <div
*ngFor="let f of cipher.fields; let i = index; trackBy:trackByFunction" class="box-content-row box-content-row-multi box-draggable-row"
[ngClass]="{'box-content-row-checkbox': f.type === fieldType.Boolean}"> cdkDrag
<a href="#" appStopClick (click)="removeField(f)" appA11yTitle="{{'remove' | i18n}}" *ngFor="let f of cipher.fields; let i = index; trackBy: trackByFunction"
role="button"> [ngClass]="{ 'box-content-row-checkbox': f.type === fieldType.Boolean }"
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i> >
</a> <a
<label for="fieldName{{i}}" class="sr-only">{{'name' | i18n}}</label> href="#"
<label for="fieldValue{{i}}" class="sr-only">{{'value' | i18n}}</label> appStopClick
<div class="row-main"> (click)="removeField(f)"
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name" appA11yTitle="{{ 'remove' | i18n }}"
class="row-label" placeholder="{{'name' | i18n}}" appInputVerbatim> role="button"
<!-- Text --> >
<input id="fieldValue{{i}}" type="text" name="Field.Value{{i}}" [(ngModel)]="f.value" <i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
*ngIf="f.type === fieldType.Text" placeholder="{{'value' | i18n}}" appInputVerbatim> </a>
<!-- Password --> <label for="fieldName{{ i }}" class="sr-only">{{ "name" | i18n }}</label>
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}" <label for="fieldValue{{ i }}" class="sr-only">{{ "value" | i18n }}</label>
name="Field.Value{{i}}" [(ngModel)]="f.value" class="monospaced" <div class="row-main">
*ngIf="f.type === fieldType.Hidden" placeholder="{{'value' | i18n}}" <input
[disabled]="!cipher.viewPassword && !f.newField" appInputVerbatim> id="fieldName{{ i }}"
<!-- Linked --> type="text"
<select id="fieldValue{{i}}" name="Field.Value{{i}}" [(ngModel)]="f.linkedId" name="Field.Name{{ i }}"
*ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null"> [(ngModel)]="f.name"
<option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{o.name}}</option> class="row-label"
</select> placeholder="{{ 'name' | i18n }}"
</div> appInputVerbatim
<!-- Boolean --> />
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox" [(ngModel)]="f.value" <!-- Text -->
*ngIf="f.type === fieldType.Boolean" appTrueFalseValue trueValue="true" <input
falseValue="false"> id="fieldValue{{ i }}"
<div class="action-buttons" type="text"
*ngIf="f.type === fieldType.Hidden && (cipher.viewPassword || f.newField)"> name="Field.Value{{ i }}"
<a class="row-btn" href="#" appStopClick appBlurClick role="button" [(ngModel)]="f.value"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)"> *ngIf="f.type === fieldType.Text"
<i class="fa fa-lg" aria-hidden="true" placeholder="{{ 'value' | i18n }}"
[ngClass]="{'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue}"></i> appInputVerbatim
</a> />
</div> <!-- Password -->
<div class="drag-handle" appA11yTitle="{{'dragToSort' | i18n}}" cdkDragHandle> <input
<i class="fa fa-bars" aria-hidden="true"></i> id="fieldValue{{ i }}"
</div> type="{{ f.showValue ? 'text' : 'password' }}"
</div> 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> </div>
<!-- Add new custom field --> <!-- Boolean -->
<div class="box-content-row" appBoxRow> <input
<a href="#" appStopClick (click)="addField()" role="button"> id="fieldValue{{ i }}"
<i class="fa fa-plus-circle fa-fw fa-lg" aria-hidden="true"></i> {{'newCustomField' | i18n}} name="Field.Value{{ i }}"
</a> type="checkbox"
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label> [(ngModel)]="f.value"
<select id="addFieldType" name="AddFieldType" [(ngModel)]="addFieldType" class="field-type"> *ngIf="f.type === fieldType.Boolean"
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{o.name}}</option> appTrueFalseValue
<option *ngIf="cipher.linkedFieldOptions != null" [ngValue]="addFieldLinkedTypeOption.value"> trueValue="true"
{{addFieldLinkedTypeOption.name}} falseValue="false"
</option> />
</select> <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>
<div class="drag-handle" appA11yTitle="{{ 'dragToSort' | i18n }}" cdkDragHandle>
<i class="fa fa-bars" aria-hidden="true"></i>
</div>
</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> </div>

View File

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

View File

@ -1,343 +1,603 @@
<form #form="ngForm" (ngSubmit)="submit()" [appApiAction]="formPromise"> <form #form="ngForm" (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content"> <div class="content">
<div class="inner-content" *ngIf="cipher"> <div class="inner-content" *ngIf="cipher">
<div class="box"> <div class="box">
<app-callout type="info" *ngIf="allowOwnershipOptions() && !allowPersonal"> <app-callout type="info" *ngIf="allowOwnershipOptions() && !allowPersonal">
{{'personalOwnershipPolicyInEffect' | i18n}} {{ "personalOwnershipPolicyInEffect" | i18n }}
</app-callout> </app-callout>
<div class="box-header"> <div class="box-header">
{{title}} {{ 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> </div>
</div> <div class="box-content">
<div class="footer"> <div class="box-content-row" *ngIf="!editMode" appBoxRow>
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" [disabled]="form.loading"> <label for="type">{{ "type" | i18n }}</label>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i> <select id="type" name="Type" [(ngModel)]="cipher.type">
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i> <option *ngFor="let o of typeOptions" [ngValue]="o.value">{{ o.name }}</option>
</button> </select>
<button appBlurClick type="button" (click)="cancel()"> </div>
{{'cancel' | i18n}} <div class="box-content-row" appBoxRow>
</button> <label for="name">{{ "name" | i18n }}</label>
<div class="right"> <input
<button appBlurClick type="button" (click)="share()" appA11yTitle="{{'moveToOrganization' | i18n}}" id="name"
*ngIf="editMode && cipher && !cipher.organizationId && !cloneMode"> type="text"
<i class="fa fa-arrow-circle-o-right fa-lg fa-fw" aria-hidden="true"></i> name="Name"
</button> [(ngModel)]="cipher.name"
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger" [appAutofocus]="!editMode"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode && !cloneMode" [disabled]="deleteBtn.loading" />
[appApiAction]="deletePromise"> </div>
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i> <!-- Login -->
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" aria-hidden="true"></i> <div *ngIf="cipher.type === cipherType.Login">
</button> <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>
<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>
<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> </form>

View File

@ -1,103 +1,125 @@
import { import { Component, NgZone, OnChanges, OnDestroy, ViewChild } from "@angular/core";
Component, import { NgForm } from "@angular/forms";
NgZone,
OnChanges,
OnDestroy,
ViewChild
} from '@angular/core';
import { NgForm } from '@angular/forms';
import { AuditService } from 'jslib-common/abstractions/audit.service'; import { AuditService } from "jslib-common/abstractions/audit.service";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from "jslib-common/abstractions/collection.service";
import { EventService } from 'jslib-common/abstractions/event.service'; import { EventService } from "jslib-common/abstractions/event.service";
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from 'jslib-common/abstractions/organization.service'; import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from 'jslib-common/abstractions/state.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({ @Component({
selector: 'app-vault-add-edit', selector: "app-vault-add-edit",
templateUrl: 'add-edit.component.html', templateUrl: "add-edit.component.html",
}) })
export class AddEditComponent extends BaseAddEditComponent implements OnChanges, OnDestroy { export class AddEditComponent extends BaseAddEditComponent implements OnChanges, OnDestroy {
@ViewChild('form') @ViewChild("form")
private form: NgForm; private form: NgForm;
constructor(cipherService: CipherService, folderService: FolderService, constructor(
i18nService: I18nService, platformUtilsService: PlatformUtilsService, cipherService: CipherService,
auditService: AuditService, stateService: StateService, folderService: FolderService,
collectionService: CollectionService, messagingService: MessagingService, i18nService: I18nService,
eventService: EventService, policyService: PolicyService, platformUtilsService: PlatformUtilsService,
passwordRepromptService: PasswordRepromptService, private broadcasterService: BroadcasterService, auditService: AuditService,
private ngZone: NgZone, logService: LogService, stateService: StateService,
organizationService: OrganizationService) { collectionService: CollectionService,
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService, messagingService: MessagingService,
collectionService, messagingService, eventService, policyService, logService, eventService: EventService,
passwordRepromptService, organizationService); 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() { async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => { this.ngZone.run(() => {
switch (message.command) { switch (message.command) {
case 'windowHidden': case "windowHidden":
this.onWindowHidden(); this.onWindowHidden();
break; break;
default: 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;
} }
super.load(); });
} });
// We use ngOnChanges for everything else instead.
}
onWindowHidden() { async ngOnChanges() {
this.showPassword = false; await super.init();
this.showCardNumber = false; await this.load();
this.showCardCode = false; }
if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach(field => {
field.showValue = false;
});
}
}
allowOwnershipOptions(): boolean { ngOnDestroy() {
return (!this.editMode || this.cloneMode) && this.ownershipOptions this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
&& (this.ownershipOptions.length > 1 || !this.allowPersonal); }
}
markPasswordAsDirty() { async load() {
this.form.controls['Login.Password'].markAsDirty(); 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() { onWindowHidden() {
this.platformUtilsService.launchUri('https://bitwarden.com/help/article/managing-items/#protect-individual-items'); 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 fade" role="dialog" aria-modal="true" aria-labelledby="attachmentsTitle">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body"> <div class="modal-body">
<div class="box" *ngIf="cipher && cipher.hasAttachments"> <div class="box" *ngIf="cipher && cipher.hasAttachments">
<div class="box-header" id="attachmentsTitle"> <div class="box-header" id="attachmentsTitle">
{{'attachments' | i18n}} {{ "attachments" | i18n }}
</div> </div>
<div class="box-content no-hover"> <div class="box-content no-hover">
<div class="box-content-row box-content-row-flex" *ngFor="let a of cipher.attachments"> <div class="box-content-row box-content-row-flex" *ngFor="let a of cipher.attachments">
<div class="row-main"> <div class="row-main">
{{a.fileName}} {{ a.fileName }}
</div> </div>
<small class="row-sub-label">{{a.sizeName}}</small> <small class="row-sub-label">{{ a.sizeName }}</small>
<div class="action-buttons no-pad"> <div class="action-buttons no-pad">
<button class="row-btn btn" type="button" appStopClick appBlurClick <button
appA11yTitle="{{'delete' | i18n}}" (click)="delete(a)" #deleteBtn class="row-btn btn"
[appApiAction]="deletePromises[a.id]" [disabled]="deleteBtn.loading"> type="button"
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" appStopClick
aria-hidden="true"></i> appBlurClick
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" appA11yTitle="{{ 'delete' | i18n }}"
aria-hidden="true"></i> (click)="delete(a)"
</button> #deleteBtn
</div> [appApiAction]="deletePromises[a.id]"
</div> [disabled]="deleteBtn.loading"
</div> >
</div> <i
<div class="box"> class="fa fa-trash-o fa-lg fa-fw"
<div class="box-header"> [hidden]="deleteBtn.loading"
{{'newAttachment' | i18n}} aria-hidden="true"
</div> ></i>
<div class="box-content no-hover"> <i
<div class="box-content-row"> class="fa fa-spinner fa-spin fa-lg fa-fw"
<label for="file">{{'file' | i18n}}</label> [hidden]="!deleteBtn.loading"
<input type="file" id="file" name="file" required> aria-hidden="true"
</div> ></i>
</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>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> </div>
</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> </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 { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.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({ @Component({
selector: 'app-vault-attachments', selector: "app-vault-attachments",
templateUrl: 'attachments.component.html', templateUrl: "attachments.component.html",
}) })
export class AttachmentsComponent extends BaseAttachmentsComponent { export class AttachmentsComponent extends BaseAttachmentsComponent {
constructor(cipherService: CipherService, i18nService: I18nService, constructor(
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService, cipherService: CipherService,
apiService: ApiService, logService: LogService, i18nService: I18nService,
stateService: StateService) { cryptoService: CryptoService,
super(cipherService, i18nService, cryptoService, platformUtilsService, platformUtilsService: PlatformUtilsService,
apiService, window, logService, stateService); apiService: ApiService,
} logService: LogService,
stateService: StateService
) {
super(
cipherService,
i18nService,
cryptoService,
platformUtilsService,
apiService,
window,
logService,
stateService
);
}
} }

View File

@ -1,39 +1,61 @@
<div class="content"> <div class="content">
<cdk-virtual-scroll-viewport itemSize="42" minBufferPx="400" maxBufferPx="600" *ngIf="ciphers.length"> <cdk-virtual-scroll-viewport
<div class="list"> itemSize="42"
<a *cdkVirtualFor="let c of ciphers; trackBy: trackByFn" appStopClick (click)="selectCipher(c)" minBufferPx="400"
(contextmenu)="rightClickCipher(c)" href="#" title="{{'viewItem' | i18n}}" maxBufferPx="600"
[ngClass]="{'active': c.id === activeCipherId}" class="flex-list-item virtual-scroll-item"> *ngIf="ciphers.length"
<app-vault-icon [cipher]="c"></app-vault-icon> >
<div class="flex-cipher-list-item"> <div class="list">
<span class="text"> <a
{{c.name}} *cdkVirtualFor="let c of ciphers; trackBy: trackByFn"
<ng-container *ngIf="c.organizationId"> appStopClick
<i class="fa fa-cube text-muted" title="{{'shared' | i18n}}" aria-hidden="true"></i> (click)="selectCipher(c)"
<span class="sr-only">{{'shared' | i18n}}</span> (contextmenu)="rightClickCipher(c)"
</ng-container> href="#"
<ng-container *ngIf="c.hasAttachments"> title="{{ 'viewItem' | i18n }}"
<i class="fa fa-paperclip text-muted" title="{{'attachments' | i18n}}" aria-hidden="true"></i> [ngClass]="{ active: c.id === activeCipherId }"
<span class="sr-only">{{'attachments' | i18n}}</span> class="flex-list-item virtual-scroll-item"
</ng-container> >
</span> <app-vault-icon [cipher]="c"></app-vault-icon>
<span *ngIf="c.subTitle" class="detail">{{c.subTitle}}</span> <div class="flex-cipher-list-item">
</div> <span class="text">
</a> {{ 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> </div>
</cdk-virtual-scroll-viewport> </a>
<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>
</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>
<div class="footer"> <div class="footer">
<button appBlurClick (click)="addCipher()" (contextmenu)="addCipherOptions()" <button
class="block primary" appA11yTitle="{{'addItem' | i18n}}" [disabled]="deleted"> appBlurClick
<i class="fa fa-plus fa-lg" aria-hidden="true"></i> (click)="addCipher()"
</button> (contextmenu)="addCipherOptions()"
class="block primary"
appA11yTitle="{{ 'addItem' | i18n }}"
[disabled]="deleted"
>
<i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
</div> </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 { CiphersComponent as BaseCiphersComponent } from "jslib-angular/components/ciphers.component";
import { SearchService } from 'jslib-common/abstractions/search.service'; 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({ @Component({
selector: 'app-vault-ciphers', selector: "app-vault-ciphers",
templateUrl: 'ciphers.component.html', templateUrl: "ciphers.component.html",
}) })
export class CiphersComponent extends BaseCiphersComponent { export class CiphersComponent extends BaseCiphersComponent {
constructor(searchService: SearchService, searchBarService: SearchBarService) {
super(searchService);
constructor(searchService: SearchService, searchBarService: SearchBarService) { searchBarService.searchText.subscribe((searchText) => {
super(searchService); this.searchText = searchText;
this.search(200);
});
}
searchBarService.searchText.subscribe(searchText => { trackByFn(index: number, c: CipherView) {
this.searchText = searchText; return c.id;
this.search(200); }
});
}
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 fade" role="dialog" aria-modal="true" aria-labelledby="collectionsTitle">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="collectionsTitle"> <div class="box-header" id="collectionsTitle">
{{'collections' | i18n}} {{ "collections" | i18n }}
</div> </div>
<div class="box-content" *ngIf="!collections || !collections.length"> <div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}} {{ "noCollectionsInList" | i18n }}
</div> </div>
<div class="box-content" *ngIf="collections && collections.length"> <div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox" <div
*ngFor="let c of collections; let i = index" appBoxRow> class="box-content-row box-content-row-checkbox"
<label for="collection_{{i}}">{{c.name}}</label> *ngFor="let c of collections; let i = index"
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked" appBoxRow
name="Collection[{{i}}].Checked"> >
</div> <label for="collection_{{ i }}">{{ c.name }}</label>
</div> <input
</div> id="collection_{{ i }}"
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
/>
</div> </div>
<div class="modal-footer"> </div>
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" </div>
[disabled]="form.loading"> </div>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i> <div class="modal-footer">
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i> <button
</button> appBlurClick
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button> type="submit"
</div> class="primary"
</form> appA11yTitle="{{ 'save' | i18n }}"
</div> [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>

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 { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({ @Component({
selector: 'app-vault-collections', selector: "app-vault-collections",
templateUrl: 'collections.component.html', templateUrl: "collections.component.html",
}) })
export class CollectionsComponent extends BaseCollectionsComponent { export class CollectionsComponent extends BaseCollectionsComponent {
constructor(cipherService: CipherService, i18nService: I18nService, constructor(
collectionService: CollectionService, platformUtilsService: PlatformUtilsService, cipherService: CipherService,
logService: LogService) { i18nService: I18nService,
super(collectionService, platformUtilsService, i18nService, cipherService, logService); 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 fade" role="dialog" aria-modal="true" aria-labelledby="exportTitle">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [formGroup]="exportForm"> <form class="modal-content" #form (ngSubmit)="submit()" [formGroup]="exportForm">
<div class="modal-body"> <div class="modal-body">
<app-callout type="warning" title="{{'vaultExportDisabled' | i18n}}" *ngIf="disabledByPolicy"> <app-callout
{{'personalVaultExportPolicyInEffect' | i18n}} type="warning"
</app-callout> title="{{ 'vaultExportDisabled' | i18n }}"
<div class="box"> *ngIf="disabledByPolicy"
<div class="box-header" id="exportTitle"> >
{{'exportVault' | i18n}} {{ "personalVaultExportPolicyInEffect" | i18n }}
</div> </app-callout>
<div class="box-content"> <div class="box">
<div class="box-content-row" appBoxRow> <div class="box-header" id="exportTitle">
<label for="format">{{'fileFormat' | i18n}}</label> {{ "exportVault" | i18n }}
<select class="form-control" id="format" name="Format" formControlName="format"> </div>
<option *ngFor="let f of formatOptions" [value]="f.value">{{f.name}}</option> <div class="box-content">
</select> <div class="box-content-row" appBoxRow>
</div> <label for="format">{{ "fileFormat" | i18n }}</label>
<app-verify-master-password ngDefaultControl formControlName="secret" name="secret"> <select class="form-control" id="format" name="Format" formControlName="format">
</app-verify-master-password> <option *ngFor="let f of formatOptions" [value]="f.value">{{ f.name }}</option>
</div> </select>
<div class="box-footer">
<p>{{'confirmIdentity' | i18n}}</p>
</div>
</div>
</div> </div>
<div class="modal-footer"> <app-verify-master-password ngDefaultControl formControlName="secret" name="secret">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'submit' | i18n}}" </app-verify-master-password>
[disabled]="disabledByPolicy"> </div>
<i class="fa fa-download fa-lg fa-fw" aria-hidden="true"></i> <div class="box-footer">
</button> <p>{{ "confirmIdentity" | i18n }}</p>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button> </div>
</div> </div>
</form> </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> </div>

View File

@ -1,57 +1,78 @@
import { import { Component, OnInit } from "@angular/core";
Component, import { FormBuilder } from "@angular/forms";
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 { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from 'jslib-common/abstractions/event.service'; import { EventService } from "jslib-common/abstractions/event.service";
import { ExportService } from 'jslib-common/abstractions/export.service'; import { ExportService } from "jslib-common/abstractions/export.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.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({ @Component({
selector: 'app-export', selector: "app-export",
templateUrl: 'export.component.html', templateUrl: "export.component.html",
}) })
export class ExportComponent extends BaseExportComponent implements OnInit { export class ExportComponent extends BaseExportComponent implements OnInit {
constructor(cryptoService: CryptoService, i18nService: I18nService, constructor(
platformUtilsService: PlatformUtilsService, exportService: ExportService, cryptoService: CryptoService,
eventService: EventService, policyService: PolicyService, i18nService: I18nService,
userVerificationService: UserVerificationService, fb: FormBuilder, platformUtilsService: PlatformUtilsService,
private broadcasterService: BroadcasterService, logService: LogService) { exportService: ExportService,
super(cryptoService, i18nService, platformUtilsService, exportService, eventService, eventService: EventService,
policyService, window, logService, userVerificationService, fb); policyService: PolicyService,
} userVerificationService: UserVerificationService,
fb: FormBuilder,
private broadcasterService: BroadcasterService,
logService: LogService
) {
super(
cryptoService,
i18nService,
platformUtilsService,
exportService,
eventService,
policyService,
window,
logService,
userVerificationService,
fb
);
}
ngOnDestroy() { ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
} }
async warningDialog() { async warningDialog() {
if (this.encryptedFormat) { if (this.encryptedFormat) {
return await this.platformUtilsService.showDialog( return await this.platformUtilsService.showDialog(
this.i18nService.t('encExportKeyWarningDesc') + this.i18nService.t("encExportKeyWarningDesc") +
os.EOL + os.EOL + os.EOL +
this.i18nService.t('encExportAccountWarningDesc'), os.EOL +
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'), this.i18nService.t("encExportAccountWarningDesc"),
this.i18nService.t('cancel'), 'warning', this.i18nService.t("confirmVaultExport"),
true); this.i18nService.t("exportVault"),
} else { this.i18nService.t("cancel"),
return await this.platformUtilsService.showDialog( "warning",
this.i18nService.t('exportWarningDesc'), true
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'), );
this.i18nService.t('cancel'), 'warning'); } 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 fade" role="dialog" aria-modal="true" aria-labelledby="folderAddEditTitle">
<div class="modal-dialog modal-sm" role="document"> <div class="modal-dialog modal-sm" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="folderAddEditTitle"> <div class="box-header" id="folderAddEditTitle">
{{title}} {{ title }}
</div> </div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label> <label for="name">{{ "name" | i18n }}</label>
<input id="name" type="text" name="Name" [(ngModel)]="folder.name" <input
[appAutofocus]="!editMode"> id="name"
</div> type="text"
</div> name="Name"
</div> [(ngModel)]="folder.name"
[appAutofocus]="!editMode"
/>
</div> </div>
<div class="modal-footer"> </div>
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" </div>
[disabled]="form.loading"> </div>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i> <div class="modal-footer">
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i> <button
</button> appBlurClick
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button> type="submit"
<div class="right"> class="primary"
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger" appA11yTitle="{{ 'save' | i18n }}"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading" [disabled]="form.loading"
[appApiAction]="deletePromise"> >
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i> <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]="!deleteBtn.loading" <i
aria-hidden="true"></i> class="fa fa-spinner fa-spin fa-lg fa-fw"
</button> [hidden]="!form.loading"
</div> aria-hidden="true"
</div> ></i>
</form> </button>
</div> <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>

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

View File

@ -1,104 +1,146 @@
<div class="mac-bar"></div> <div class="mac-bar"></div>
<div class="content"> <div class="content">
<div class="inner-content"> <div class="inner-content">
<h2 class="sr-only">{{'filters' | i18n}}</h2> <h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul> <ul>
<li [ngClass]="{active: selectedAll}"> <li [ngClass]="{ active: selectedAll }">
<a href="#" appStopClick appBlurClick (click)="selectAll()"> <a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{'allItems' | i18n}} <i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{ "allItems" | i18n }}
</a> </a>
</li> </li>
<li [ngClass]="{active: selectedFavorites}"> <li [ngClass]="{ active: selectedFavorites }">
<a href="#" appStopClick appBlurClick (click)="selectFavorites()"> <a href="#" appStopClick appBlurClick (click)="selectFavorites()">
<i class="fa fa-fw fa-star" aria-hidden="true"></i>&nbsp;{{'favorites' | i18n}} <i class="fa fa-fw fa-star" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
</a> </a>
</li> </li>
<li [ngClass]="{active: selectedTrash}"> <li [ngClass]="{ active: selectedTrash }">
<a href="#" appStopClick appBlurClick (click)="selectTrash()"> <a href="#" appStopClick appBlurClick (click)="selectTrash()">
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>&nbsp;{{'trash' | i18n}} <i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>&nbsp;{{ "trash" | i18n }}
</a> </a>
</li> </li>
</ul> </ul>
<h2>{{'types' | i18n}}</h2> <h2>{{ "types" | i18n }}</h2>
<ul> <ul>
<li [ngClass]="{active: selectedType === cipherType.Login}"> <li [ngClass]="{ active: selectedType === cipherType.Login }">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Login)"> <a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Login)">
<i class="fa fa-fw fa-globe" aria-hidden="true"></i>&nbsp;{{'typeLogin' | i18n}} <i class="fa fa-fw fa-globe" aria-hidden="true"></i>&nbsp;{{ "typeLogin" | i18n }}
</a> </a>
</li> </li>
<li [ngClass]="{active: selectedType === cipherType.Card}"> <li [ngClass]="{ active: selectedType === cipherType.Card }">
<a href="#" appStopClick appBlurClick (click)="selectType(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}} <i class="fa fa-fw fa-credit-card" aria-hidden="true"></i>&nbsp;{{ "typeCard" | i18n }}
</a> </a>
</li> </li>
<li [ngClass]="{active: selectedType === cipherType.Identity}"> <li [ngClass]="{ active: selectedType === cipherType.Identity }">
<a href="#" appStopClick appBlurClick (click)="selectType(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}} <i class="fa fa-fw fa-id-card-o" aria-hidden="true"></i>&nbsp;{{ "typeIdentity" | i18n }}
</a> </a>
</li> </li>
<li [ngClass]="{active: selectedType === cipherType.SecureNote}"> <li [ngClass]="{ active: selectedType === cipherType.SecureNote }">
<a href="#" appStopClick appBlurClick (click)="selectType(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}} <i class="fa fa-fw fa-sticky-note-o" aria-hidden="true"></i>&nbsp;{{
</a> "typeSecureNote" | i18n
</li> }}
</ul> </a>
<p *ngIf="!loaded" class="text-muted">{{'loading' | i18n}}</p> </li>
<ng-container *ngIf="loaded"> </ul>
<div class="heading"> <p *ngIf="!loaded" class="text-muted">{{ "loading" | i18n }}</p>
<h2>{{'folders' | i18n}}</h2> <ng-container *ngIf="loaded">
<button appBlurClick (click)="addFolder()" appA11yTitle="{{'addFolder' | i18n}}"> <div class="heading">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i> <h2>{{ "folders" | i18n }}</h2>
</button> <button appBlurClick (click)="addFolder()" appA11yTitle="{{ 'addFolder' | i18n }}">
</div> <i class="fa fa-plus fa-fw" aria-hidden="true"></i>
<ul> </button>
<ng-template #recursiveFolders let-folders> </div>
<li *ngFor="let f of folders" <ul>
[ngClass]="{active: selectedFolder && f.node.id === selectedFolderId}"> <ng-template #recursiveFolders let-folders>
<a href="#" appStopClick appBlurClick (click)="selectFolder(f.node)"> <li
<i *ngIf="f.children.length" class="fa-fw fa" title="{{'toggleCollapse' | i18n}}" aria-hidden="true" *ngFor="let f of folders"
[ngClass]="{'fa-caret-right': isCollapsed(f.node), 'fa-caret-down': !isCollapsed(f.node)}" [ngClass]="{ active: selectedFolder && f.node.id === selectedFolderId }"
(click)="collapse(f.node)" appStopProp></i> >
<i *ngIf="f.children.length === 0" class="fa-fw fa fa-folder-o" aria-hidden="true"></i> <a href="#" appStopClick appBlurClick (click)="selectFolder(f.node)">
&nbsp;{{f.node.name}} <i
<span appStopProp appStopClick (click)="editFolder(f.node)" role="button" *ngIf="f.children.length"
appA11yTitle="{{'editFolder' | i18n}}" *ngIf="f.node.id"> class="fa-fw fa"
<i class="fa fa-pencil fa-fw" aria-hidden="true"></i> title="{{ 'toggleCollapse' | i18n }}"
</span> aria-hidden="true"
</a> [ngClass]="{
<ul class="fa-ul" *ngIf="f.children.length && !isCollapsed(f.node)"> 'fa-caret-right': isCollapsed(f.node),
<ng-container *ngTemplateOutlet="recursiveFolders; context:{ $implicit: f.children }"> 'fa-caret-down': !isCollapsed(f.node)
</ng-container> }"
</ul> (click)="collapse(f.node)"
</li> appStopProp
</ng-template> ></i>
<ng-container *ngTemplateOutlet="recursiveFolders; context:{ $implicit: nestedFolders }"></ng-container> <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> </ul>
<div *ngIf="collections && collections.length"> </li>
<h2>{{'collections' | i18n}}</h2> </ng-template>
<ul> <ng-container
<ng-template #recursiveCollections let-collections> *ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }"
<li *ngFor="let c of collections" [ngClass]="{active: c.node.id === selectedCollectionId}"> ></ng-container>
<a href="#" appStopClick appBlurClick (click)="selectCollection(c.node)"> </ul>
<i *ngIf="c.children.length" class="fa-fw fa" title="{{'toggleCollapse' | i18n}}" aria-hidden="true" <div *ngIf="collections && collections.length">
[ngClass]="{'fa-caret-right': isCollapsed(c.node), 'fa-caret-down': !isCollapsed(c.node)}" <h2>{{ "collections" | i18n }}</h2>
(click)="collapse(c.node)" appStopProp></i> <ul>
<i *ngIf="c.children.length === 0" class="fa-fw fa fa-cube" aria-hidden="true"></i> <ng-template #recursiveCollections let-collections>
&nbsp;{{c.node.name}} <li
</a> *ngFor="let c of collections"
<ul class="fa-ul" *ngIf="c.children.length && !isCollapsed(c.node)"> [ngClass]="{ active: c.node.id === selectedCollectionId }"
<ng-container >
*ngTemplateOutlet="recursiveCollections; context:{ $implicit: c.children }"> <a href="#" appStopClick appBlurClick (click)="selectCollection(c.node)">
</ng-container> <i
</ul> *ngIf="c.children.length"
</li> class="fa-fw fa"
</ng-template> title="{{ 'toggleCollapse' | i18n }}"
<ng-container *ngTemplateOutlet="recursiveCollections; context:{ $implicit: nestedCollections }"> aria-hidden="true"
</ng-container> [ngClass]="{
</ul> 'fa-caret-right': isCollapsed(c.node),
</div> 'fa-caret-down': !isCollapsed(c.node)
</ng-container> }"
</div> (click)="collapse(c.node)"
<div class="footer"> appStopProp
<app-nav class="nav"></app-nav> ></i>
</div> <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> </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 { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from "jslib-common/abstractions/folder.service";
import { StateService } from 'jslib-common/abstractions/state.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({ @Component({
selector: 'app-vault-groupings', selector: "app-vault-groupings",
templateUrl: 'groupings.component.html', templateUrl: "groupings.component.html",
}) })
export class GroupingsComponent extends BaseGroupingsComponent { export class GroupingsComponent extends BaseGroupingsComponent {
constructor(collectionService: CollectionService, folderService: FolderService, constructor(
stateService: StateService) { collectionService: CollectionService,
super(collectionService, folderService, stateService); 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 fade" role="dialog" aria-modal="true" aria-labelledby="passwordGenHistoryTitle">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="passwordGenHistoryTitle"> <div class="box-header" id="passwordGenHistoryTitle">
{{'passwordHistory' | i18n}} {{ "passwordHistory" | i18n }}
</div> </div>
<div class="box-content condensed"> <div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history"> <div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main"> <div class="row-main">
<div class="password-wrapper monospaced" appSelectCopy <div
[innerHTML]="h.password | colorPassword"></div> class="password-wrapper monospaced"
<span class="detail">{{h.date | date:'medium'}}</span> appSelectCopy
</div> [innerHTML]="h.password | colorPassword"
<div class="action-buttons"> ></div>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}" <span class="detail">{{ h.date | date: "medium" }}</span>
(click)="copy(h.password)" role="button"> </div>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> <div class="action-buttons">
</a> <a
</div> class="row-btn"
</div> href="#"
<div class="box-content-row" *ngIf="!history.length"> appStopClick
{{'noPasswordsInList' | i18n}} appA11yTitle="{{ 'copyPassword' | i18n }}"
</div> (click)="copy(h.password)"
</div> role="button"
</div> >
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div> </div>
<div class="modal-footer"> <div class="box-content-row" *ngIf="!history.length">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> {{ "noPasswordsInList" | i18n }}
<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> </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>
</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 { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "jslib-angular/components/password-generator-history.component";
PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent,
} from 'jslib-angular/components/password-generator-history.component';
@Component({ @Component({
selector: 'app-password-generator-history', selector: "app-password-generator-history",
templateUrl: 'password-generator-history.component.html', templateUrl: "password-generator-history.component.html",
}) })
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent { export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, constructor(
i18nService: I18nService) { passwordGenerationService: PasswordGenerationService,
super(passwordGenerationService, platformUtilsService, i18nService, window); 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
<div class="modal-dialog modal-sm" role="document"> class="modal fade"
<div class="modal-content"> role="dialog"
<div class="modal-body"> aria-modal="true"
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()"> attr.aria-label="{{ 'generatePassword' | i18n }}"
{{'passwordGeneratorPolicyInEffect' | i18n}} >
</app-callout> <div class="modal-dialog modal-sm" role="document">
<div class="password-block"> <div class="modal-content">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div> <div class="modal-body">
</div> <app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
<div class="box"> {{ "passwordGeneratorPolicyInEffect" | i18n }}
<div class="box-content condensed"> </app-callout>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="regenerate()"> <div class="password-block">
<i class="fa fa-fw fa-refresh" aria-hidden="true"></i> {{'regeneratePassword' | i18n}} <div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
</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 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>
</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 { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
PasswordGeneratorComponent as BasePasswordGeneratorComponent,
} from 'jslib-angular/components/password-generator.component';
@Component({ @Component({
selector: 'app-password-generator', selector: "app-password-generator",
templateUrl: 'password-generator.component.html', templateUrl: "password-generator.component.html",
}) })
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent { export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, constructor(
i18nService: I18nService) { passwordGenerationService: PasswordGenerationService,
super(passwordGenerationService, platformUtilsService, i18nService, window); 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 fade" role="dialog" aria-modal="true" aria-labelledby="passwordHistoryTitle">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="passwordHistoryTitle"> <div class="box-header" id="passwordHistoryTitle">
{{'passwordHistory' | i18n}} {{ "passwordHistory" | i18n }}
</div> </div>
<div class="box-content condensed"> <div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history"> <div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main"> <div class="row-main">
<span class="text monospaced"> <span class="text monospaced">
{{h.password}} {{ h.password }}
</span> </span>
<span class="detail">{{h.lastUsedDate | date:'medium'}}</span> <span class="detail">{{ h.lastUsedDate | date: "medium" }}</span>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}" <a
(click)="copy(h.password)" role="button"> class="row-btn"
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> href="#"
</a> appStopClick
</div> appA11yTitle="{{ 'copyPassword' | i18n }}"
</div> (click)="copy(h.password)"
<div class="box-content-row" *ngIf="!history.length"> role="button"
{{'noPasswordsInList' | i18n}} >
</div> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</div> </a>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="box-content-row" *ngIf="!history.length">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> {{ "noPasswordsInList" | i18n }}
</div> </div>
</div>
</div> </div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div> </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 { CipherService } from "jslib-common/abstractions/cipher.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "jslib-angular/components/password-history.component";
PasswordHistoryComponent as BasePasswordHistoryComponent,
} from 'jslib-angular/components/password-history.component';
@Component({ @Component({
selector: 'app-password-history', selector: "app-password-history",
templateUrl: 'password-history.component.html', templateUrl: "password-history.component.html",
}) })
export class PasswordHistoryComponent extends BasePasswordHistoryComponent { export class PasswordHistoryComponent extends BasePasswordHistoryComponent {
constructor(cipherService: CipherService, platformUtilsService: PlatformUtilsService, constructor(
i18nService: I18nService) { cipherService: CipherService,
super(cipherService, platformUtilsService, i18nService, window); 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 fade" role="dialog" aria-modal="true" aria-labelledby="moveToOrgTitle">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="moveToOrgTitle"> <div class="box-header" id="moveToOrgTitle">
{{'moveToOrganization' | i18n}} {{ "moveToOrganization" | i18n }}
</div> </div>
<div class="box-content" *ngIf="!organizations || !organizations.length"> <div class="box-content" *ngIf="!organizations || !organizations.length">
<div class="box-content-row"> <div class="box-content-row">
{{'noOrganizationsList' | i18n}} {{ "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> </div>
<div class="modal-footer"> </div>
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" <div class="box-content" *ngIf="organizations && organizations.length">
[disabled]="form.loading || !canSave" *ngIf="organizations && organizations.length"> <div class="box-content-row" appBoxRow>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i> <label for="organization">{{ "organization" | i18n }}</label>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i> <select
</button> id="organization"
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button> name="OrganizationId"
[(ngModel)]="organizationId"
(change)="filterCollections()"
>
<option *ngFor="let o of organizations" [ngValue]="o.id">{{ o.name }}</option>
</select>
</div> </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> </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 { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from 'jslib-common/abstractions/organization.service'; import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({ @Component({
selector: 'app-vault-share', selector: "app-vault-share",
templateUrl: 'share.component.html', templateUrl: "share.component.html",
}) })
export class ShareComponent extends BaseShareComponent { export class ShareComponent extends BaseShareComponent {
constructor(cipherService: CipherService, i18nService: I18nService, constructor(
collectionService: CollectionService, platformUtilsService: PlatformUtilsService, cipherService: CipherService,
logService: LogService, organizationService: OrganizationService) { i18nService: I18nService,
super(collectionService, platformUtilsService, i18nService, cipherService, collectionService: CollectionService,
logService, organizationService); 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}}"> <div id="vault" class="vault" attr.aria-hidden="{{ showingModal }}">
<app-vault-groupings id="groupings" class="groupings" (onAllClicked)="clearGroupingFilters()" (onFavoritesClicked)="filterFavorites()" <app-vault-groupings
(onCipherTypeClicked)="filterCipherType($event)" (onFolderClicked)="filterFolder($event.id)" id="groupings"
(onAddFolder)="addFolder()" (onEditFolder)="editFolder($event.id)" class="groupings"
(onCollectionClicked)="filterCollection($event.id)" (onTrashClicked)="filterDeleted()"> (onAllClicked)="clearGroupingFilters()"
</app-vault-groupings> (onFavoritesClicked)="filterFavorites()"
<app-vault-ciphers id="items" class="items" [activeCipherId]="cipherId" (onCipherClicked)="viewCipher($event)" (onCipherTypeClicked)="filterCipherType($event)"
(onCipherRightClicked)="viewCipherMenu($event)" (onAddCipher)="addCipher($event)" (onFolderClicked)="filterFolder($event.id)"
(onAddCipherOptions)="addCipherOptions()"> (onAddFolder)="addFolder()"
</app-vault-ciphers> (onEditFolder)="editFolder($event.id)"
<app-vault-view id="details" class="details" *ngIf="cipherId && action === 'view'" [cipherId]="cipherId" (onCollectionClicked)="filterCollection($event.id)"
(onCloneCipher)="cloneCipherWithoutPasswordPrompt($event)" (onEditCipher)="editCipherWithoutPasswordPrompt($event)" (onTrashClicked)="filterDeleted()"
(onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)" (onRestoredCipher)="restoredCipher($event)" >
(onDeletedCipher)="deletedCipher($event)"> </app-vault-groupings>
</app-vault-view> <app-vault-ciphers
<app-vault-add-edit id="addEdit" class="details" *ngIf="action === 'add' || action === 'edit' || action === 'clone'" id="items"
[cloneMode]="action === 'clone'" class="items"
[folderId]="action === 'add' && folderId !== 'none' ? folderId : null" [activeCipherId]="cipherId"
[organizationId]="action === 'add' ? addOrganizationId : null" (onCipherClicked)="viewCipher($event)"
[collectionIds]="action === 'add' ? addCollectionIds : null" (onCipherRightClicked)="viewCipherMenu($event)"
[type]="action === 'add' ? (addType ? addType : type) : null" [cipherId]="(action === 'edit' || action === 'clone') ? cipherId : null" (onAddCipher)="addCipher($event)"
(onSavedCipher)="savedCipher($event)" (onDeletedCipher)="deletedCipher($event)" (onAddCipherOptions)="addCipherOptions()"
(onEditAttachments)="editCipherAttachments($event)" (onCancelled)="cancelledAddEdit($event)" >
(onShareCipher)="shareCipher($event)" (onEditCollections)="cipherCollections($event)" </app-vault-ciphers>
(onGeneratePassword)="openPasswordGenerator(true)"> <app-vault-view
</app-vault-add-edit> id="details"
<div id="logo" class="logo" *ngIf="action !== 'add' && action !== 'edit' && action !== 'view' && action !== 'clone'"> class="details"
<div class="content"> *ngIf="cipherId && action === 'view'"
<div class="inner-content"> [cipherId]="cipherId"
<img class="logo-image" alt="Bitwarden" aria-hidden="true" /> (onCloneCipher)="cloneCipherWithoutPasswordPrompt($event)"
</div> (onEditCipher)="editCipherWithoutPasswordPrompt($event)"
</div> (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>
</div> </div>
<ng-template #passwordGenerator></ng-template> <ng-template #passwordGenerator></ng-template>
<ng-template #attachments></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">
<div class="box-header"> <div class="box-header">
{{'customFields' | i18n}} {{ "customFields" | i18n }}
</div> </div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let field of cipher.fields"> <div class="box-content-row box-content-row-flex" *ngFor="let field of cipher.fields">
<div class="row-main"> <div class="row-main">
<span class="row-label">{{field.name}}</span> <span class="row-label">{{ field.name }}</span>
<div *ngIf="field.type === fieldType.Text"> <div *ngIf="field.type === fieldType.Text">
{{field.value || '&nbsp;'}} {{ 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 *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>
</div> </div>

View File

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

View File

@ -1,265 +1,422 @@
<div class="content"> <div class="content">
<div class="inner-content" *ngIf="cipher"> <div class="inner-content" *ngIf="cipher">
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
{{'itemInformation' | i18n}} {{ "itemInformation" | i18n }}
</div> </div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row"> <div class="box-content-row">
<span class="row-label">{{'name' | i18n}}</span> <span class="row-label">{{ "name" | i18n }}</span>
{{cipher.name}} {{ 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> </div>
<div class="box" *ngIf="cipher.login && cipher.login.hasUris"> <!-- Login -->
<div class="box-content"> <div *ngIf="cipher.login">
<div class="box-content-row box-content-row-flex" *ngFor="let u of cipher.login.uris; let i = index"> <div class="box-content-row box-content-row-flex" *ngIf="cipher.login.username">
<div class="row-main"> <div class="row-main">
<span class="row-label" *ngIf="!u.isWebsite">{{'uri' | i18n}}</span> <span
<span class="row-label" *ngIf="u.isWebsite">{{'website' | i18n}}</span> class="row-label draggable"
<span title="{{u.uri}}">{{u.hostOrUri}}</span> draggable="true"
</div> (dragstart)="setTextDataOnDrag($event, cipher.login.username)"
<div class="action-buttons"> >{{ "username" | i18n }}</span
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'launch' | i18n}}" *ngIf="u.canLaunch" >
(click)="launch(u)" role="button"> {{ cipher.login.username }}
<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="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>
<div class="box" *ngIf="cipher.notes"> <!-- Card -->
<div class="box-header"> <div *ngIf="cipher.card">
{{'notes' | i18n}} <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>
<div class="box-content"> <div class="action-buttons">
<div class="box-content-row pre-wrap">{{cipher.notes}}</div> <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>
<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> </div>
<app-vault-view-custom-fields *ngIf="cipher.hasFields" [cipher]="cipher" <!-- Identity -->
[promptPassword]="promptPassword.bind(this)" [copy]="copy.bind(this)"> <div *ngIf="cipher.identity">
</app-vault-view-custom-fields> <div class="box-content-row" *ngIf="cipher.identity.fullName">
<div class="box" *ngIf="cipher.hasAttachments && (canAccessPremium || cipher.organizationId)"> <span class="row-label">{{ "identityName" | i18n }}</span>
<div class="box-header"> {{ cipher.identity.fullName }}
{{'attachments' | i18n}} </div>
</div> <div class="box-content-row" *ngIf="cipher.identity.username">
<div class="box-content"> <span class="row-label">{{ "username" | i18n }}</span>
<a class="box-content-row box-content-row-flex text-default" {{ cipher.identity.username }}
*ngFor="let attachment of cipher.attachments" href="#" appStopClick appBlurCLick </div>
(click)="downloadAttachment(attachment)"> <div class="box-content-row" *ngIf="cipher.identity.company">
<span class="row-main">{{attachment.fileName}}</span> <span class="row-label">{{ "company" | i18n }}</span>
<small class="row-sub-label">{{attachment.sizeName}}</small> {{ cipher.identity.company }}
<i class="fa fa-download fa-fw row-sub-icon" *ngIf="!attachment.downloading" aria-hidden="true"></i> </div>
<i class="fa fa-spinner fa-fw fa-spin row-sub-icon" *ngIf="attachment.downloading" <div class="box-content-row" *ngIf="cipher.identity.ssn">
aria-hidden="true"></i> <span class="row-label">{{ "ssn" | i18n }}</span>
</a> {{ cipher.identity.ssn }}
</div> </div>
</div> <div class="box-content-row" *ngIf="cipher.identity.passportNumber">
<div class="box"> <span class="row-label">{{ "passportNumber" | i18n }}</span>
<div class="box-footer"> {{ cipher.identity.passportNumber }}
<div> </div>
<b class="font-weight-semibold">{{'dateUpdated' | i18n}}:</b> <div class="box-content-row" *ngIf="cipher.identity.licenseNumber">
{{cipher.revisionDate | date:'medium'}} <span class="row-label">{{ "licenseNumber" | i18n }}</span>
</div> {{ cipher.identity.licenseNumber }}
<div *ngIf="cipher.passwordRevisionDisplayDate"> </div>
<b class="font-weight-semibold">{{'datePasswordUpdated' | i18n}}:</b> <div class="box-content-row" *ngIf="cipher.identity.email">
{{cipher.passwordRevisionDisplayDate | date:'medium'}} <span class="row-label">{{ "email" | i18n }}</span>
</div> {{ cipher.identity.email }}
<div *ngIf="cipher.hasPasswordHistory"> </div>
<b class="font-weight-semibold">{{'passwordHistory' | i18n}}:</b> <div class="box-content-row" *ngIf="cipher.identity.phone">
<a href="#" (click)="viewHistory()" appStopClick role="button" <span class="row-label">{{ "phone" | i18n }}</span>
appA11yTitle="{{'passwordHistory' | i18n}}, {{cipher.passwordHistory.length}}"> {{ cipher.identity.phone }}
<span aria-hidden="true">{{cipher.passwordHistory.length}}</span> </div>
</a> <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>
<div *ngIf="cipher.identity.country">{{ cipher.identity.country }}</div>
</div>
</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>
<div class="footer" *ngIf="cipher"> <div class="footer" *ngIf="cipher">
<button appBlurClick class="primary" (click)="edit()" appA11yTitle="{{'edit' | i18n}}" *ngIf="!cipher.isDeleted"> <button
<i class="fa fa-pencil fa-fw fa-lg" aria-hidden="true"></i> 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>
<button appBlurClick class="primary" (click)="restore()" appA11yTitle="{{'restore' | i18n}}" </div>
*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 { import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
EventEmitter, EventEmitter,
NgZone, NgZone,
OnChanges, OnChanges,
Output, Output,
} from '@angular/core'; } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuditService } from 'jslib-common/abstractions/audit.service'; import { AuditService } from "jslib-common/abstractions/audit.service";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from 'jslib-common/abstractions/event.service'; import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { TokenService } from 'jslib-common/abstractions/token.service'; import { TokenService } from "jslib-common/abstractions/token.service";
import { TotpService } from 'jslib-common/abstractions/totp.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({ @Component({
selector: 'app-vault-view', selector: "app-vault-view",
templateUrl: 'view.component.html', templateUrl: "view.component.html",
}) })
export class ViewComponent extends BaseViewComponent implements OnChanges { export class ViewComponent extends BaseViewComponent implements OnChanges {
@Output() onViewCipherPasswordHistory = new EventEmitter<CipherView>(); @Output() onViewCipherPasswordHistory = new EventEmitter<CipherView>();
constructor(cipherService: CipherService, totpService: TotpService, constructor(
tokenService: TokenService, i18nService: I18nService, cipherService: CipherService,
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService, totpService: TotpService,
auditService: AuditService, broadcasterService: BroadcasterService, tokenService: TokenService,
ngZone: NgZone, changeDetectorRef: ChangeDetectorRef, i18nService: I18nService,
eventService: EventService, apiService: ApiService, cryptoService: CryptoService,
private messagingService: MessagingService, passwordRepromptService: PasswordRepromptService, platformUtilsService: PlatformUtilsService,
logService: LogService, stateService: StateService) { auditService: AuditService,
super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService, broadcasterService: BroadcasterService,
auditService, window, broadcasterService, ngZone, changeDetectorRef, eventService, ngZone: NgZone,
apiService, passwordRepromptService, logService, stateService); changeDetectorRef: ChangeDetectorRef,
} eventService: EventService,
ngOnInit() { apiService: ApiService,
super.ngOnInit(); private messagingService: MessagingService,
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { passwordRepromptService: PasswordRepromptService,
this.ngZone.run(() => { logService: LogService,
switch (message.command) { stateService: StateService
case 'windowHidden': ) {
this.onWindowHidden(); super(
break; cipherService,
default: totpService,
} tokenService,
}); i18nService,
}); cryptoService,
} platformUtilsService,
auditService,
ngOnDestroy() { window,
super.ngOnDestroy(); broadcasterService,
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); ngZone,
} changeDetectorRef,
eventService,
async ngOnChanges() { apiService,
await super.load(); passwordRepromptService,
} logService,
stateService
viewHistory() { );
this.onViewCipherPasswordHistory.emit(this.cipher); }
} ngOnInit() {
super.ngOnInit();
async copy(value: string, typeI18nKey: string, aType: string) { this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
super.copy(value, typeI18nKey, aType); this.ngZone.run(() => {
this.messagingService.send('minimizeOnCopy'); switch (message.command) {
} case "windowHidden":
this.onWindowHidden();
onWindowHidden() { break;
this.showPassword = false; default:
this.showCardNumber = false;
this.showCardCode = false;
if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach(field => {
field.showValue = false;
});
} }
});
});
}
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 // We need to import the other dependencies using `require` since `import` will
// generate `Error: Cannot find module 'electron'`. The cause of this error is // 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 // which removes the electron module. This flag is needed for stdin/out to work
// properly on Windows. // properly on Windows.
if (process.argv.some(arg => arg.indexOf('chrome-extension://') !== -1 || arg.indexOf('{') !== -1)) { if (
if (process.platform === 'darwin') { process.argv.some((arg) => arg.indexOf("chrome-extension://") !== -1 || arg.indexOf("{") !== -1)
// tslint:disable-next-line ) {
const app = require('electron').app; if (process.platform === "darwin") {
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 // tslint:disable-next-line
const Main = require('./main').Main; const app = require("electron").app;
const main = new Main(); app.on("ready", () => {
main.bootstrap(); 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> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; <meta
img-src 'self' data: *; child-src *; frame-src *; connect-src *;"> http-equiv="Content-Security-Policy"
<meta name="viewport" content="width=device-width, initial-scale=1"> 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> <title>Bitwarden</title>
<base href=""> <base href="" />
</head> </head>
<body class="layout_frontend"> <body class="layout_frontend">
<app-root> <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> </app-root>
</body> </body>
</html> </html>

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