mirror of
https://github.com/bitwarden/web
synced 2025-12-12 22:33:23 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfa3d81cf8 | ||
|
|
12c0049b9e | ||
|
|
7f7ed9da92 | ||
|
|
ae4ce3e575 | ||
|
|
d5325164ff |
238
.github/workflows/build.yml
vendored
238
.github/workflows/build.yml
vendored
@@ -15,7 +15,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
cloc:
|
cloc:
|
||||||
name: CLOC
|
name: CLOC
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
@@ -29,131 +29,9 @@ jobs:
|
|||||||
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
||||||
|
|
||||||
|
|
||||||
setup:
|
build-selfhost:
|
||||||
name: Setup
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
outputs:
|
|
||||||
version: ${{ steps.version.outputs.value }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
|
||||||
|
|
||||||
- name: Get GitHub sha as version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=value::${GITHUB_SHA:0:7}"
|
|
||||||
|
|
||||||
|
|
||||||
build-oss-selfhost:
|
|
||||||
name: Build OSS zip
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
needs: setup
|
|
||||||
env:
|
|
||||||
_VERSION: ${{ needs.setup.outputs.version }}
|
|
||||||
steps:
|
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
|
||||||
with:
|
|
||||||
node-version: '14'
|
|
||||||
|
|
||||||
- name: Update NPM
|
|
||||||
run: |
|
|
||||||
npm install -g npm@7
|
|
||||||
|
|
||||||
- name: Cache npm
|
|
||||||
id: npm-cache
|
|
||||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
|
||||||
with:
|
|
||||||
path: '~/.npm'
|
|
||||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
|
|
||||||
- name: Print environment
|
|
||||||
run: |
|
|
||||||
whoami
|
|
||||||
node --version
|
|
||||||
npm --version
|
|
||||||
gulp --version
|
|
||||||
docker --version
|
|
||||||
echo "GitHub ref: $GITHUB_REF"
|
|
||||||
echo "GitHub event: $GITHUB_EVENT"
|
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build OSS selfhost
|
|
||||||
run: |
|
|
||||||
npm run dist:oss:selfhost
|
|
||||||
zip -r web-$_VERSION-selfhosted-open-source.zip build
|
|
||||||
|
|
||||||
- name: Upload build artifact
|
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
|
||||||
with:
|
|
||||||
name: web-${{ env._VERSION }}-selfhosted-open-source.zip
|
|
||||||
path: ./web-${{ env._VERSION }}-selfhosted-open-source.zip
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
|
|
||||||
build-cloud:
|
|
||||||
name: Build Cloud zip
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
needs: setup
|
|
||||||
env:
|
|
||||||
_VERSION: ${{ needs.setup.outputs.version }}
|
|
||||||
steps:
|
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
|
||||||
with:
|
|
||||||
node-version: '14'
|
|
||||||
|
|
||||||
- name: Update NPM
|
|
||||||
run: |
|
|
||||||
npm install -g npm@7
|
|
||||||
|
|
||||||
- name: Cache npm
|
|
||||||
id: npm-cache
|
|
||||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
|
||||||
with:
|
|
||||||
path: '~/.npm'
|
|
||||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
|
|
||||||
- name: Print environment
|
|
||||||
run: |
|
|
||||||
whoami
|
|
||||||
node --version
|
|
||||||
npm --version
|
|
||||||
gulp --version
|
|
||||||
docker --version
|
|
||||||
echo "GitHub ref: $GITHUB_REF"
|
|
||||||
echo "GitHub event: $GITHUB_EVENT"
|
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build Cloud
|
|
||||||
run: |
|
|
||||||
npm run dist:bit:cloud
|
|
||||||
zip -r web-$_VERSION-cloud-COMMERCIAL.zip build
|
|
||||||
|
|
||||||
- name: Upload build artifact
|
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
|
||||||
with:
|
|
||||||
name: web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
|
|
||||||
path: ./web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
|
|
||||||
build-commercial-selfhost:
|
|
||||||
name: Build SelfHost Docker image
|
name: Build SelfHost Docker image
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
needs: setup
|
|
||||||
env:
|
|
||||||
_VERSION: ${{ needs.setup.outputs.version }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
@@ -169,7 +47,7 @@ jobs:
|
|||||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
||||||
with:
|
with:
|
||||||
path: '~/.npm'
|
path: '~/.npm'
|
||||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-${{ github.run_id }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
@@ -181,13 +59,39 @@ jobs:
|
|||||||
echo "GitHub ref: $GITHUB_REF"
|
echo "GitHub ref: $GITHUB_REF"
|
||||||
echo "GitHub event: $GITHUB_EVENT"
|
echo "GitHub event: $GITHUB_EVENT"
|
||||||
|
|
||||||
- name: Setup DCT
|
- name: Login to Azure
|
||||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/release'
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
id: setup-dct
|
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||||
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
|
|
||||||
with:
|
with:
|
||||||
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||||
azure-keyvault-name: "bitwarden-prod-kv"
|
|
||||||
|
- name: Retrieve secrets
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
id: retrieve-secrets
|
||||||
|
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
||||||
|
with:
|
||||||
|
keyvault: "bitwarden-prod-kv"
|
||||||
|
secrets: "docker-password,
|
||||||
|
docker-username,
|
||||||
|
dct-delegate-2-repo-passphrase,
|
||||||
|
dct-delegate-2-key"
|
||||||
|
|
||||||
|
- name: Log into Docker
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||||
|
env:
|
||||||
|
DOCKER_USERNAME: ${{ steps.retrieve-secrets.outputs.docker-username }}
|
||||||
|
DOCKER_PASSWORD: ${{ steps.retrieve-secrets.outputs.docker-password }}
|
||||||
|
|
||||||
|
- name: Setup Docker Trust
|
||||||
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.docker/trust/private
|
||||||
|
|
||||||
|
echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key
|
||||||
|
env:
|
||||||
|
DCT_DELEGATION_KEY_ID: "c9bde8ec820701516491e5e03d3a6354e7bd66d05fa3df2b0062f68b116dc59c"
|
||||||
|
DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }}
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
@@ -196,26 +100,15 @@ jobs:
|
|||||||
run: dotnet tool restore
|
run: dotnet tool restore
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm install
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
echo -e "# Building Web\n"
|
echo -e "# Building Web\n"
|
||||||
echo "Building app"
|
echo "Building app"
|
||||||
echo "npm version $(npm --version)"
|
echo "npm version $(npm --version)"
|
||||||
|
npm run dist:selfhost
|
||||||
|
|
||||||
npm run dist:bit:selfhost
|
|
||||||
zip -r web-$_VERSION-selfhosted-COMMERCIAL.zip build
|
|
||||||
|
|
||||||
- name: Upload build artifact
|
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
|
||||||
with:
|
|
||||||
name: web-${{ env._VERSION }}-selfhosted-COMMERCIAL.zip
|
|
||||||
path: ./web-${{ env._VERSION }}-selfhosted-COMMERCIAL.zip
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Build Docker image
|
|
||||||
run: |
|
|
||||||
echo -e "\nBuilding Docker image"
|
echo -e "\nBuilding Docker image"
|
||||||
docker --version
|
docker --version
|
||||||
docker build -t bitwarden/web .
|
docker build -t bitwarden/web .
|
||||||
@@ -228,43 +121,32 @@ jobs:
|
|||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
run: docker tag bitwarden/web bitwarden/web:dev
|
run: docker tag bitwarden/web bitwarden/web:dev
|
||||||
|
|
||||||
- name: Tag release branch
|
|
||||||
if: github.ref == 'refs/heads/release'
|
|
||||||
run: docker tag bitwarden/web bitwarden/web:latest
|
|
||||||
|
|
||||||
- name: List Docker images
|
- name: List Docker images
|
||||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/release'
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
run: docker images
|
run: docker images
|
||||||
|
|
||||||
- name: Push rc image
|
- name: Push rc images
|
||||||
if: github.ref == 'refs/heads/rc'
|
if: github.ref == 'refs/heads/rc'
|
||||||
run: docker push bitwarden/web:rc
|
run: docker push bitwarden/web:rc
|
||||||
env:
|
env:
|
||||||
DOCKER_CONTENT_TRUST: 1
|
DOCKER_CONTENT_TRUST: 1
|
||||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
|
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||||
|
|
||||||
- name: Push dev image
|
- name: Push dev images
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
run: docker push bitwarden/web:dev
|
run: docker push bitwarden/web:dev
|
||||||
env:
|
env:
|
||||||
DOCKER_CONTENT_TRUST: 1
|
DOCKER_CONTENT_TRUST: 1
|
||||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
|
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||||
|
|
||||||
- name: Push latest image
|
|
||||||
if: github.ref == 'refs/heads/release'
|
|
||||||
run: docker push bitwarden/web:latest
|
|
||||||
env:
|
|
||||||
DOCKER_CONTENT_TRUST: 1
|
|
||||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
|
|
||||||
|
|
||||||
- name: Log out of Docker
|
- name: Log out of Docker
|
||||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/release'
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||||
run: docker logout
|
run: docker logout
|
||||||
|
|
||||||
|
|
||||||
build-qa:
|
build-qa:
|
||||||
name: Build Docker images for QA environment
|
name: Build QA Docker image
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
@@ -280,7 +162,7 @@ jobs:
|
|||||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
||||||
with:
|
with:
|
||||||
path: '~/.npm'
|
path: '~/.npm'
|
||||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-${{ github.run_id }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
@@ -318,7 +200,7 @@ jobs:
|
|||||||
jq --arg version "$VERSION - ${GITHUB_SHA:0:7}" '.version = $version' package.json > package.json.tmp
|
jq --arg version "$VERSION - ${GITHUB_SHA:0:7}" '.version = $version' package.json > package.json.tmp
|
||||||
mv package.json.tmp package.json
|
mv package.json.tmp package.json
|
||||||
|
|
||||||
npm run build:bit:qa
|
npm run build:qa
|
||||||
|
|
||||||
echo "{\"commit_hash\": \"$GITHUB_SHA\", \"ref\": \"$GITHUB_REF\"}" | jq . > build/info.json
|
echo "{\"commit_hash\": \"$GITHUB_SHA\", \"ref\": \"$GITHUB_REF\"}" | jq . > build/info.json
|
||||||
|
|
||||||
@@ -327,10 +209,10 @@ jobs:
|
|||||||
docker build -t bitwardenqa.azurecr.io/web .
|
docker build -t bitwardenqa.azurecr.io/web .
|
||||||
|
|
||||||
- name: Get image tag
|
- name: Get image tag
|
||||||
id: image-tag
|
id: image_tag
|
||||||
run: |
|
run: |
|
||||||
IMAGE_TAG=$(echo "$GITHUB_REF" | awk '{split($0, a, "/"); print a[3];}')
|
IMAGE_TAG=$(echo "$GITHUB_REF" | awk '{split($0, a, "/"); print a[3];}')
|
||||||
TAG_EXTENSION=${{ github.event.inputs.custom_tag_extension }}
|
TAG_EXTENSION=${{ github.events.inputs.custom_tag_extension }}
|
||||||
|
|
||||||
if [[ $TAG_EXTENSION ]]; then
|
if [[ $TAG_EXTENSION ]]; then
|
||||||
IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
|
IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
|
||||||
@@ -339,7 +221,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Tag image
|
- name: Tag image
|
||||||
env:
|
env:
|
||||||
IMAGE_TAG: ${{ steps.image-tag.outputs.value }}
|
IMAGE_TAG: ${{ steps.image_tag.outputs.value }}
|
||||||
run: docker tag bitwardenqa.azurecr.io/web "bitwardenqa.azurecr.io/web:$IMAGE_TAG"
|
run: docker tag bitwardenqa.azurecr.io/web "bitwardenqa.azurecr.io/web:$IMAGE_TAG"
|
||||||
|
|
||||||
- name: Tag dev
|
- name: Tag dev
|
||||||
@@ -351,7 +233,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Push image
|
- name: Push image
|
||||||
env:
|
env:
|
||||||
IMAGE_TAG: ${{ steps.image-tag.outputs.value }}
|
IMAGE_TAG: ${{ steps.image_tag.outputs.value }}
|
||||||
run: docker push "bitwardenqa.azurecr.io/web:$IMAGE_TAG"
|
run: docker push "bitwardenqa.azurecr.io/web:$IMAGE_TAG"
|
||||||
|
|
||||||
- name: Push dev images
|
- name: Push dev images
|
||||||
@@ -364,7 +246,7 @@ jobs:
|
|||||||
|
|
||||||
windows:
|
windows:
|
||||||
name: Test code on Windows
|
name: Test code on Windows
|
||||||
runs-on: windows-2019
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up NuGet
|
- name: Set up NuGet
|
||||||
uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1
|
uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1
|
||||||
@@ -374,13 +256,6 @@ jobs:
|
|||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
|
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
|
||||||
|
|
||||||
- name: Cache npm
|
|
||||||
id: npm-cache
|
|
||||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
|
||||||
with:
|
|
||||||
path: '~/.npm'
|
|
||||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
with:
|
with:
|
||||||
@@ -406,11 +281,8 @@ jobs:
|
|||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: NPM install
|
- name: NPM install
|
||||||
run: npm ci
|
run: npm install
|
||||||
|
|
||||||
- name: NPM build
|
- name: NPM build
|
||||||
run: npm run build:bit:cloud
|
run: npm run build:cloud
|
||||||
|
|||||||
73
.github/workflows/deploy.yml
vendored
Normal file
73
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_version:
|
||||||
|
description: "Release Tag Version <vX.X.X>"
|
||||||
|
required: true
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy Web Vault
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||||
|
with:
|
||||||
|
ref: gh-pages
|
||||||
|
|
||||||
|
- name: Get release version
|
||||||
|
id: release-version
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "release" ]]; then
|
||||||
|
echo "::set-output name=version::${{ github.event.release.tag_name }}"
|
||||||
|
else
|
||||||
|
echo "::set-output name=version::${{ github.event.inputs.release_version }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create deploy branch
|
||||||
|
run: |
|
||||||
|
git switch -c deploy-${{ steps.release-version.outputs.version }}
|
||||||
|
git push -u origin deploy-${{ steps.release-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Checkout Repo
|
||||||
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||||
|
with:
|
||||||
|
ref: rc
|
||||||
|
|
||||||
|
- name: Setup git config
|
||||||
|
run: |
|
||||||
|
git config user.name = "GitHub Action Bot"
|
||||||
|
git config user.email = "<>"
|
||||||
|
git config --global url."https://github.com/".insteadOf ssh://git@github.com/
|
||||||
|
git config --global url."https://".insteadOf ssh://
|
||||||
|
|
||||||
|
- name: Install and Build
|
||||||
|
run: |
|
||||||
|
npm run sub:init
|
||||||
|
npm ci
|
||||||
|
npm run dist
|
||||||
|
|
||||||
|
- name: Deploy GitHub Pages
|
||||||
|
uses: crazy-max/ghaction-github-pages@db4476a01402e1a7ce05f41832040eef16d14925 # v2.5.0
|
||||||
|
with:
|
||||||
|
target_branch: deploy-${{ steps.release-version.outputs.version }}
|
||||||
|
build_dir: build
|
||||||
|
keep_history: true
|
||||||
|
commit_message: "Staging deploy ${{ steps.release-version.outputs.version }}"
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create Deploy PR
|
||||||
|
run: |
|
||||||
|
gh pr create --title "Deploy $VERSION" --body "Deploying $VERSION" --base gh-pages --head "$PR_BRANCH"
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.release-version.outputs.version }}
|
||||||
|
PR_BRANCH: deploy-${{ steps.release-version.outputs.version }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
4
.github/workflows/qa-deploy.yml
vendored
4
.github/workflows/qa-deploy.yml
vendored
@@ -17,7 +17,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy QA Web
|
name: Deploy QA Web
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
id: image_tag
|
id: image_tag
|
||||||
run: |
|
run: |
|
||||||
IMAGE_TAG=$(echo "$GITHUB_REF" | awk '{split($0, a, "/"); print a[3];}')
|
IMAGE_TAG=$(echo "$GITHUB_REF" | awk '{split($0, a, "/"); print a[3];}')
|
||||||
TAG_EXTENSION=${{ github.event.inputs.image_extension }}
|
TAG_EXTENSION=${{ github.events.inputs.image_extension }}
|
||||||
|
|
||||||
if [[ $TAG_EXTENSION ]]; then
|
if [[ $TAG_EXTENSION ]]; then
|
||||||
IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
|
IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
|
||||||
|
|||||||
245
.github/workflows/release.yml
vendored
245
.github/workflows/release.yml
vendored
@@ -3,21 +3,25 @@ name: Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs: {}
|
inputs:
|
||||||
|
release_tag_name_input:
|
||||||
|
description: "Release Tag Name <X.X.X>"
|
||||||
|
required: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
setup:
|
||||||
name: Setup
|
name: Setup
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
release_version: ${{ steps.version.outputs.package }}
|
release_upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
tag_version: ${{ steps.version.outputs.tag }}
|
release_version: ${{ steps.create_tags.outputs.package_version }}
|
||||||
|
tag_version: ${{ steps.create_tags.outputs.tag_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Branch check
|
- name: Branch check
|
||||||
run: |
|
run: |
|
||||||
if [[ "$GITHUB_REF" != "refs/heads/release" ]]; then
|
if [[ "$GITHUB_REF" != "refs/heads/rc" ]]; then
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
echo "[!] Can only release from the 'release' branch"
|
echo "[!] Can only release from rc branch"
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -25,163 +29,132 @@ jobs:
|
|||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # 2.3.4
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # 2.3.4
|
||||||
|
|
||||||
- name: Check Release Version
|
- name: Create Release Vars
|
||||||
id: version
|
id: create_tags
|
||||||
run: |
|
run: |
|
||||||
version=$( jq -r ".version" package.json)
|
case "${RELEASE_TAG_NAME_INPUT:0:1}" in
|
||||||
previous_release_tag_version=$(
|
v)
|
||||||
curl -sL https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest | jq -r ".tag_name"
|
echo "RELEASE_NAME=${RELEASE_TAG_NAME_INPUT:1}" >> $GITHUB_ENV
|
||||||
)
|
echo "RELEASE_TAG_NAME=$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
|
||||||
|
echo "::set-output name=package_version::${RELEASE_TAG_NAME_INPUT:1}"
|
||||||
|
echo "::set-output name=tag_version::$RELEASE_TAG_NAME_INPUT"
|
||||||
|
;;
|
||||||
|
[0-9])
|
||||||
|
echo "RELEASE_NAME=$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
|
||||||
|
echo "RELEASE_TAG_NAME=v$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
|
||||||
|
echo "::set-output name=package_version::$RELEASE_TAG_NAME_INPUT"
|
||||||
|
echo "::set-output name=tag_version::v$RELEASE_TAG_NAME_INPUT"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
env:
|
||||||
|
RELEASE_TAG_NAME_INPUT: ${{ github.event.inputs.release_tag_name_input }}
|
||||||
|
|
||||||
if [ "v$version" == "$previous_release_tag_version" ]; then
|
- name: Create Draft Release
|
||||||
echo "[!] Already released v$version. Please bump version to continue"
|
id: create_release
|
||||||
exit 1
|
uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # 1.1.4 - Repo Archived
|
||||||
fi
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ env.RELEASE_TAG_NAME }}
|
||||||
|
release_name: Version ${{ env.RELEASE_NAME }}
|
||||||
|
draft: true
|
||||||
|
prerelease: false
|
||||||
|
|
||||||
echo "::set-output name=package::$version"
|
ubuntu:
|
||||||
echo "::set-output name=tag::v$version"
|
name: Ubuntu
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
self-host:
|
|
||||||
name: Release self-host docker
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
needs: setup
|
needs: setup
|
||||||
env:
|
env:
|
||||||
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
|
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||||
|
with:
|
||||||
|
node-version: '14'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g npm@7
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
whoami
|
whoami
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
gulp --version
|
||||||
docker --version
|
docker --version
|
||||||
echo "GitHub ref: $GITHUB_REF"
|
echo "GitHub ref: $GITHUB_REF"
|
||||||
echo "GitHub event: $GITHUB_EVENT"
|
echo "GitHub event: $GITHUB_EVENT"
|
||||||
|
|
||||||
- name: Setup DCT
|
- name: Login to Azure
|
||||||
id: setup-dct
|
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||||
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
|
|
||||||
with:
|
with:
|
||||||
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||||
azure-keyvault-name: "bitwarden-prod-kv"
|
|
||||||
|
- name: Retrieve secrets
|
||||||
|
id: retrieve-secrets
|
||||||
|
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
||||||
|
with:
|
||||||
|
keyvault: "bitwarden-prod-kv"
|
||||||
|
secrets: "docker-password,
|
||||||
|
docker-username,
|
||||||
|
dct-delegate-2-repo-passphrase,
|
||||||
|
dct-delegate-2-key"
|
||||||
|
|
||||||
|
- name: Log into Docker
|
||||||
|
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||||
|
env:
|
||||||
|
DOCKER_USERNAME: ${{ steps.retrieve-secrets.outputs.docker-username }}
|
||||||
|
DOCKER_PASSWORD: ${{ steps.retrieve-secrets.outputs.docker-password }}
|
||||||
|
|
||||||
|
- name: Setup Docker Trust
|
||||||
|
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.docker/trust/private
|
||||||
|
|
||||||
|
echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key
|
||||||
|
env:
|
||||||
|
DCT_DELEGATION_KEY_ID: "c9bde8ec820701516491e5e03d3a6354e7bd66d05fa3df2b0062f68b116dc59c"
|
||||||
|
DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }}
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||||
|
|
||||||
- name: Pull latest selfhost Release image
|
- name: Restore
|
||||||
run: docker pull bitwarden/web:latest
|
run: dotnet tool restore
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
echo -e "# Building Web\n"
|
||||||
|
echo "Building app"
|
||||||
|
echo "npm version $(npm --version)"
|
||||||
|
npm install
|
||||||
|
npm run dist:selfhost
|
||||||
|
|
||||||
|
echo -e "\nBuilding Docker image"
|
||||||
|
docker --version
|
||||||
|
docker build -t bitwarden/web .
|
||||||
|
|
||||||
- name: Tag version
|
- name: Tag version
|
||||||
run: |
|
run: docker tag bitwarden/web bitwarden/web:$_RELEASE_VERSION
|
||||||
docker tag bitwarden/web:latest bitwarden/web:$_RELEASE_VERSION
|
|
||||||
|
|
||||||
- name: List Docker images
|
- name: List Docker images
|
||||||
run: docker images
|
run: docker images
|
||||||
|
|
||||||
- name: Push images
|
- name: Push latest images
|
||||||
run: |
|
run: docker push bitwarden/web:latest
|
||||||
docker push bitwarden/web:$_RELEASE_VERSION
|
|
||||||
env:
|
env:
|
||||||
DOCKER_CONTENT_TRUST: 1
|
DOCKER_CONTENT_TRUST: 1
|
||||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
|
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||||
|
|
||||||
|
- name: Push version images
|
||||||
|
run: docker push bitwarden/web:$_RELEASE_VERSION
|
||||||
|
env:
|
||||||
|
DOCKER_CONTENT_TRUST: 1
|
||||||
|
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||||
|
|
||||||
- name: Log out of Docker
|
- name: Log out of Docker
|
||||||
run: docker logout
|
run: docker logout
|
||||||
|
|
||||||
|
|
||||||
ghpages-deploy:
|
|
||||||
name: Deploy Web Vault
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
needs:
|
|
||||||
- setup
|
|
||||||
- self-host
|
|
||||||
env:
|
|
||||||
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
|
|
||||||
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
with:
|
|
||||||
ref: gh-pages
|
|
||||||
|
|
||||||
- name: Create deploy branch
|
|
||||||
run: |
|
|
||||||
git switch -c deploy-$_TAG_VERSION
|
|
||||||
git push -u origin deploy-$_TAG_VERSION
|
|
||||||
|
|
||||||
- name: Checkout Repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
with:
|
|
||||||
ref: release
|
|
||||||
|
|
||||||
- name: Setup git config
|
|
||||||
run: |
|
|
||||||
git config user.name = "GitHub Action Bot"
|
|
||||||
git config user.email = "<>"
|
|
||||||
git config --global url."https://github.com/".insteadOf ssh://git@github.com/
|
|
||||||
git config --global url."https://".insteadOf ssh://
|
|
||||||
|
|
||||||
- name: Download latest cloud asset
|
|
||||||
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
|
|
||||||
with:
|
|
||||||
workflow: build.yml
|
|
||||||
workflow_conclusion: success
|
|
||||||
branch: release
|
|
||||||
artifacts: web-*-cloud-COMMERCIAL.zip
|
|
||||||
|
|
||||||
# This should result in a build directory in the current working directory
|
|
||||||
- name: Unzip build asset
|
|
||||||
run: unzip web-*-cloud-COMMERCIAL.zip
|
|
||||||
|
|
||||||
- name: Deploy GitHub Pages
|
|
||||||
uses: crazy-max/ghaction-github-pages@db4476a01402e1a7ce05f41832040eef16d14925 # v2.5.0
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
target_branch: deploy-${{ needs.setup.outputs.tag_version }}
|
|
||||||
build_dir: build
|
|
||||||
keep_history: true
|
|
||||||
commit_message: "Staging deploy ${{ needs.setup.outputs.release_version }}"
|
|
||||||
|
|
||||||
- name: Create Deploy PR
|
|
||||||
env:
|
|
||||||
PR_BRANCH: deploy-${{ env._TAG_VERSION }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
gh pr create --title "Deploy $_RELEASE_VERSION" \
|
|
||||||
--body "Deploying $_RELEASE_VERSION" \
|
|
||||||
--base gh-pages \
|
|
||||||
--head "$PR_BRANCH"
|
|
||||||
|
|
||||||
|
|
||||||
release:
|
|
||||||
name: Create GitHub Release
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
needs:
|
|
||||||
- setup
|
|
||||||
- self-host
|
|
||||||
- ghpages-deploy
|
|
||||||
steps:
|
|
||||||
- name: Download latest build artifacts
|
|
||||||
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
|
|
||||||
with:
|
|
||||||
workflow: build.yml
|
|
||||||
workflow_conclusion: success
|
|
||||||
branch: release
|
|
||||||
artifacts: "web-*-selfhosted-COMMERCIAL.zip,
|
|
||||||
web-*-selfhosted-open-source.zip"
|
|
||||||
|
|
||||||
- name: Rename assets
|
|
||||||
run: |
|
|
||||||
mv web-*-selfhosted-COMMERCIAL.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-COMMERCIAL.zip
|
|
||||||
mv web-*-selfhosted-open-source.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09
|
|
||||||
with:
|
|
||||||
name: "Version ${{ needs.setup.outputs.release_version }}"
|
|
||||||
commit: ${{ github.sha }}
|
|
||||||
tag: "${{ needs.setup.outputs.tag_version }}"
|
|
||||||
body: "<insert release notes here>"
|
|
||||||
artifacts: "web-${{ needs.setup.outputs.release_version }}-selfhosted-COMMERCIAL.zip,
|
|
||||||
web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip"
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
draft: true
|
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -32,7 +32,7 @@ For local development, run the app with:
|
|||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
npm run build:oss:watch
|
npm run build:watch
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now access the web vault in your browser at `https://localhost:8080`.
|
You can now access the web vault in your browser at `https://localhost:8080`.
|
||||||
@@ -41,7 +41,7 @@ If you want to point the development web vault to the production APIs, you can r
|
|||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
ENV=production npm run build:oss:watch
|
ENV=production npm run build:watch
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also manually adjusting your API endpoint settings by adding `config/local.json` overriding any of the following values:
|
You can also manually adjusting your API endpoint settings by adding `config/local.json` overriding any of the following values:
|
||||||
@@ -52,6 +52,7 @@ You can also manually adjusting your API endpoint settings by adding `config/loc
|
|||||||
"proxyIdentity": "http://your-identity-url",
|
"proxyIdentity": "http://your-identity-url",
|
||||||
"proxyEvents": "http://your-events-url",
|
"proxyEvents": "http://your-events-url",
|
||||||
"proxyNotifications": "http://your-notifications-url",
|
"proxyNotifications": "http://your-notifications-url",
|
||||||
|
"proxyPortal": "http://your-portal-url",
|
||||||
"allowedHosts": ["hostnames-to-allow-in-webpack"],
|
"allowedHosts": ["hostnames-to-allow-in-webpack"],
|
||||||
"urls": {
|
"urls": {
|
||||||
|
|
||||||
@@ -59,7 +60,11 @@ You can also manually adjusting your API endpoint settings by adding `config/loc
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Where the `urls` object is defined by the [Urls type in jslib](https://github.com/bitwarden/jslib/blob/master/common/src/abstractions/environment.service.ts).
|
Where the `urls` object is defined by the [Urls type in jslib](https://github.com/bitwarden/jslib/blob/master/common/src/abstractions/environment.service.ts). To pick up the overrides in the newly created `config/local.json` file, run the app with:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build:dev:watch
|
||||||
|
```
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
import { AppComponent as BaseAppComponent } from 'src/app/app.component';
|
|
||||||
import { DisablePersonalVaultExportPolicy } from './policies/disable-personal-vault-export.component';
|
|
||||||
import { MaximumVaultTimeoutPolicy } from './policies/maximum-vault-timeout.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-root',
|
|
||||||
templateUrl: '../../../src/app/app.component.html',
|
|
||||||
})
|
|
||||||
export class AppComponent extends BaseAppComponent {
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
super.ngOnInit();
|
|
||||||
|
|
||||||
this.policyListService.addPolicies([
|
|
||||||
new MaximumVaultTimeoutPolicy(),
|
|
||||||
new DisablePersonalVaultExportPolicy(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,41 +3,27 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
|||||||
|
|
||||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { RouterModule } from '@angular/router';
|
|
||||||
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
import { OrganizationsModule } from './organizations/organizations.module';
|
|
||||||
import { DisablePersonalVaultExportPolicyComponent } from './policies/disable-personal-vault-export.component';
|
|
||||||
import { MaximumVaultTimeoutPolicyComponent } from './policies/maximum-vault-timeout.component';
|
|
||||||
|
|
||||||
|
import { AppComponent } from 'src/app/app.component';
|
||||||
import { OssRoutingModule } from 'src/app/oss-routing.module';
|
import { OssRoutingModule } from 'src/app/oss-routing.module';
|
||||||
import { OssModule } from 'src/app/oss.module';
|
import { OssModule } from 'src/app/oss.module';
|
||||||
import { ServicesModule } from 'src/app/services/services.module';
|
import { ServicesModule } from 'src/app/services/services.module';
|
||||||
import { WildcardRoutingModule } from 'src/app/wildcard-routing.module';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
OssModule,
|
OssModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
|
||||||
ServicesModule,
|
ServicesModule,
|
||||||
ToasterModule.forRoot(),
|
ToasterModule.forRoot(),
|
||||||
InfiniteScrollModule,
|
InfiniteScrollModule,
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
OssRoutingModule,
|
OssRoutingModule,
|
||||||
OrganizationsModule,
|
|
||||||
RouterModule,
|
|
||||||
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
AppComponent,
|
|
||||||
MaximumVaultTimeoutPolicyComponent,
|
|
||||||
DisablePersonalVaultExportPolicyComponent,
|
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,284 +0,0 @@
|
|||||||
<div class="page-header d-flex">
|
|
||||||
<h1>{{'singleSignOn' | i18n}}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-container *ngIf="loading">
|
|
||||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
|
||||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<form #form (ngSubmit)="submit()" [formGroup]="data" [appApiAction]="formPromise" *ngIf="!loading">
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
|
|
||||||
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="type">{{'type' | i18n}}</label>
|
|
||||||
<select class="form-control" id="type" formControlName="configType">
|
|
||||||
<option value="0" disabled>{{'selectType' | i18n}}</option>
|
|
||||||
<option value="1">OpenID Connect</option>
|
|
||||||
<option value="2">SAML 2.0</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- OIDC -->
|
|
||||||
<div *ngIf="data.value.configType == 1">
|
|
||||||
<div class="config-section">
|
|
||||||
<h2>{{'openIdConnectConfig' | i18n}}</h2>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'callbackPath' | i18n}}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control" readonly [value]="callbackPath">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
|
||||||
(click)="copy(callbackPath)">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'signedOutCallbackPath' | i18n}}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control" readonly [value]="signedOutCallbackPath">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
|
||||||
(click)="copy(signedOutCallbackPath)">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'authority' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="authority">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'clientId' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="clientId">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'clientSecret' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="clientSecret">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'metadataAddress' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="metadataAddress">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'oidcRedirectBehavior' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="redirectBehavior">
|
|
||||||
<option value="0">Redirect GET</option>
|
|
||||||
<option value="1">Form POST</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="getClaimsFromUserInfoEndpoint"
|
|
||||||
formControlName="getClaimsFromUserInfoEndpoint">
|
|
||||||
<label class="form-check-label" for="getClaimsFromUserInfoEndpoint">
|
|
||||||
{{'getClaimsFromUserInfoEndpoint' | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'additionalScopes' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="additionalScopes">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'additionalUserIdClaimTypes' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="additionalUserIdClaimTypes">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'additionalEmailClaimTypes' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="additionalEmailClaimTypes">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'additionalNameClaimTypes' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="additionalNameClaimTypes">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'acrValues' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="acrValues">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'expectedReturnAcrValue' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="expectedReturnAcrValue">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="data.value.configType == 2">
|
|
||||||
<!-- SAML2 SP -->
|
|
||||||
<div class="config-section">
|
|
||||||
<h2>{{'samlSpConfig' | i18n}}</h2>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'spEntityId' | i18n}}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control" readonly [value]="spEntityId" >
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
|
||||||
(click)="copy(spEntityId)">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'spMetadataUrl' | i18n}}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control" readonly [value]="spMetadataUrl">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'launch' | i18n}}"
|
|
||||||
(click)="launchUri(spMetadataUrl)">
|
|
||||||
<i class="fa fa-lg fa-external-link" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
|
||||||
(click)="copy(spMetadataUrl)">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'spAcsUrl' | i18n}}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control" readonly [value]="spAcsUrl">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
|
||||||
(click)="copy(spAcsUrl)">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'spNameIdFormat' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="spNameIdFormat">
|
|
||||||
<option value="0">Not Configured</option>
|
|
||||||
<option value="1">Unspecified</option>
|
|
||||||
<option value="2">Email Address</option>
|
|
||||||
<option value="3">X.509 Subject Name</option>
|
|
||||||
<option value="4">Windows Domain Qualified Name</option>
|
|
||||||
<option value="5">Kerberos Principal Name</option>
|
|
||||||
<option value="6">Entity Identifier</option>
|
|
||||||
<option value="7">Persistent</option>
|
|
||||||
<option value="8">Transient</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'spOutboundSigningAlgorithm' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="spOutboundSigningAlgorithm">
|
|
||||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'spSigningBehavior' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="spSigningBehavior">
|
|
||||||
<option value="0">If IdP Wants Authn Requests Signed</option>
|
|
||||||
<option value="1">Always</option>
|
|
||||||
<option value="3">Never</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'spMinIncomingSigningAlgorithm' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="spMinIncomingSigningAlgorithm">
|
|
||||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="spWantAssertionsSigned" formControlName="spWantAssertionsSigned">
|
|
||||||
<label class="form-check-label" for="spWantAssertionsSigned">{{'spWantAssertionsSigned' | i18n}}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="spValidateCertificates" formControlName="spValidateCertificates">
|
|
||||||
<label class="form-check-label" for="spValidateCertificates">{{'spValidateCertificates' | i18n}}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- SAML2 IDP -->
|
|
||||||
<div class="config-section">
|
|
||||||
<h2>{{'samlIdpConfig' | i18n}}</h2>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'idpEntityId' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="idpEntityId">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'idpBindingType' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="idpBindingType">
|
|
||||||
<option value="1">Redirect</option>
|
|
||||||
<option value="2">HTTP POST</option>
|
|
||||||
<option value="4">Artifact</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'idpSingleSignOnServiceUrl' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="idpSingleSignOnServiceUrl">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'idpSingleLogoutServiceUrl' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="idpSingleLogoutServiceUrl">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'idpArtifactResolutionServiceUrl' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="idpArtifactResolutionServiceUrl">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'idpX509PublicCert' | i18n}}</label>
|
|
||||||
<textarea formControlName="idpX509PublicCert" class="form-control form-control-sm text-monospace" rows="6"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'idpOutboundSigningAlgorithm' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="idpOutboundSigningAlgorithm">
|
|
||||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="idpAllowUnsolicitedAuthnResponse"
|
|
||||||
formControlName="idpAllowUnsolicitedAuthnResponse">
|
|
||||||
<label class="form-check-label" for="idpAllowUnsolicitedAuthnResponse">
|
|
||||||
{{'idpAllowUnsolicitedAuthnResponse' | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="idpDisableOutboundLogoutRequests"
|
|
||||||
formControlName="idpDisableOutboundLogoutRequests">
|
|
||||||
<label class="form-check-label" for="idpDisableOutboundLogoutRequests">
|
|
||||||
{{'idpDisableOutboundLogoutRequests' | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="idpWantAuthnRequestsSigned"
|
|
||||||
formControlName="idpWantAuthnRequestsSigned">
|
|
||||||
<label class="form-check-label" for="idpWantAuthnRequestsSigned">
|
|
||||||
{{'idpWantAuthnRequestsSigned' | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
|
||||||
<span>{{'save' | i18n}}</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { FormBuilder } from '@angular/forms';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
|
||||||
import { OrganizationSsoRequest } from 'jslib-common/models/request/organization/organizationSsoRequest';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-org-manage-sso',
|
|
||||||
templateUrl: 'sso.component.html',
|
|
||||||
})
|
|
||||||
export class SsoComponent implements OnInit {
|
|
||||||
|
|
||||||
samlSigningAlgorithms = [
|
|
||||||
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
|
|
||||||
'http://www.w3.org/2000/09/xmldsig#rsa-sha384',
|
|
||||||
'http://www.w3.org/2000/09/xmldsig#rsa-sha512',
|
|
||||||
'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
|
|
||||||
];
|
|
||||||
|
|
||||||
loading = true;
|
|
||||||
organizationId: string;
|
|
||||||
formPromise: Promise<any>;
|
|
||||||
|
|
||||||
callbackPath: string;
|
|
||||||
signedOutCallbackPath: string;
|
|
||||||
spEntityId: string;
|
|
||||||
spMetadataUrl: string;
|
|
||||||
spAcsUrl: string;
|
|
||||||
|
|
||||||
enabled = this.fb.control(false);
|
|
||||||
data = this.fb.group({
|
|
||||||
configType: [],
|
|
||||||
|
|
||||||
// OpenId
|
|
||||||
authority: [],
|
|
||||||
clientId: [],
|
|
||||||
clientSecret: [],
|
|
||||||
metadataAddress: [],
|
|
||||||
redirectBehavior: [],
|
|
||||||
getClaimsFromUserInfoEndpoint: [],
|
|
||||||
additionalScopes: [],
|
|
||||||
additionalUserIdClaimTypes: [],
|
|
||||||
additionalEmailClaimTypes: [],
|
|
||||||
additionalNameClaimTypes: [],
|
|
||||||
acrValues: [],
|
|
||||||
expectedReturnAcrValue: [],
|
|
||||||
|
|
||||||
// SAML
|
|
||||||
spNameIdFormat: [],
|
|
||||||
spOutboundSigningAlgorithm: [],
|
|
||||||
spSigningBehavior: [],
|
|
||||||
spMinIncomingSigningAlgorithm: [],
|
|
||||||
spWantAssertionsSigned: [],
|
|
||||||
spValidateCertificates: [],
|
|
||||||
|
|
||||||
idpEntityId: [],
|
|
||||||
idpBindingType: [],
|
|
||||||
idpSingleSignOnServiceUrl: [],
|
|
||||||
idpSingleLogoutServiceUrl: [],
|
|
||||||
idpArtifactResolutionServiceUrl: [],
|
|
||||||
idpX509PublicCert: [],
|
|
||||||
idpOutboundSigningAlgorithm: [],
|
|
||||||
idpAllowUnsolicitedAuthnResponse: [],
|
|
||||||
idpDisableOutboundLogoutRequests: [],
|
|
||||||
idpWantAuthnRequestsSigned: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(private fb: FormBuilder, private route: ActivatedRoute, private apiService: ApiService,
|
|
||||||
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService) { }
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
this.route.parent.parent.params.subscribe(async params => {
|
|
||||||
this.organizationId = params.organizationId;
|
|
||||||
await this.load();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {
|
|
||||||
const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId);
|
|
||||||
|
|
||||||
this.data.patchValue(ssoSettings.data);
|
|
||||||
this.enabled.setValue(ssoSettings.enabled);
|
|
||||||
|
|
||||||
this.callbackPath = ssoSettings.urls.callbackPath;
|
|
||||||
this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath;
|
|
||||||
this.spEntityId = ssoSettings.urls.spEntityId;
|
|
||||||
this.spMetadataUrl = ssoSettings.urls.spMetadataUrl;
|
|
||||||
this.spAcsUrl = ssoSettings.urls.spAcsUrl;
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(value: string) {
|
|
||||||
this.platformUtilsService.copyToClipboard(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
launchUri(url: string) {
|
|
||||||
this.platformUtilsService.launchUri(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
|
||||||
const request = new OrganizationSsoRequest();
|
|
||||||
request.enabled = this.enabled.value;
|
|
||||||
request.data = this.data.value;
|
|
||||||
|
|
||||||
this.formPromise = this.apiService.postOrganizationSso(this.organizationId, request);
|
|
||||||
|
|
||||||
const response = await this.formPromise;
|
|
||||||
this.data.patchValue(response.data);
|
|
||||||
this.enabled.setValue(response.enabled);
|
|
||||||
|
|
||||||
this.formPromise = null;
|
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('ssoSettingsSaved'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
|
||||||
|
|
||||||
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service';
|
|
||||||
|
|
||||||
import { Permissions } from 'jslib-common/enums/permissions';
|
|
||||||
|
|
||||||
import { OrganizationLayoutComponent } from 'src/app/layouts/organization-layout.component';
|
|
||||||
import { ManageComponent } from 'src/app/organizations/manage/manage.component';
|
|
||||||
import { OrganizationGuardService } from 'src/app/services/organization-guard.service';
|
|
||||||
import { OrganizationTypeGuardService } from 'src/app/services/organization-type-guard.service';
|
|
||||||
|
|
||||||
import { SsoComponent } from './manage/sso.component';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: 'organizations/:organizationId',
|
|
||||||
component: OrganizationLayoutComponent,
|
|
||||||
canActivate: [AuthGuardService, OrganizationGuardService],
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'manage',
|
|
||||||
component: ManageComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
permissions: [
|
|
||||||
Permissions.CreateNewCollections,
|
|
||||||
Permissions.EditAnyCollection,
|
|
||||||
Permissions.DeleteAnyCollection,
|
|
||||||
Permissions.EditAssignedCollections,
|
|
||||||
Permissions.DeleteAssignedCollections,
|
|
||||||
Permissions.AccessEventLogs,
|
|
||||||
Permissions.ManageGroups,
|
|
||||||
Permissions.ManageUsers,
|
|
||||||
Permissions.ManagePolicies,
|
|
||||||
Permissions.ManageSso,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'sso',
|
|
||||||
component: SsoComponent,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule.forChild(routes)],
|
|
||||||
exports: [RouterModule],
|
|
||||||
})
|
|
||||||
export class OrganizationsRoutingModule { }
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
|
|
||||||
import { OssModule } from 'src/app/oss.module';
|
|
||||||
|
|
||||||
import { SsoComponent } from './manage/sso.component';
|
|
||||||
import { OrganizationsRoutingModule } from './organizations-routing.module';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
OssModule,
|
|
||||||
OrganizationsRoutingModule,
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
SsoComponent,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class OrganizationsModule {}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
|
|
||||||
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { FormBuilder } from '@angular/forms';
|
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
|
|
||||||
import { PolicyType } from 'jslib-common/enums/policyType';
|
|
||||||
|
|
||||||
import { PolicyRequest } from 'jslib-common/models/request/policyRequest';
|
|
||||||
|
|
||||||
import { BasePolicy, BasePolicyComponent } from 'src/app/organizations/policies/base-policy.component';
|
|
||||||
|
|
||||||
export class DisablePersonalVaultExportPolicy extends BasePolicy {
|
|
||||||
name = 'disablePersonalVaultExport';
|
|
||||||
description = 'disablePersonalVaultExportDesc';
|
|
||||||
type = PolicyType.DisablePersonalVaultExport;
|
|
||||||
component = DisablePersonalVaultExportPolicyComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'policy-disable-personal-vault-export',
|
|
||||||
templateUrl: 'disable-personal-vault-export.component.html',
|
|
||||||
})
|
|
||||||
export class DisablePersonalVaultExportPolicyComponent extends BasePolicyComponent {
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<app-callout type="tip" title="{{'prerequisite' | i18n}}">
|
|
||||||
{{'requireSsoPolicyReq' | i18n}}
|
|
||||||
</app-callout>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
|
|
||||||
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div [formGroup]="data">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="hours">{{'maximumVaultTimeoutLabel' | i18n}}</label>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-6">
|
|
||||||
<input id="hours" class="form-control" type="number" min="0" name="hours" formControlName="hours">
|
|
||||||
<small>{{'hours' | i18n }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes"
|
|
||||||
formControlName="minutes">
|
|
||||||
<small>{{'minutes' | i18n }}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { FormBuilder } from '@angular/forms';
|
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
|
|
||||||
import { PolicyType } from 'jslib-common/enums/policyType';
|
|
||||||
|
|
||||||
import { PolicyRequest } from 'jslib-common/models/request/policyRequest';
|
|
||||||
|
|
||||||
import { BasePolicy, BasePolicyComponent } from 'src/app/organizations/policies/base-policy.component';
|
|
||||||
|
|
||||||
export class MaximumVaultTimeoutPolicy extends BasePolicy {
|
|
||||||
name = 'maximumVaultTimeout';
|
|
||||||
description = 'maximumVaultTimeoutDesc';
|
|
||||||
type = PolicyType.MaximumVaultTimeout;
|
|
||||||
component = MaximumVaultTimeoutPolicyComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'policy-maximum-timeout',
|
|
||||||
templateUrl: 'maximum-vault-timeout.component.html',
|
|
||||||
})
|
|
||||||
export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent {
|
|
||||||
|
|
||||||
data = this.fb.group({
|
|
||||||
hours: [null],
|
|
||||||
minutes: [null],
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(private fb: FormBuilder, private i18nService: I18nService) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
loadData() {
|
|
||||||
const minutes = this.policyResponse.data?.minutes;
|
|
||||||
|
|
||||||
if (minutes == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data.patchValue({
|
|
||||||
hours: Math.floor(minutes / 60),
|
|
||||||
minutes: minutes % 60,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
buildRequestData() {
|
|
||||||
if (this.data.value.hours == null && this.data.value.minutes == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
minutes: this.data.value.hours * 60 + this.data.value.minutes,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
|
||||||
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
|
|
||||||
if (this.enabled.value && !singleOrgEnabled) {
|
|
||||||
throw new Error(this.i18nService.t('requireSsoPolicyReqError'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = this.buildRequestData();
|
|
||||||
if (data?.minutes == null || data?.minutes <= 0) {
|
|
||||||
throw new Error(this.i18nService.t('invalidMaximumVaultTimeout'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.buildRequest(policiesEnabledMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
||||||
<div>
|
<div>
|
||||||
<img class="mb-4 logo logo-themed" alt="Bitwarden">
|
<img src="/src/images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
||||||
<div>
|
<div>
|
||||||
<img class="mb-4 logo logo-themed" alt="Bitwarden">
|
<img src="/src/images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
{
|
{
|
||||||
"urls": {},
|
"urls": {}
|
||||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
|
||||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
|
||||||
"paypal": {
|
|
||||||
"businessId": "AD3LAUZSNVPJY",
|
|
||||||
"buttonAction": "https://www.sandbox.paypal.com/cgi-bin/webscr"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
{
|
{
|
||||||
"urls": {
|
"urls": {
|
||||||
"icons": "https://icons.bitwarden.net",
|
"icons": "https://icons.bitwarden.net",
|
||||||
"notifications": "https://notifications.bitwarden.com"
|
"notifications": "https://notifications.bitwarden.com",
|
||||||
},
|
"enterprise": "https://portal.bitwarden.com"
|
||||||
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
|
||||||
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
|
|
||||||
"paypal": {
|
|
||||||
"businessId": "4ZDA7DLUUJGMN",
|
|
||||||
"buttonAction": "https://www.paypal.com/cgi-bin/webscr"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
"proxyIdentity": "http://localhost:33656",
|
"proxyIdentity": "http://localhost:33656",
|
||||||
"proxyEvents": "http://localhost:46273",
|
"proxyEvents": "http://localhost:46273",
|
||||||
"proxyNotifications": "http://localhost:61840",
|
"proxyNotifications": "http://localhost:61840",
|
||||||
|
"proxyEnterprise": "http://localhost:52313",
|
||||||
"allowedHosts": [],
|
"allowedHosts": [],
|
||||||
"urls": {
|
"urls": {
|
||||||
"notifications": "http://localhost:61840"
|
"notifications": "http://localhost:61840",
|
||||||
|
"enterprise": "http://localhost:52313"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"urls": {
|
"urls": {
|
||||||
"icons": "https://icons.qa.bitwarden.pw",
|
"icons": "https://icons.qa.bitwarden.pw",
|
||||||
"notifications": "https://notifications.qa.bitwarden.pw"
|
"notifications": "https://notifications.qa.bitwarden.pw",
|
||||||
|
"enterprise": "https://portal.qa.bitwarden.pw"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
37
gulpfile.js
Normal file
37
gulpfile.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const gulp = require('gulp');
|
||||||
|
const googleWebFonts = require('gulp-google-webfonts');
|
||||||
|
const del = require('del');
|
||||||
|
const package = require('./package.json');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const paths = {
|
||||||
|
node_modules: './node_modules/',
|
||||||
|
src: './src/',
|
||||||
|
build: './build/',
|
||||||
|
cssDir: './src/css/',
|
||||||
|
};
|
||||||
|
|
||||||
|
function clean() {
|
||||||
|
return del([paths.cssDir]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function webfonts() {
|
||||||
|
return gulp.src('./webfonts.list')
|
||||||
|
.pipe(googleWebFonts({
|
||||||
|
fontsDir: 'webfonts',
|
||||||
|
cssFilename: 'webfonts.css',
|
||||||
|
format: 'woff',
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest(paths.cssDir));
|
||||||
|
};
|
||||||
|
|
||||||
|
function version(cb) {
|
||||||
|
fs.writeFileSync(paths.build + 'version.json', '{"version":"' + package.version + '"}');
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.clean = clean;
|
||||||
|
exports.webfonts = gulp.series(clean, webfonts);
|
||||||
|
exports.prebuild = gulp.series(clean, webfonts);
|
||||||
|
exports.version = version;
|
||||||
|
exports.postdist = version;
|
||||||
2
jslib
2
jslib
Submodule jslib updated: 5fb0247a6a...1c28396d1a
8815
package-lock.json
generated
8815
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
41
package.json
41
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bitwarden-web",
|
"name": "bitwarden-web",
|
||||||
"version": "2.24.2",
|
"version": "2.23.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"repository": "https://github.com/bitwarden/web",
|
"repository": "https://github.com/bitwarden/web",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -11,24 +11,26 @@
|
|||||||
"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",
|
||||||
"build:oss": "webpack",
|
"build": "gulp prebuild && webpack -c bitwarden_license/webpack.config.js",
|
||||||
"build:bit": "webpack -c bitwarden_license/webpack.config.js",
|
"build:oss": "gulp prebuild && webpack",
|
||||||
"build:oss:watch": "webpack serve",
|
"build:watch": "gulp prebuild && webpack serve -c bitwarden_license/webpack.config.js",
|
||||||
"build:bit:watch": "webpack serve -c bitwarden_license/webpack.config.js",
|
"build:watch:oss": "gulp prebuild && webpack serve",
|
||||||
"build:bit:dev": "cross-env ENV=development npm run build:bit",
|
"build:dev": "cross-env ENV=development npm run build",
|
||||||
"build:bit:dev:watch": "cross-env ENV=development npm run build:bit:watch",
|
"build:dev:watch": "cross-env ENV=development npm run build:watch",
|
||||||
"build:bit:qa": "cross-env NODE_ENV=production ENV=qa npm run build:bit",
|
"build:qa": "cross-env NODE_ENV=production ENV=qa npm run build",
|
||||||
"build:bit:cloud": "cross-env NODE_ENV=production ENV=cloud npm run build:bit",
|
"build:cloud": "cross-env NODE_ENV=production ENV=cloud npm run build",
|
||||||
"build:oss:selfhost:watch": "cross-env ENV=selfhosted npm run build:oss:watch",
|
"build:cloud:oss": "cross-env NODE_ENV=production ENV=cloud npm run build:oss",
|
||||||
"build:bit:selfhost:watch": "cross-env ENV=selfhosted npm run build:bit:watch",
|
"build:selfhost": "cross-env ENV=selfhosted npm run build:watch",
|
||||||
"build:oss:selfhost:prod": "cross-env ENV=selfhosted NODE_ENV=production npm run build:oss",
|
"build:selfhost:watch": "cross-env ENV=selfhosted npm run build:watch",
|
||||||
"build:bit:selfhost:prod": "cross-env ENV=selfhosted NODE_ENV=production npm run build:bit",
|
"build:selfhost:prod": "cross-env ENV=selfhosted NODE_ENV=production npm run build",
|
||||||
|
"build:selfhost:prod:oss": "cross-env ENV=selfhosted NODE_ENV=production npm run build:oss",
|
||||||
"clean:l10n": "git push origin --delete l10n_master",
|
"clean:l10n": "git push origin --delete l10n_master",
|
||||||
"dist:bit:cloud": "npm run build:bit:cloud",
|
"dist": "npm run build:cloud && gulp postdist",
|
||||||
"dist:oss:selfhost": "npm run build:oss:selfhost:prod",
|
"dist:oss": "npm run build:cloud:oss && gulp postdist",
|
||||||
"dist:bit:selfhost": "npm run build:bit:selfhost:prod",
|
"dist:selfhost": "npm run build:selfhost:prod && gulp postdist",
|
||||||
"deploy": "npm run dist:bit && gh-pages -d build",
|
"dist:selfhost:oss": "npm run build:selfhost:prod:oss && gulp postdist",
|
||||||
"deploy:dev": "npm run dist:bit && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git",
|
"deploy": "npm run dist && gh-pages -d build",
|
||||||
|
"deploy:dev": "npm run dist && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git",
|
||||||
"lint": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' || true",
|
"lint": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' || true",
|
||||||
"lint:fix": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' --fix"
|
"lint:fix": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' --fix"
|
||||||
},
|
},
|
||||||
@@ -46,8 +48,9 @@
|
|||||||
"del": "^6.0.0",
|
"del": "^6.0.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"gh-pages": "^3.1.0",
|
"gh-pages": "^3.1.0",
|
||||||
|
"gulp": "^4.0.2",
|
||||||
|
"gulp-google-webfonts": "^4.0.0",
|
||||||
"html-loader": "^1.3.2",
|
"html-loader": "^1.3.2",
|
||||||
"html-webpack-injector": "1.1.4",
|
|
||||||
"html-webpack-plugin": "^4.5.1",
|
"html-webpack-plugin": "^4.5.1",
|
||||||
"mini-css-extract-plugin": "^1.5.0",
|
"mini-css-extract-plugin": "^1.5.0",
|
||||||
"sass": "^1.32.10",
|
"sass": "^1.32.10",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
||||||
<div>
|
<div>
|
||||||
<img class="mb-4 logo logo-themed" alt="Bitwarden">
|
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||||
<div class="row justify-content-md-center mt-5">
|
<div class="row justify-content-md-center mt-5">
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<img class="mb-2 logo logo-themed" alt="Bitwarden">
|
<img src="../../images/logo-dark@2x.png" class="logo mb-2" alt="Bitwarden">
|
||||||
<p class="lead text-center mx-4 mb-4">{{'loginOrCreateNewAccount' | i18n}}</p>
|
<p class="lead text-center mx-4 mb-4">{{'loginOrCreateNewAccount' | i18n}}</p>
|
||||||
<div class="card d-block">
|
<div class="card d-block">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<form #form (ngSubmit)="submit()" class="container" [appApiAction]="initiateSsoFormPromise" ngNativeValidate>
|
<form #form (ngSubmit)="submit()" class="container" [appApiAction]="initiateSsoFormPromise" ngNativeValidate>
|
||||||
<div class="row justify-content-md-center mt-5">
|
<div class="row justify-content-md-center mt-5">
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<img class="logo mb-2 logo-themed" alt="Bitwarden">
|
<img src="../../images/logo-dark@2x.png" class="logo mb-2" alt="Bitwarden">
|
||||||
<div class="card d-block mt-4">
|
<div class="card d-block mt-4">
|
||||||
<div class="card-body" *ngIf="loggingIn">
|
<div class="card-body" *ngIf="loggingIn">
|
||||||
<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>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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 { SyncService } from 'jslib-common/abstractions/sync.service';
|
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.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';
|
||||||
@@ -21,9 +20,8 @@ export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent
|
|||||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
||||||
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
|
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
|
||||||
cryptoService: CryptoService, userService: UserService,
|
cryptoService: CryptoService, userService: UserService,
|
||||||
messagingService: MessagingService, apiService: ApiService,
|
messagingService: MessagingService, apiService: ApiService) {
|
||||||
syncService: SyncService) {
|
|
||||||
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
|
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
|
||||||
userService, messagingService, apiService, syncService);
|
userService, messagingService, apiService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="mt-5 d-flex justify-content-center">
|
<div class="mt-5 d-flex justify-content-center">
|
||||||
<div>
|
<div>
|
||||||
<img class="mb-4 logo logo-themed" alt="Bitwarden">
|
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { AppComponent } from './app.component';
|
|||||||
import { OssRoutingModule } from './oss-routing.module';
|
import { OssRoutingModule } from './oss-routing.module';
|
||||||
import { OssModule } from './oss.module';
|
import { OssModule } from './oss.module';
|
||||||
import { ServicesModule } from './services/services.module';
|
import { ServicesModule } from './services/services.module';
|
||||||
import { WildcardRoutingModule } from './wildcard-routing.module';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,10 +21,6 @@ import { WildcardRoutingModule } from './wildcard-routing.module';
|
|||||||
InfiniteScrollModule,
|
InfiniteScrollModule,
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
OssRoutingModule,
|
OssRoutingModule,
|
||||||
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
AppComponent,
|
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { ToasterService } from 'angular2-toaster';
|
|||||||
|
|
||||||
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 { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
|
|
||||||
import { EventView } from 'jslib-common/models/view/eventView';
|
import { EventView } from 'jslib-common/models/view/eventView';
|
||||||
|
|
||||||
|
import { ListResponse } from 'jslib-common/models/response';
|
||||||
import { EventResponse } from 'jslib-common/models/response/eventResponse';
|
import { EventResponse } from 'jslib-common/models/response/eventResponse';
|
||||||
import { ListResponse } from 'jslib-common/models/response/listResponse';
|
|
||||||
|
|
||||||
|
import { LogService } from 'jslib-common/abstractions';
|
||||||
import { EventService } from 'src/app/services/event.service';
|
import { EventService } from 'src/app/services/event.service';
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
|
|||||||
@@ -253,10 +253,11 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
|
|||||||
comp.publicKey = publicKey;
|
comp.publicKey = publicKey;
|
||||||
comp.onConfirmedUser.subscribe(async () => {
|
comp.onConfirmedUser.subscribe(async () => {
|
||||||
try {
|
try {
|
||||||
comp.formPromise = confirmUser(publicKey);
|
await confirmUser(publicKey);
|
||||||
await comp.formPromise;
|
|
||||||
modal.close();
|
modal.close();
|
||||||
} catch { }
|
} catch (e) {
|
||||||
|
this.logService.error(`Handled exception: ${e}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
<div class="form-group mb-0">
|
|
||||||
<div class="form-check mt-1 form-check-block">
|
|
||||||
<input class="form-check-input" type="checkbox" [name]="pascalize(parentId)" [id]="parentId"
|
|
||||||
[(ngModel)]="parentChecked" [indeterminate]="parentIndeterminate">
|
|
||||||
<label class="form-check-label font-weight-normal" [for]="parentId">
|
|
||||||
{{parentId | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-group form-group-child-check mb-0">
|
|
||||||
<div class="form-check mt-1" *ngFor="let c of checkboxes">
|
|
||||||
<input class="form-check-input" type="checkbox" [name]="pascalize(c.id)" [id]="c.id" [ngModel]="c.get()"
|
|
||||||
(ngModelChange)="c.set($event)">
|
|
||||||
<label class="form-check-label font-weight-normal" [for]="c.id">
|
|
||||||
{{c.id | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-nested-checkbox',
|
|
||||||
templateUrl: 'nested-checkbox.component.html',
|
|
||||||
})
|
|
||||||
export class NestedCheckboxComponent {
|
|
||||||
@Input() parentId: string;
|
|
||||||
@Input() checkboxes: { id: string, get: () => boolean, set: (v: boolean) => void; }[];
|
|
||||||
@Output() onSavedUser = new EventEmitter();
|
|
||||||
@Output() onDeletedUser = new EventEmitter();
|
|
||||||
|
|
||||||
get parentIndeterminate() {
|
|
||||||
return !this.parentChecked &&
|
|
||||||
this.checkboxes.some(c => c.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
get parentChecked() {
|
|
||||||
return this.checkboxes.every(c => c.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
set parentChecked(value: boolean) {
|
|
||||||
this.checkboxes.forEach(c => {
|
|
||||||
c.set(value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pascalize(s: string) {
|
|
||||||
return Utils.camelToPascalCase(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<nav class="navbar navbar-expand navbar-dark" [ngClass]="{'nav-background-alt': selfHosted}">
|
<nav class="navbar navbar-expand navbar-dark bg-primary" [ngClass]="{'bg-secondary-alt': selfHosted}">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="navbar-brand" routerLink="/" appA11yTitle="{{'pageTitle' | i18n : 'Bitwarden'}}">
|
<a class="navbar-brand" routerLink="/" appA11yTitle="{{'pageTitle' | i18n : 'Bitwarden'}}">
|
||||||
<i class="fa fa-shield" aria-hidden="true"></i>
|
<i class="fa fa-shield" aria-hidden="true"></i>
|
||||||
|
|||||||
@@ -48,6 +48,15 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ml-auto d-flex align-items-center">
|
||||||
|
<button class="btn btn-primary" (click)="goToBusinessPortal()" #businessBtn
|
||||||
|
[appApiAction]="businessTokenPromise" *ngIf="showBusinessPortalButton">
|
||||||
|
<i class="fa fa-bank fa-fw" [hidden]="businessBtn.loading" aria-hidden="true"></i>
|
||||||
|
<i class="fa fa-spinner fa-spin fa-fw" [hidden]="!businessBtn.loading" title="{{'loading' | i18n}}"
|
||||||
|
aria-hidden="true"></i>
|
||||||
|
{{'businessPortal' | i18n}} →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
|
|
||||||
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
|
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
|
||||||
|
|
||||||
|
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||||
|
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { Organization } from 'jslib-common/models/domain/organization';
|
import { Organization } from 'jslib-common/models/domain/organization';
|
||||||
@@ -23,11 +26,16 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
|||||||
organization: Organization;
|
organization: Organization;
|
||||||
businessTokenPromise: Promise<any>;
|
businessTokenPromise: Promise<any>;
|
||||||
private organizationId: string;
|
private organizationId: string;
|
||||||
|
private businessUrl: string;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private userService: UserService,
|
constructor(private route: ActivatedRoute, private userService: UserService,
|
||||||
private broadcasterService: BroadcasterService, private ngZone: NgZone) { }
|
private broadcasterService: BroadcasterService, private ngZone: NgZone,
|
||||||
|
private apiService: ApiService, private platformUtilsService: PlatformUtilsService,
|
||||||
|
private environmentService: EnvironmentService) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.businessUrl = this.environmentService.getEnterpriseUrl();
|
||||||
|
|
||||||
document.body.classList.remove('layout_frontend');
|
document.body.classList.remove('layout_frontend');
|
||||||
this.route.params.subscribe(async params => {
|
this.route.params.subscribe(async params => {
|
||||||
this.organizationId = params.organizationId;
|
this.organizationId = params.organizationId;
|
||||||
@@ -52,14 +60,30 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
|||||||
this.organization = await this.userService.getOrganization(this.organizationId);
|
this.organization = await this.userService.getOrganization(this.organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async goToBusinessPortal() {
|
||||||
|
if (this.businessTokenPromise != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.businessTokenPromise = this.apiService.getEnterprisePortalSignInToken();
|
||||||
|
const token = await this.businessTokenPromise;
|
||||||
|
if (token != null) {
|
||||||
|
const userId = await this.userService.getUserId();
|
||||||
|
this.platformUtilsService.launchUri(this.businessUrl + '/login?userId=' + userId +
|
||||||
|
'&token=' + (window as any).encodeURIComponent(token) + '&organizationId=' + this.organization.id);
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
this.businessTokenPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
get showMenuBar() {
|
get showMenuBar() {
|
||||||
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
|
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showManageTab(): boolean {
|
get showManageTab(): boolean {
|
||||||
return this.organization.canManageUsers ||
|
return this.organization.canManageUsers ||
|
||||||
this.organization.canViewAllCollections ||
|
this.organization.canManageAssignedCollections ||
|
||||||
this.organization.canViewAssignedCollections ||
|
this.organization.canManageAllCollections ||
|
||||||
this.organization.canManageGroups ||
|
this.organization.canManageGroups ||
|
||||||
this.organization.canManagePolicies ||
|
this.organization.canManagePolicies ||
|
||||||
this.organization.canAccessEventLogs;
|
this.organization.canAccessEventLogs;
|
||||||
@@ -69,6 +93,10 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
|||||||
return this.organization.canAccessImportExport || this.organization.canAccessReports;
|
return this.organization.canAccessImportExport || this.organization.canAccessReports;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get showBusinessPortalButton(): boolean {
|
||||||
|
return this.organization.useBusinessPortal && this.organization.canAccessBusinessPortal;
|
||||||
|
}
|
||||||
|
|
||||||
get toolsRoute(): string {
|
get toolsRoute(): string {
|
||||||
return this.organization.canAccessImportExport ?
|
return this.organization.canAccessImportExport ?
|
||||||
'tools/import' :
|
'tools/import' :
|
||||||
@@ -81,7 +109,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
|||||||
case this.organization.canManageUsers:
|
case this.organization.canManageUsers:
|
||||||
route = 'manage/people';
|
route = 'manage/people';
|
||||||
break;
|
break;
|
||||||
case this.organization.canViewAssignedCollections || this.organization.canViewAllCollections:
|
case this.organization.canManageAssignedCollections || this.organization.canManageAllCollections:
|
||||||
route = 'manage/collections';
|
route = 'manage/collections';
|
||||||
break;
|
break;
|
||||||
case this.organization.canManageGroups:
|
case this.organization.canManageGroups:
|
||||||
|
|||||||
@@ -15,18 +15,17 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">{{'name' | i18n}}</label>
|
<label for="name">{{'name' | i18n}}</label>
|
||||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required
|
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required
|
||||||
appAutofocus [disabled]="!this.canSave">
|
appAutofocus>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="externalId">{{'externalId' | i18n}}</label>
|
<label for="externalId">{{'externalId' | i18n}}</label>
|
||||||
<input id="externalId" class="form-control" type="text" name="ExternalId" [(ngModel)]="externalId"
|
<input id="externalId" class="form-control" type="text" name="ExternalId" [(ngModel)]="externalId">
|
||||||
[disabled]="!this.canSave">
|
|
||||||
<small class="form-text text-muted">{{'externalIdDesc' | i18n}}</small>
|
<small class="form-text text-muted">{{'externalIdDesc' | i18n}}</small>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="accessGroups">
|
<ng-container *ngIf="accessGroups">
|
||||||
<h3 class="mt-4 d-flex mb-0">
|
<h3 class="mt-4 d-flex mb-0">
|
||||||
{{'groupAccess' | i18n}}
|
{{'groupAccess' | i18n}}
|
||||||
<div class="ml-auto" *ngIf="groups && groups.length && this.canSave">
|
<div class="ml-auto" *ngIf="groups && groups.length">
|
||||||
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||||
{{'selectAll' | i18n}}
|
{{'selectAll' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
@@ -51,7 +50,7 @@
|
|||||||
<tr *ngFor="let g of groups; let i = index">
|
<tr *ngFor="let g of groups; let i = index">
|
||||||
<td class="table-list-checkbox" (click)="check(g)">
|
<td class="table-list-checkbox" (click)="check(g)">
|
||||||
<input type="checkbox" [(ngModel)]="g.checked" name="Groups[{{i}}].Checked"
|
<input type="checkbox" [(ngModel)]="g.checked" name="Groups[{{i}}].Checked"
|
||||||
[disabled]="g.accessAll || !this.canSave" appStopProp>
|
[disabled]="g.accessAll" appStopProp>
|
||||||
</td>
|
</td>
|
||||||
<td (click)="check(g)">
|
<td (click)="check(g)">
|
||||||
{{g.name}}
|
{{g.name}}
|
||||||
@@ -63,11 +62,11 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<input type="checkbox" [(ngModel)]="g.hidePasswords"
|
<input type="checkbox" [(ngModel)]="g.hidePasswords"
|
||||||
name="Groups[{{i}}].HidePasswords" [disabled]="!g.checked || g.accessAll || !this.canSave">
|
name="Groups[{{i}}].HidePasswords" [disabled]="!g.checked || g.accessAll">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<input type="checkbox" [(ngModel)]="g.readOnly" name="Groups[{{i}}].ReadOnly"
|
<input type="checkbox" [(ngModel)]="g.readOnly" name="Groups[{{i}}].ReadOnly"
|
||||||
[disabled]="!g.checked || g.accessAll || !this.canSave">
|
[disabled]="!g.checked || g.accessAll">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -75,23 +74,22 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading" *ngIf="this.canSave">
|
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||||
<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>
|
||||||
<span>{{'save' | i18n}}</span>
|
<span>{{'save' | i18n}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
<button type="button" class="btn btn-outline-secondary"
|
||||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||||
<div class="ml-auto" *ngIf="this.canDelete">
|
<div class="ml-auto">
|
||||||
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
|
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
|
||||||
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode"
|
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||||
[disabled]="deleteBtn.loading" [appApiAction]="deletePromise">
|
[appApiAction]="deletePromise">
|
||||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
|
<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"
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
|
||||||
title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ import { Utils } from 'jslib-common/misc/utils';
|
|||||||
export class CollectionAddEditComponent implements OnInit {
|
export class CollectionAddEditComponent implements OnInit {
|
||||||
@Input() collectionId: string;
|
@Input() collectionId: string;
|
||||||
@Input() organizationId: string;
|
@Input() organizationId: string;
|
||||||
@Input() canSave: boolean;
|
|
||||||
@Input() canDelete: boolean;
|
|
||||||
@Output() onSavedCollection = new EventEmitter();
|
@Output() onSavedCollection = new EventEmitter();
|
||||||
@Output() onDeletedCollection = new EventEmitter();
|
@Output() onDeletedCollection = new EventEmitter();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}"
|
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}"
|
||||||
[(ngModel)]="searchText">
|
[(ngModel)]="searchText">
|
||||||
</div>
|
</div>
|
||||||
<button type="button" *ngIf="this.canCreate" class="btn btn-sm btn-outline-primary ml-3" (click)="add()">
|
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="add()">
|
||||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
||||||
{{'newCollection' | i18n}}
|
{{'newCollection' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
@@ -27,17 +27,17 @@
|
|||||||
<a href="#" appStopClick (click)="edit(c)">{{c.name}}</a>
|
<a href="#" appStopClick (click)="edit(c)">{{c.name}}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-list-options">
|
<td class="table-list-options">
|
||||||
<div class="dropdown" appListDropdown *ngIf="this.canEdit(c) || this.canDelete(c)">
|
<div class="dropdown" appListDropdown>
|
||||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown"
|
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown"
|
||||||
aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
|
aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
|
||||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
<a class="dropdown-item" href="#" appStopClick *ngIf="this.canEdit(c)" (click)="users(c)">
|
<a class="dropdown-item" href="#" appStopClick (click)="users(c)">
|
||||||
<i class="fa fa-fw fa-users" aria-hidden="true"></i>
|
<i class="fa fa-fw fa-users" aria-hidden="true"></i>
|
||||||
{{'users' | i18n}}
|
{{'users' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
<a class="dropdown-item text-danger" href="#" appStopClick *ngIf="this.canDelete(c)" (click)="delete(c)">
|
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(c)">
|
||||||
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
|
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
|
||||||
{{'delete' | i18n}}
|
{{'delete' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { ModalService } from 'jslib-angular/services/modal.service';
|
|||||||
|
|
||||||
import { CollectionData } from 'jslib-common/models/data/collectionData';
|
import { CollectionData } from 'jslib-common/models/data/collectionData';
|
||||||
import { Collection } from 'jslib-common/models/domain/collection';
|
import { Collection } from 'jslib-common/models/domain/collection';
|
||||||
import { Organization } from 'jslib-common/models/domain/organization';
|
|
||||||
import {
|
import {
|
||||||
CollectionDetailsResponse,
|
CollectionDetailsResponse,
|
||||||
CollectionResponse,
|
CollectionResponse,
|
||||||
@@ -38,11 +37,8 @@ export class CollectionsComponent implements OnInit {
|
|||||||
@ViewChild('usersTemplate', { read: ViewContainerRef, static: true }) usersModalRef: ViewContainerRef;
|
@ViewChild('usersTemplate', { read: ViewContainerRef, static: true }) usersModalRef: ViewContainerRef;
|
||||||
|
|
||||||
loading = true;
|
loading = true;
|
||||||
organization: Organization;
|
|
||||||
canCreate: boolean = false;
|
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
collections: CollectionView[];
|
collections: CollectionView[];
|
||||||
assignedCollections: CollectionView[];
|
|
||||||
pagedCollections: CollectionView[];
|
pagedCollections: CollectionView[];
|
||||||
searchText: string;
|
searchText: string;
|
||||||
|
|
||||||
@@ -71,27 +67,16 @@ export class CollectionsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.organization = await this.userService.getOrganization(this.organizationId);
|
const organization = await this.userService.getOrganization(this.organizationId);
|
||||||
this.canCreate = this.organization.canCreateNewCollections;
|
let response: ListResponse<CollectionResponse>;
|
||||||
|
if (organization.canManageAllCollections) {
|
||||||
const decryptCollections = async (r: ListResponse<CollectionResponse>) => {
|
response = await this.apiService.getCollections(this.organizationId);
|
||||||
const collections = r.data.filter(c => c.organizationId === this.organizationId).map(d =>
|
|
||||||
new Collection(new CollectionData(d as CollectionDetailsResponse)));
|
|
||||||
return await this.collectionService.decryptMany(collections);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.organization.canViewAssignedCollections) {
|
|
||||||
const response = await this.apiService.getUserCollections();
|
|
||||||
this.assignedCollections = await decryptCollections(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.organization.canViewAllCollections) {
|
|
||||||
const response = await this.apiService.getCollections(this.organizationId);
|
|
||||||
this.collections = await decryptCollections(response);
|
|
||||||
} else {
|
} else {
|
||||||
this.collections = this.assignedCollections;
|
response = await this.apiService.getUserCollections();
|
||||||
}
|
}
|
||||||
|
const collections = response.data.filter(c => c.organizationId === this.organizationId).map(r =>
|
||||||
|
new Collection(new CollectionData(r as CollectionDetailsResponse)));
|
||||||
|
this.collections = await this.collectionService.decryptMany(collections);
|
||||||
this.resetPaging();
|
this.resetPaging();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@@ -114,20 +99,9 @@ export class CollectionsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async edit(collection: CollectionView) {
|
async edit(collection: CollectionView) {
|
||||||
const canCreate = collection == null && this.canCreate;
|
|
||||||
const canEdit = collection != null && this.canEdit(collection);
|
|
||||||
const canDelete = collection != null && this.canDelete(collection);
|
|
||||||
|
|
||||||
if (!(canCreate || canEdit || canDelete)) {
|
|
||||||
this.toasterService.popAsync('error', null, this.i18nService.t('missingPermissions'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [modal] = await this.modalService.openViewRef(CollectionAddEditComponent, this.addEditModalRef, comp => {
|
const [modal] = await this.modalService.openViewRef(CollectionAddEditComponent, this.addEditModalRef, comp => {
|
||||||
comp.organizationId = this.organizationId;
|
comp.organizationId = this.organizationId;
|
||||||
comp.collectionId = collection != null ? collection.id : null;
|
comp.collectionId = collection != null ? collection.id : null;
|
||||||
comp.canSave = canCreate || canEdit;
|
|
||||||
comp.canDelete = canDelete;
|
|
||||||
comp.onSavedCollection.subscribe(() => {
|
comp.onSavedCollection.subscribe(() => {
|
||||||
modal.close();
|
modal.close();
|
||||||
this.load();
|
this.load();
|
||||||
@@ -155,9 +129,7 @@ export class CollectionsComponent implements OnInit {
|
|||||||
await this.apiService.deleteCollection(this.organizationId, collection.id);
|
await this.apiService.deleteCollection(this.organizationId, collection.id);
|
||||||
this.toasterService.popAsync('success', null, this.i18nService.t('deletedCollectionId', collection.name));
|
this.toasterService.popAsync('success', null, this.i18nService.t('deletedCollectionId', collection.name));
|
||||||
this.removeCollection(collection);
|
this.removeCollection(collection);
|
||||||
} catch {
|
} catch { }
|
||||||
this.toasterService.popAsync('error', null, this.i18nService.t('missingPermissions'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async users(collection: CollectionView) {
|
async users(collection: CollectionView) {
|
||||||
@@ -191,28 +163,6 @@ export class CollectionsComponent implements OnInit {
|
|||||||
return !searching && this.collections && this.collections.length > this.pageSize;
|
return !searching && this.collections && this.collections.length > this.pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
canEdit(collection: CollectionView) {
|
|
||||||
if (this.organization.canEditAnyCollection) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.organization.canEditAssignedCollections && this.assignedCollections.some(c => c.id === collection.id)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
canDelete(collection: CollectionView) {
|
|
||||||
if (this.organization.canDeleteAnyCollection) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.organization.canDeleteAssignedCollections && this.assignedCollections.some(c => c.id === collection.id)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeCollection(collection: CollectionView) {
|
private removeCollection(collection: CollectionView) {
|
||||||
const index = this.collections.indexOf(collection);
|
const index = this.collections.indexOf(collection);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
{{'people' | i18n}}
|
{{'people' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
<a routerLink="collections" class="list-group-item" routerLinkActive="active"
|
<a routerLink="collections" class="list-group-item" routerLinkActive="active"
|
||||||
*ngIf="organization.canViewAllCollections || organization.canViewAssignedCollections">
|
*ngIf="organization.canManageAssignedCollections || organization.canManageAllCollections">
|
||||||
{{'collections' | i18n}}
|
{{'collections' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
<a routerLink="groups" class="list-group-item" routerLinkActive="active"
|
<a routerLink="groups" class="list-group-item" routerLinkActive="active"
|
||||||
@@ -20,10 +20,6 @@
|
|||||||
*ngIf="organization.canManagePolicies && accessPolicies">
|
*ngIf="organization.canManagePolicies && accessPolicies">
|
||||||
{{'policies' | i18n}}
|
{{'policies' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
<a routerLink="sso" class="list-group-item" routerLinkActive="active"
|
|
||||||
*ngIf="organization.canManageSso && accessSso">
|
|
||||||
{{'singleSignOn' | i18n}}
|
|
||||||
</a>
|
|
||||||
<a routerLink="events" class="list-group-item" routerLinkActive="active"
|
<a routerLink="events" class="list-group-item" routerLinkActive="active"
|
||||||
*ngIf="organization.canAccessEventLogs && accessEvents">
|
*ngIf="organization.canAccessEventLogs && accessEvents">
|
||||||
{{'eventLogs' | i18n}}
|
{{'eventLogs' | i18n}}
|
||||||
|
|||||||
@@ -14,18 +14,16 @@ import { Organization } from 'jslib-common/models/domain/organization';
|
|||||||
})
|
})
|
||||||
export class ManageComponent implements OnInit {
|
export class ManageComponent implements OnInit {
|
||||||
organization: Organization;
|
organization: Organization;
|
||||||
accessPolicies: boolean = false;
|
accessPolicies = false;
|
||||||
accessGroups: boolean = false;
|
accessGroups = false;
|
||||||
accessEvents: boolean = false;
|
accessEvents = false;
|
||||||
accessSso: boolean = false;
|
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private userService: UserService) {}
|
constructor(private route: ActivatedRoute, private userService: UserService) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.route.parent.params.subscribe(async params => {
|
this.route.parent.params.subscribe(async params => {
|
||||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||||
this.accessPolicies = this.organization.usePolicies;
|
this.accessPolicies = this.organization.usePolicies;
|
||||||
this.accessSso = this.organization.useSso;
|
|
||||||
this.accessEvents = this.organization.useEvents;
|
this.accessEvents = this.organization.useEvents;
|
||||||
this.accessGroups = this.organization.useGroups;
|
this.accessGroups = this.organization.useGroups;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import {
|
|||||||
import { PolicyType } from 'jslib-common/enums/policyType';
|
import { PolicyType } from 'jslib-common/enums/policyType';
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||||
|
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||||
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -22,7 +25,7 @@ import { Organization } from 'jslib-common/models/domain/organization';
|
|||||||
|
|
||||||
import { PolicyEditComponent } from './policy-edit.component';
|
import { PolicyEditComponent } from './policy-edit.component';
|
||||||
|
|
||||||
import { PolicyListService } from '../../services/policy-list.service';
|
import { PolicyListService } from 'src/app/services/policy-list.service';
|
||||||
import { BasePolicy } from '../policies/base-policy.component';
|
import { BasePolicy } from '../policies/base-policy.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -37,12 +40,19 @@ export class PoliciesComponent implements OnInit {
|
|||||||
policies: BasePolicy[];
|
policies: BasePolicy[];
|
||||||
organization: Organization;
|
organization: Organization;
|
||||||
|
|
||||||
|
// Remove when removing deprecation warning
|
||||||
|
enterpriseTokenPromise: Promise<any>;
|
||||||
|
|
||||||
|
private enterpriseUrl: string;
|
||||||
|
|
||||||
private orgPolicies: PolicyResponse[];
|
private orgPolicies: PolicyResponse[];
|
||||||
private policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
|
private policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||||
private modalService: ModalService, private userService: UserService,
|
private i18nService: I18nService, private modalService: ModalService,
|
||||||
private policyListService: PolicyListService, private router: Router) { }
|
private platformUtilsService: PlatformUtilsService, private userService: UserService,
|
||||||
|
private policyListService: PolicyListService, private router: Router,
|
||||||
|
private environmentService: EnvironmentService) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.route.parent.parent.params.subscribe(async params => {
|
this.route.parent.parent.params.subscribe(async params => {
|
||||||
@@ -79,6 +89,9 @@ export class PoliciesComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Remove when removing deprecation warning
|
||||||
|
this.enterpriseUrl = this.environmentService.getEnterpriseUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
@@ -102,4 +115,21 @@ export class PoliciesComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove when removing deprecation warning
|
||||||
|
async goToEnterprisePortal() {
|
||||||
|
if (this.enterpriseTokenPromise != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.enterpriseTokenPromise = this.apiService.getEnterprisePortalSignInToken();
|
||||||
|
const token = await this.enterpriseTokenPromise;
|
||||||
|
if (token != null) {
|
||||||
|
const userId = await this.userService.getUserId();
|
||||||
|
this.platformUtilsService.launchUri(this.enterpriseUrl + '/login?userId=' + userId +
|
||||||
|
'&token=' + (window as any).encodeURIComponent(token) + '&organizationId=' + this.organizationId);
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
this.enterpriseTokenPromise = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,15 +80,32 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="font-weight-bold mb-0">Manager Permissions</label>
|
<label class="font-weight-bold mb-0">Manager Permissions</label>
|
||||||
<hr class="my-0 mr-2" />
|
<hr class="my-0 mr-2" />
|
||||||
<app-nested-checkbox parentId="manageAssignedCollections"
|
<div class="form-group mb-0">
|
||||||
[checkboxes]="manageAssignedCollectionsCheckboxes">
|
<div class="form-check mt-1 form-check-block">
|
||||||
</app-nested-checkbox>
|
<input class="form-check-input" type="checkbox" name="manageAssignedCollections"
|
||||||
|
id="manageAssignedCollections"
|
||||||
|
[(ngModel)]="permissions.manageAssignedCollections">
|
||||||
|
<label class="form-check-label font-weight-normal"
|
||||||
|
for="manageAssignedCollections">
|
||||||
|
{{'manageAssignedCollections' | i18n}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="font-weight-bold mb-0">Admin Permissions</label>
|
<label class="font-weight-bold mb-0">Admin Permissions</label>
|
||||||
<hr class="my-0 mr-2" />
|
<hr class="my-0 mr-2" />
|
||||||
|
<div class="form-group mb-0">
|
||||||
|
<div class="form-check mt-1 form-check-block">
|
||||||
|
<input class="form-check-input" type="checkbox" name="accessBusinessPortal"
|
||||||
|
id="accessBusinessPortal" [(ngModel)]="permissions.accessBusinessPortal">
|
||||||
|
<label class="form-check-label font-weight-normal" for="accessBusinessPortal">
|
||||||
|
{{'accessBusinessPortal' | i18n}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group mb-0">
|
<div class="form-group mb-0">
|
||||||
<div class="form-check mt-1 form-check-block">
|
<div class="form-check mt-1 form-check-block">
|
||||||
<input class="form-check-input" type="checkbox" name="accessEventLogs"
|
<input class="form-check-input" type="checkbox" name="accessEventLogs"
|
||||||
@@ -116,9 +133,15 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-nested-checkbox parentId="manageAllCollections"
|
<div class="form-group mb-0">
|
||||||
[checkboxes]="manageAllCollectionsCheckboxes">
|
<div class="form-check mt-1 form-check-block">
|
||||||
</app-nested-checkbox>
|
<input class="form-check-input" type="checkbox" name="manageAllCollections"
|
||||||
|
id="manageAllCollections" [(ngModel)]="permissions.manageAllCollections">
|
||||||
|
<label class="form-check-label font-weight-normal" for="manageAllCollections">
|
||||||
|
{{'manageAllCollections' | i18n}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group mb-0">
|
<div class="form-group mb-0">
|
||||||
<div class="form-check mt-1 form-check-block">
|
<div class="form-check mt-1 form-check-block">
|
||||||
<input class="form-check-input" type="checkbox" name="manageGroups"
|
<input class="form-check-input" type="checkbox" name="manageGroups"
|
||||||
|
|||||||
@@ -48,37 +48,6 @@ export class UserAddEditComponent implements OnInit {
|
|||||||
deletePromise: Promise<any>;
|
deletePromise: Promise<any>;
|
||||||
organizationUserType = OrganizationUserType;
|
organizationUserType = OrganizationUserType;
|
||||||
|
|
||||||
manageAllCollectionsCheckboxes = [
|
|
||||||
{
|
|
||||||
id: 'createNewCollections',
|
|
||||||
get: () => this.permissions.createNewCollections,
|
|
||||||
set: (v: boolean) => this.permissions.createNewCollections = v,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'editAnyCollection',
|
|
||||||
get: () => this.permissions.editAnyCollection,
|
|
||||||
set: (v: boolean) => this.permissions.editAnyCollection = v,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'deleteAnyCollection',
|
|
||||||
get: () => this.permissions.deleteAnyCollection,
|
|
||||||
set: (v: boolean) => this.permissions.deleteAnyCollection = v,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
manageAssignedCollectionsCheckboxes = [
|
|
||||||
{
|
|
||||||
id: 'editAssignedCollections',
|
|
||||||
get: () => this.permissions.editAssignedCollections,
|
|
||||||
set: (v: boolean) => this.permissions.editAssignedCollections = v,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'deleteAssignedCollections',
|
|
||||||
get: () => this.permissions.deleteAssignedCollections,
|
|
||||||
set: (v: boolean) => this.permissions.deleteAssignedCollections = v,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
get customUserTypeSelected(): boolean {
|
get customUserTypeSelected(): boolean {
|
||||||
return this.type === OrganizationUserType.Custom;
|
return this.type === OrganizationUserType.Custom;
|
||||||
}
|
}
|
||||||
@@ -138,7 +107,39 @@ export class UserAddEditComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setRequestPermissions(p: PermissionsApi, clearPermissions: boolean) {
|
setRequestPermissions(p: PermissionsApi, clearPermissions: boolean) {
|
||||||
Object.assign(p, clearPermissions ? new PermissionsApi() : this.permissions);
|
p.accessBusinessPortal = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.accessBusinessPortal;
|
||||||
|
p.accessEventLogs = this.permissions.accessEventLogs = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.accessEventLogs;
|
||||||
|
p.accessImportExport = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.accessImportExport;
|
||||||
|
p.accessReports = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.accessReports;
|
||||||
|
p.manageAllCollections = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.manageAllCollections;
|
||||||
|
p.manageAssignedCollections = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.manageAssignedCollections;
|
||||||
|
p.manageGroups = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.manageGroups;
|
||||||
|
p.manageSso = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.manageSso;
|
||||||
|
p.managePolicies = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.managePolicies;
|
||||||
|
p.manageUsers = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.manageUsers;
|
||||||
|
p.manageResetPassword = clearPermissions ?
|
||||||
|
false :
|
||||||
|
this.permissions.manageResetPassword;
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,4 +203,5 @@ export class UserAddEditComponent implements OnInit {
|
|||||||
this.onDeletedUser.emit();
|
this.onDeletedUser.emit();
|
||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
|
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
|
||||||
<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()" [appApiAction]="formPromise">
|
<form class="modal-content" #form (ngSubmit)="submit()">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2 class="modal-title" id="confirmUserTitle">
|
<h2 class="modal-title" id="confirmUserTitle">
|
||||||
{{'confirmUser' | i18n}}
|
{{'confirmUser' | i18n}}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ export class UserConfirmComponent implements OnInit {
|
|||||||
dontAskAgain = false;
|
dontAskAgain = false;
|
||||||
loading = true;
|
loading = true;
|
||||||
fingerprint: string;
|
fingerprint: string;
|
||||||
formPromise: Promise<any>;
|
|
||||||
|
|
||||||
constructor(private cryptoService: CryptoService, private storageService: StorageService) { }
|
constructor(private cryptoService: CryptoService, private storageService: StorageService) { }
|
||||||
|
|
||||||
|
|||||||
@@ -35,28 +35,19 @@ export abstract class BasePolicyComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.enabled.setValue(this.policyResponse.enabled);
|
this.enabled.setValue(this.policyResponse.enabled);
|
||||||
|
|
||||||
if (this.policyResponse.data != null) {
|
|
||||||
this.loadData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadData() {
|
|
||||||
this.data.patchValue(this.policyResponse.data ?? {});
|
|
||||||
}
|
|
||||||
|
|
||||||
buildRequestData() {
|
|
||||||
if (this.data != null) {
|
if (this.data != null) {
|
||||||
return this.data.value;
|
this.data.patchValue(this.policyResponse.data ?? {});
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>) {
|
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>) {
|
||||||
const request = new PolicyRequest();
|
const request = new PolicyRequest();
|
||||||
request.enabled = this.enabled.value;
|
request.enabled = this.enabled.value;
|
||||||
request.type = this.policy.type;
|
request.type = this.policy.type;
|
||||||
request.data = this.buildRequestData();
|
|
||||||
|
if (this.data != null) {
|
||||||
|
request.data = this.data.value;
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.resolve(request);
|
return Promise.resolve(request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,14 +26,9 @@ export class SingleOrgPolicyComponent extends BasePolicyComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
||||||
if (!this.enabled.value) {
|
const requireSsoEnabled = policiesEnabledMap.get(PolicyType.RequireSso) ?? false;
|
||||||
if (policiesEnabledMap.get(PolicyType.RequireSso) ?? false) {
|
if (!this.enabled.value && requireSsoEnabled) {
|
||||||
throw new Error(this.i18nService.t('disableRequiredError', this.i18nService.t('requireSso')));
|
throw new Error(this.i18nService.t('disableRequireSsoError'));
|
||||||
}
|
|
||||||
|
|
||||||
if (policiesEnabledMap.get(PolicyType.MaximumVaultTimeout) ?? false) {
|
|
||||||
throw new Error(this.i18nService.t('disableRequiredError', this.i18nService.t('maximumVaultTimeoutLabel')));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.buildRequest(policiesEnabledMap);
|
return super.buildRequest(policiesEnabledMap);
|
||||||
|
|||||||
29
src/app/organizations/settings/adjust-seats.component.html
Normal file
29
src/app/organizations/settings/adjust-seats.component.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<form #form class="card" (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||||
|
<div class="card-body">
|
||||||
|
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}" (click)="cancel()"><span
|
||||||
|
aria-hidden="true">×</span></button>
|
||||||
|
<h3 class="card-body-header">{{(add ? 'addSeats' : 'removeSeats') | i18n}}</h3>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group col-6">
|
||||||
|
<label for="seatAdjustment">{{(add ? 'seatsToAdd' : 'seatsToRemove') | i18n}}</label>
|
||||||
|
<input id="seatAdjustment" class="form-control" type="number" name="SeatAdjustment"
|
||||||
|
[(ngModel)]="seatAdjustment" min="0" step="1" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="add" class="mb-3">
|
||||||
|
<strong>{{'total' | i18n}}:</strong> {{seatAdjustment || 0}} × {{seatPrice | currency:'$'}} = {{adjustedSeatTotal
|
||||||
|
| currency:'$'}} /{{interval | i18n}}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||||
|
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
|
<span>{{'submit' | i18n}}</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="cancel()">
|
||||||
|
{{'cancel' | i18n}}
|
||||||
|
</button>
|
||||||
|
<small class="d-block text-muted mt-3">
|
||||||
|
{{(add ? 'seatsAddNote' : 'seatsRemoveNote') | i18n}}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<app-payment [showMethods]="false"></app-payment>
|
||||||
87
src/app/organizations/settings/adjust-seats.component.ts
Normal file
87
src/app/organizations/settings/adjust-seats.component.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
ViewChild,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActivatedRoute,
|
||||||
|
Router,
|
||||||
|
} from '@angular/router';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
|
||||||
|
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||||
|
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||||
|
|
||||||
|
import { SeatRequest } from 'jslib-common/models/request/seatRequest';
|
||||||
|
|
||||||
|
import { PaymentComponent } from '../../settings/payment.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-adjust-seats',
|
||||||
|
templateUrl: 'adjust-seats.component.html',
|
||||||
|
})
|
||||||
|
export class AdjustSeatsComponent {
|
||||||
|
@Input() seatPrice = 0;
|
||||||
|
@Input() add = true;
|
||||||
|
@Input() organizationId: string;
|
||||||
|
@Input() interval = 'year';
|
||||||
|
@Output() onAdjusted = new EventEmitter<number>();
|
||||||
|
@Output() onCanceled = new EventEmitter();
|
||||||
|
|
||||||
|
@ViewChild(PaymentComponent, { static: true }) paymentComponent: PaymentComponent;
|
||||||
|
|
||||||
|
seatAdjustment = 0;
|
||||||
|
formPromise: Promise<any>;
|
||||||
|
|
||||||
|
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||||
|
private toasterService: ToasterService, private router: Router,
|
||||||
|
private activatedRoute: ActivatedRoute) { }
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
try {
|
||||||
|
const request = new SeatRequest();
|
||||||
|
request.seatAdjustment = this.seatAdjustment;
|
||||||
|
if (!this.add) {
|
||||||
|
request.seatAdjustment *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let paymentFailed = false;
|
||||||
|
const action = async () => {
|
||||||
|
const result = await this.apiService.postOrganizationSeat(this.organizationId, request);
|
||||||
|
if (result != null && result.paymentIntentClientSecret != null) {
|
||||||
|
try {
|
||||||
|
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);
|
||||||
|
} catch {
|
||||||
|
paymentFailed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.formPromise = action();
|
||||||
|
await this.formPromise;
|
||||||
|
this.onAdjusted.emit(this.seatAdjustment);
|
||||||
|
if (paymentFailed) {
|
||||||
|
this.toasterService.popAsync({
|
||||||
|
body: this.i18nService.t('couldNotChargeCardPayInvoice'),
|
||||||
|
type: 'warning',
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
this.router.navigate(['../billing'], { relativeTo: this.activatedRoute });
|
||||||
|
} else {
|
||||||
|
this.toasterService.popAsync('success', null,
|
||||||
|
this.i18nService.t('adjustedSeats', request.seatAdjustment.toString()));
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.onCanceled.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
get adjustedSeatTotal(): number {
|
||||||
|
return this.seatAdjustment * this.seatPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
|
||||||
<div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="form-group col-6">
|
|
||||||
<label for="newSeatCount">{{'subscriptionSeats' | i18n}}</label>
|
|
||||||
<input id="newSeatCount" class="form-control" type="number" name="NewSeatCount"
|
|
||||||
[(ngModel)]="newSeatCount" min="0" step="1" required>
|
|
||||||
<small class="d-block text-muted mb-4">
|
|
||||||
<strong>{{'total' | i18n}}:</strong> {{newSeatCount || 0}} × {{seatPrice | currency:'$'}} =
|
|
||||||
{{adjustedSeatTotal | currency:'$'}} / {{interval | i18n}}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-4">
|
|
||||||
<div class="form-group col-sm">
|
|
||||||
<div class="form-check">
|
|
||||||
<input id="limitSubscription" class="form-check-input" type="checkbox" name="LimitSubscription"
|
|
||||||
[(ngModel)]="limitSubscription" (change)="limitSubscriptionChanged()">
|
|
||||||
<label for="limitSubscription">{{'limitSubscription' | i18n}}</label>
|
|
||||||
</div>
|
|
||||||
<small class="d-block text-muted">{{'limitSubscriptionDesc' | i18n}}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-4" [hidden]="!limitSubscription">
|
|
||||||
<div class="form-group col-sm">
|
|
||||||
<label for="maxAutoscaleSeats">{{'maxSeatLimit' | i18n}}</label>
|
|
||||||
<input id="maxAutoscaleSeats" class="form-control col-6" type="number" name="MaxAutoscaleSeats"
|
|
||||||
[(ngModel)]="newMaxSeats" [min]="newSeatCount == null ? 1 : newSeatCount" step="1"
|
|
||||||
[required]="limitSubscription">
|
|
||||||
<small class="d-block text-muted">
|
|
||||||
<strong>{{'maxSeatCost' | i18n}}:</strong> {{newMaxSeats || 0}} ×
|
|
||||||
{{seatPrice | currency:'$'}} = {{maxSeatTotal | currency:'$'}} / {{interval | i18n}}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
|
||||||
<span>{{'save' | i18n}}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<app-payment [showMethods]="false"></app-payment>
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { ToasterService } from 'angular2-toaster';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
import { OrganizationSubscriptionUpdateRequest } from 'jslib-common/models/request/organizationSubscriptionUpdateRequest';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-adjust-subscription',
|
|
||||||
templateUrl: 'adjust-subscription.component.html',
|
|
||||||
})
|
|
||||||
export class AdjustSubscription {
|
|
||||||
@Input() organizationId: string;
|
|
||||||
@Input() maxAutoscaleSeats: number;
|
|
||||||
@Input() currentSeatCount: number;
|
|
||||||
@Input() seatPrice = 0;
|
|
||||||
@Input() interval = 'year';
|
|
||||||
@Output() onAdjusted = new EventEmitter();
|
|
||||||
|
|
||||||
formPromise: Promise<any>;
|
|
||||||
limitSubscription: boolean;
|
|
||||||
newSeatCount: number;
|
|
||||||
newMaxSeats: number;
|
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
|
||||||
private toasterService: ToasterService) { }
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.limitSubscription = this.maxAutoscaleSeats != null;
|
|
||||||
this.newSeatCount = this.currentSeatCount;
|
|
||||||
this.newMaxSeats = this.maxAutoscaleSeats;
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
|
||||||
try {
|
|
||||||
const seatAdjustment = this.newSeatCount - this.currentSeatCount;
|
|
||||||
const request = new OrganizationSubscriptionUpdateRequest(seatAdjustment, this.newMaxSeats);
|
|
||||||
this.formPromise = this.apiService.postOrganizationUpdateSubscription(this.organizationId, request);
|
|
||||||
|
|
||||||
await this.formPromise;
|
|
||||||
|
|
||||||
this.toasterService.popAsync('success', null, this.i18nService.t('subscriptionUpdated'));
|
|
||||||
} catch { }
|
|
||||||
this.onAdjusted.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
limitSubscriptionChanged() {
|
|
||||||
if (!this.limitSubscription) {
|
|
||||||
this.newMaxSeats = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get adjustedSeatTotal(): number {
|
|
||||||
return this.newSeatCount * this.seatPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
get maxSeatTotal(): number {
|
|
||||||
return this.newMaxSeats * this.seatPrice;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,67 +23,110 @@
|
|||||||
<span>{{'reinstateSubscription' | i18n}}</span>
|
<span>{{'reinstateSubscription' | i18n}}</span>
|
||||||
</button>
|
</button>
|
||||||
</app-callout>
|
</app-callout>
|
||||||
<ng-container *ngIf="!selfHosted">
|
<dl *ngIf="selfHosted">
|
||||||
<div class="row">
|
<dt>{{'billingPlan' | i18n}}</dt>
|
||||||
<div class="col-4">
|
<dd>{{sub.plan.name}}</dd>
|
||||||
<dl>
|
<dt>{{'expiration' | i18n}}</dt>
|
||||||
<dt>{{'billingPlan' | i18n}}</dt>
|
<dd *ngIf="sub.expiration">
|
||||||
<dd>{{sub.plan.name}}</dd>
|
{{sub.expiration | date:'mediumDate'}}
|
||||||
<ng-container *ngIf="subscription">
|
<span *ngIf="isExpired" class="text-danger ml-2">
|
||||||
<dt>{{'status' | i18n}}</dt>
|
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||||
<dd>
|
{{'licenseIsExpired' | i18n}}
|
||||||
<span class="text-capitalize">{{subscription.status || '-'}}</span>
|
</span>
|
||||||
<span class="badge badge-warning"
|
</dd>
|
||||||
*ngIf="subscriptionMarkedForCancel">{{'pendingCancellation' |
|
<dd *ngIf="!sub.expiration">{{'neverExpires' | i18n}}</dd>
|
||||||
i18n}}</span>
|
</dl>
|
||||||
</dd>
|
<div class="row" *ngIf="!selfHosted">
|
||||||
<dt>{{'nextCharge' | i18n}}</dt>
|
<div class="col-4">
|
||||||
<dd>{{nextInvoice ? ((nextInvoice.date | date: 'mediumDate') + ', ' + (nextInvoice.amount |
|
<dl>
|
||||||
currency:'$'))
|
<dt>{{'billingPlan' | i18n}}</dt>
|
||||||
: '-'}}
|
<dd>{{sub.plan.name}}</dd>
|
||||||
</dd>
|
<ng-container *ngIf="subscription">
|
||||||
</ng-container>
|
<dt>{{'status' | i18n}}</dt>
|
||||||
</dl>
|
<dd>
|
||||||
</div>
|
<span class="text-capitalize">{{subscription.status || '-'}}</span>
|
||||||
<div class="col-8" *ngIf="subscription">
|
<span class="badge badge-warning"
|
||||||
<strong class="d-block mb-1">{{'details' | i18n}}</strong>
|
*ngIf="subscriptionMarkedForCancel">{{'pendingCancellation' | i18n}}</span>
|
||||||
<table class="table">
|
</dd>
|
||||||
<tbody>
|
<dt>{{'nextCharge' | i18n}}</dt>
|
||||||
<tr *ngFor="let i of subscription.items">
|
<dd>{{nextInvoice ? ((nextInvoice.date | date: 'mediumDate') + ', ' + (nextInvoice.amount | currency:'$'))
|
||||||
<td>
|
: '-'}}
|
||||||
{{i.name}} {{i.quantity > 1 ? '×' + i.quantity : ''}} @ {{i.amount |
|
</dd>
|
||||||
currency:'$'}}
|
</ng-container>
|
||||||
</td>
|
</dl>
|
||||||
<td>
|
|
||||||
{{(i.quantity * i.amount) | currency:'$'}} /{{i.interval | i18n}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<ng-container *ngIf="userOrg?.providerId != null">
|
|
||||||
<div class="col-sm">
|
|
||||||
<dl>
|
|
||||||
<dt>{{'provider' | i18n}}</dt>
|
|
||||||
<dd>{{'yourProviderIs' | i18n : userOrg.providerName}}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
<ng-container>
|
<div class="col-8" *ngIf="subscription">
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="changePlan()" *ngIf="showChangePlanButton">
|
<strong class="d-block mb-1">{{'details' | i18n}}</strong>
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let i of subscription.items">
|
||||||
|
<td>
|
||||||
|
{{i.name}} {{i.quantity > 1 ? '×' + i.quantity : ''}} @ {{i.amount | currency:'$'}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{(i.quantity * i.amount) | currency:'$'}} /{{i.interval | i18n}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="selfHosted">
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
|
||||||
|
{{'updateLicense' | i18n}}
|
||||||
|
</button>
|
||||||
|
<a href="https://vault.bitwarden.com" target="_blank" rel="noopener" class="btn btn-outline-secondary">
|
||||||
|
{{'manageSubscription' | i18n}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="card mt-3" *ngIf="showUpdateLicense">
|
||||||
|
<div class="card-body">
|
||||||
|
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}"
|
||||||
|
(click)="closeUpdateLicense(false)"><span aria-hidden="true">×</span></button>
|
||||||
|
<h3 class="card-body-header">{{'updateLicense' | i18n}}</h3>
|
||||||
|
<app-update-license [organizationId]="organizationId" (onUpdated)="closeUpdateLicense(true)"
|
||||||
|
(onCanceled)="closeUpdateLicense(false)"></app-update-license>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!selfHosted">
|
||||||
|
<div class="d-flex">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="changePlan()" *ngIf="!showChangePlan">
|
||||||
{{'changeBillingPlan' | i18n}}
|
{{'changeBillingPlan' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
<app-change-plan [organizationId]="organizationId" (onChanged)="closeChangePlan(true)"
|
<button type="button" class="btn btn-outline-secondary ml-1" (click)="downloadLicense()"
|
||||||
(onCanceled)="closeChangePlan(false)" *ngIf="showChangePlan"></app-change-plan>
|
*ngIf="canDownloadLicense" [disabled]="showDownloadLicense">
|
||||||
</ng-container>
|
{{'downloadLicense' | i18n}}
|
||||||
<h2 class="spaced-header">{{'manageSubscription' | i18n}}</h2>
|
</button>
|
||||||
<p class="mb-4">{{subscriptionDesc}}</p>
|
<button #cancelBtn type="button" class="btn btn-outline-danger btn-submit ml-auto" (click)="cancel()"
|
||||||
|
[appApiAction]="cancelPromise" [disabled]="cancelBtn.loading"
|
||||||
|
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
|
||||||
|
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
|
<span>{{'cancelSubscription' | i18n}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<app-change-plan [organizationId]="organizationId" (onChanged)="closeChangePlan(true)"
|
||||||
|
(onCanceled)="closeChangePlan(false)" *ngIf="showChangePlan"></app-change-plan>
|
||||||
|
<div class="mt-3" *ngIf="showDownloadLicense">
|
||||||
|
<app-download-license [organizationId]="organizationId" (onDownloaded)="closeDownloadLicense()"
|
||||||
|
(onCanceled)="closeDownloadLicense()"></app-download-license>
|
||||||
|
</div>
|
||||||
|
<h2 class="spaced-header">{{'userSeats' | i18n}}</h2>
|
||||||
|
<p>{{'subscriptionUserSeats' | i18n : sub.seats}}</p>
|
||||||
<ng-container *ngIf="subscription && canAdjustSeats && !subscription.cancelled && !subscriptionMarkedForCancel">
|
<ng-container *ngIf="subscription && canAdjustSeats && !subscription.cancelled && !subscriptionMarkedForCancel">
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<app-adjust-subscription [seatPrice]="seatPrice" [organizationId]="organizationId" [interval]="billingInterval"
|
<div class="d-flex" *ngIf="!showAdjustSeats">
|
||||||
[currentSeatCount]="seats" [maxAutoscaleSeats]="maxAutoscaleSeats" (onAdjusted)="subscriptionAdjusted()">
|
<button type="button" class="btn btn-outline-secondary" (click)="adjustSeats(true)">
|
||||||
</app-adjust-subscription>
|
{{'addSeats' | i18n}}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary ml-1" (click)="adjustSeats(false)">
|
||||||
|
{{'removeSeats' | i18n}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<app-adjust-seats [seatPrice]="seatPrice" [add]="adjustSeatsAdd" [organizationId]="organizationId"
|
||||||
|
[interval]="billingInterval" (onAdjusted)="closeSeats(true)" (onCanceled)="closeSeats(false)"
|
||||||
|
*ngIf="showAdjustSeats"></app-adjust-seats>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<h2 class="spaced-header">{{'storage' | i18n}}</h2>
|
<h2 class="spaced-header">{{'storage' | i18n}}</h2>
|
||||||
@@ -109,57 +152,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<h2 class="spaced-header">{{'additionalOptions' | i18n}}</h2>
|
<ng-container *ngIf="userOrg?.providerId != null">
|
||||||
<p class="mb-4">
|
<div class="secondary-header border-0 mb-0">
|
||||||
{{'additionalOptionsDesc' | i18n }}
|
<h1>{{'provider' | i18n}}</h1>
|
||||||
</p>
|
|
||||||
<div class="d-flex">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="downloadLicense()" *ngIf="canDownloadLicense"
|
|
||||||
[disabled]="showDownloadLicense">
|
|
||||||
{{'downloadLicense' | i18n}}
|
|
||||||
</button>
|
|
||||||
<button #cancelBtn type="button" class="btn btn-outline-danger btn-submit ml-1" (click)="cancel()"
|
|
||||||
[appApiAction]="cancelPromise" [disabled]="cancelBtn.loading"
|
|
||||||
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
|
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
|
||||||
<span>{{'cancelSubscription' | i18n}}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3" *ngIf="showDownloadLicense">
|
|
||||||
<app-download-license [organizationId]="organizationId" (onDownloaded)="closeDownloadLicense()"
|
|
||||||
(onCanceled)="closeDownloadLicense()"></app-download-license>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="selfHosted">
|
|
||||||
<dl>
|
|
||||||
<dt>{{'billingPlan' | i18n}}</dt>
|
|
||||||
<dd>{{sub.plan.name}}</dd>
|
|
||||||
<dt>{{'expiration' | i18n}}</dt>
|
|
||||||
<dd *ngIf="sub.expiration">
|
|
||||||
{{sub.expiration | date:'mediumDate'}}
|
|
||||||
<span *ngIf="isExpired" class="text-danger ml-2">
|
|
||||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
|
||||||
{{'licenseIsExpired' | i18n}}
|
|
||||||
</span>
|
|
||||||
</dd>
|
|
||||||
<dd *ngIf="!sub.expiration">{{'neverExpires' | i18n}}</dd>
|
|
||||||
</dl>
|
|
||||||
<div>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
|
|
||||||
{{'updateLicense' | i18n}}
|
|
||||||
</button>
|
|
||||||
<a href="https://vault.bitwarden.com" target="_blank" rel="noopener" class="btn btn-outline-secondary">
|
|
||||||
{{'manageSubscription' | i18n}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="card mt-3" *ngIf="showUpdateLicense">
|
|
||||||
<div class="card-body">
|
|
||||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}"
|
|
||||||
(click)="closeUpdateLicense(false)"><span aria-hidden="true">×</span></button>
|
|
||||||
<h3 class="card-body-header">{{'updateLicense' | i18n}}</h3>
|
|
||||||
<app-update-license [organizationId]="organizationId" (onUpdated)="closeUpdateLicense(true)"
|
|
||||||
(onCanceled)="closeUpdateLicense(false)"></app-update-license>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{{'yourProviderIs' | i18n : userOrg.providerName}}
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ 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 { 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 { UserService } from 'jslib-common/abstractions/user.service';
|
|
||||||
|
|
||||||
|
import { UserService } from 'jslib-common/abstractions';
|
||||||
import { PlanType } from 'jslib-common/enums/planType';
|
import { PlanType } from 'jslib-common/enums/planType';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -26,7 +26,6 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
|||||||
organizationId: string;
|
organizationId: string;
|
||||||
adjustSeatsAdd = true;
|
adjustSeatsAdd = true;
|
||||||
showAdjustSeats = false;
|
showAdjustSeats = false;
|
||||||
showAdjustSeatAutoscale = false;
|
|
||||||
adjustStorageAdd = true;
|
adjustStorageAdd = true;
|
||||||
showAdjustStorage = false;
|
showAdjustStorage = false;
|
||||||
showUpdateLicense = false;
|
showUpdateLicense = false;
|
||||||
@@ -105,7 +104,15 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async changePlan() {
|
async changePlan() {
|
||||||
this.showChangePlan = !this.showChangePlan;
|
if (this.subscription == null && this.sub.planType === PlanType.Free) {
|
||||||
|
this.showChangePlan = !this.showChangePlan;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const contactSupport = await this.platformUtilsService.showDialog(this.i18nService.t('changeBillingPlanDesc'),
|
||||||
|
this.i18nService.t('changeBillingPlan'), this.i18nService.t('contactSupport'), this.i18nService.t('close'));
|
||||||
|
if (contactSupport) {
|
||||||
|
this.platformUtilsService.launchUri('https://bitwarden.com/contact');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closeChangePlan(changed: boolean) {
|
closeChangePlan(changed: boolean) {
|
||||||
@@ -135,8 +142,16 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptionAdjusted() {
|
adjustSeats(add: boolean) {
|
||||||
this.load();
|
this.adjustSeatsAdd = add;
|
||||||
|
this.showAdjustSeats = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeSeats(load: boolean) {
|
||||||
|
this.showAdjustSeats = false;
|
||||||
|
if (load) {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustStorage(add: boolean) {
|
adjustStorage(add: boolean) {
|
||||||
@@ -190,14 +205,6 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
|||||||
return this.sub.plan.seatPrice;
|
return this.sub.plan.seatPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
get seats() {
|
|
||||||
return this.sub.seats;
|
|
||||||
}
|
|
||||||
|
|
||||||
get maxAutoscaleSeats() {
|
|
||||||
return this.sub.maxAutoscaleSeats;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canAdjustSeats() {
|
get canAdjustSeats() {
|
||||||
return this.sub.plan.hasAdditionalSeatsOption;
|
return this.sub.plan.hasAdditionalSeatsOption;
|
||||||
}
|
}
|
||||||
@@ -206,22 +213,4 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
|||||||
return (this.sub.planType !== PlanType.Free && this.subscription == null) ||
|
return (this.sub.planType !== PlanType.Free && this.subscription == null) ||
|
||||||
(this.subscription != null && !this.subscription.cancelled);
|
(this.subscription != null && !this.subscription.cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
get subscriptionDesc() {
|
|
||||||
if (this.sub.planType === PlanType.Free) {
|
|
||||||
return this.i18nService.t('subscriptionFreePlan', this.sub.seats.toString());
|
|
||||||
} else if (this.sub.planType === PlanType.FamiliesAnnually || this.sub.planType === PlanType.FamiliesAnnually2019) {
|
|
||||||
return this.i18nService.t('subscriptionFamiliesPlan', this.sub.seats.toString());
|
|
||||||
} else if (this.sub.maxAutoscaleSeats === this.sub.seats && this.sub.seats != null) {
|
|
||||||
return this.i18nService.t('subscriptionMaxReached', this.sub.seats.toString());
|
|
||||||
} else if (this.sub.maxAutoscaleSeats == null) {
|
|
||||||
return this.i18nService.t('subscriptionUserSeatsUnlimitedAutoscale');
|
|
||||||
} else {
|
|
||||||
return this.i18nService.t('subscriptionUserSeatsLimitedAutoscale', this.sub.maxAutoscaleSeats.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get showChangePlanButton() {
|
|
||||||
return this.subscription == null && this.sub.planType === PlanType.Free && !this.showChangePlan;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ 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 { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
|
||||||
|
|
||||||
import { ExportComponent as BaseExportComponent } from '../../tools/export.component';
|
import { ExportComponent as BaseExportComponent } from '../../tools/export.component';
|
||||||
|
|
||||||
|
import { EventType } from 'jslib-common/enums/eventType';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-org-export',
|
selector: 'app-org-export',
|
||||||
templateUrl: '../../tools/export.component.html',
|
templateUrl: '../../tools/export.component.html',
|
||||||
@@ -17,21 +18,16 @@ import { ExportComponent as BaseExportComponent } from '../../tools/export.compo
|
|||||||
export class ExportComponent extends BaseExportComponent {
|
export class ExportComponent extends BaseExportComponent {
|
||||||
constructor(cryptoService: CryptoService, i18nService: I18nService,
|
constructor(cryptoService: CryptoService, i18nService: I18nService,
|
||||||
platformUtilsService: PlatformUtilsService, exportService: ExportService,
|
platformUtilsService: PlatformUtilsService, exportService: ExportService,
|
||||||
eventService: EventService, private route: ActivatedRoute, policyService: PolicyService) {
|
eventService: EventService, private route: ActivatedRoute) {
|
||||||
super(cryptoService, i18nService, platformUtilsService, exportService, eventService, policyService);
|
super(cryptoService, i18nService, platformUtilsService, exportService, eventService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
ngOnInit() {
|
||||||
await super.ngOnInit();
|
|
||||||
this.route.parent.parent.params.subscribe(async params => {
|
this.route.parent.parent.params.subscribe(async params => {
|
||||||
this.organizationId = params.organizationId;
|
this.organizationId = params.organizationId;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkExportDisabled() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getExportData() {
|
getExportData() {
|
||||||
return this.exportService.getOrganizationExport(this.organizationId, this.format);
|
return this.exportService.getOrganizationExport(this.organizationId, this.format);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from 'jslib-common/abstractions/cipher.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 { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -25,8 +24,8 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
|
|||||||
|
|
||||||
constructor(cipherService: CipherService, auditService: AuditService,
|
constructor(cipherService: CipherService, auditService: AuditService,
|
||||||
modalService: ModalService, messagingService: MessagingService,
|
modalService: ModalService, messagingService: MessagingService,
|
||||||
userService: UserService, passwordRepromptService: PasswordRepromptService, private route: ActivatedRoute) {
|
userService: UserService, private route: ActivatedRoute) {
|
||||||
super(cipherService, auditService, modalService, messagingService, userService, passwordRepromptService);
|
super(cipherService, auditService, modalService, messagingService, userService);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
|
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from 'jslib-common/abstractions/cipher.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 { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -20,9 +19,9 @@ import { CipherView } from 'jslib-common/models/view/cipherView';
|
|||||||
})
|
})
|
||||||
export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent {
|
export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent {
|
||||||
constructor(cipherService: CipherService, modalService: ModalService,
|
constructor(cipherService: CipherService, modalService: ModalService,
|
||||||
messagingService: MessagingService, userService: UserService, passwordRepromptService: PasswordRepromptService,
|
messagingService: MessagingService, userService: UserService,
|
||||||
private route: ActivatedRoute) {
|
private route: ActivatedRoute) {
|
||||||
super(cipherService, modalService, messagingService, userService, passwordRepromptService);
|
super(cipherService, modalService, messagingService, userService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
|
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from 'jslib-common/abstractions/cipher.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 { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -24,9 +23,9 @@ export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportCom
|
|||||||
manageableCiphers: Cipher[];
|
manageableCiphers: Cipher[];
|
||||||
|
|
||||||
constructor(cipherService: CipherService, modalService: ModalService,
|
constructor(cipherService: CipherService, modalService: ModalService,
|
||||||
messagingService: MessagingService, userService: UserService, passwordRepromptService: PasswordRepromptService,
|
messagingService: MessagingService, userService: UserService,
|
||||||
private route: ActivatedRoute) {
|
private route: ActivatedRoute) {
|
||||||
super(cipherService, modalService, messagingService, userService, passwordRepromptService);
|
super(cipherService, modalService, messagingService, userService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
|
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from 'jslib-common/abstractions/cipher.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 { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -20,9 +19,9 @@ import { CipherView } from 'jslib-common/models/view/cipherView';
|
|||||||
})
|
})
|
||||||
export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent {
|
export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent {
|
||||||
constructor(cipherService: CipherService, modalService: ModalService,
|
constructor(cipherService: CipherService, modalService: ModalService,
|
||||||
messagingService: MessagingService, userService: UserService, passwordRepromptService: PasswordRepromptService,
|
messagingService: MessagingService, userService: UserService,
|
||||||
private route: ActivatedRoute) {
|
private route: ActivatedRoute) {
|
||||||
super(cipherService, modalService, messagingService, userService, passwordRepromptService);
|
super(cipherService, modalService, messagingService, userService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from 'jslib-common/abstractions/cipher.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 { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -26,9 +25,8 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone
|
|||||||
|
|
||||||
constructor(cipherService: CipherService, passwordGenerationService: PasswordGenerationService,
|
constructor(cipherService: CipherService, passwordGenerationService: PasswordGenerationService,
|
||||||
modalService: ModalService, messagingService: MessagingService,
|
modalService: ModalService, messagingService: MessagingService,
|
||||||
userService: UserService, passwordRepromptService: PasswordRepromptService, private route: ActivatedRoute) {
|
userService: UserService, private route: ActivatedRoute) {
|
||||||
super(cipherService, passwordGenerationService, modalService, messagingService, userService,
|
super(cipherService, passwordGenerationService, modalService, messagingService, userService);
|
||||||
passwordRepromptService);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||||||
protected allowOwnershipAssignment() {
|
protected allowOwnershipAssignment() {
|
||||||
if (this.ownershipOptions != null && (this.ownershipOptions.length > 1 || !this.allowPersonal)) {
|
if (this.ownershipOptions != null && (this.ownershipOptions.length > 1 || !this.allowPersonal)) {
|
||||||
if (this.organization != null) {
|
if (this.organization != null) {
|
||||||
return this.cloneMode && this.organization.canEditAnyCollection;
|
return this.cloneMode && this.organization.canManageAllCollections;
|
||||||
} else {
|
} else {
|
||||||
return !this.editMode || this.cloneMode;
|
return !this.editMode || this.cloneMode;
|
||||||
}
|
}
|
||||||
@@ -55,14 +55,14 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected loadCollections() {
|
protected loadCollections() {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
return super.loadCollections();
|
return super.loadCollections();
|
||||||
}
|
}
|
||||||
return Promise.resolve(this.collections);
|
return Promise.resolve(this.collections);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadCipher() {
|
protected async loadCipher() {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
return await super.loadCipher();
|
return await super.loadCipher();
|
||||||
}
|
}
|
||||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||||
@@ -72,14 +72,14 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected encryptCipher() {
|
protected encryptCipher() {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
return super.encryptCipher();
|
return super.encryptCipher();
|
||||||
}
|
}
|
||||||
return this.cipherService.encrypt(this.cipher, null, this.originalCipher);
|
return this.cipherService.encrypt(this.cipher, null, this.originalCipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async saveCipher(cipher: Cipher) {
|
protected async saveCipher(cipher: Cipher) {
|
||||||
if (!this.organization.canEditAnyCollection || cipher.organizationId == null) {
|
if (!this.organization.canManageAllCollections || cipher.organizationId == null) {
|
||||||
return super.saveCipher(cipher);
|
return super.saveCipher(cipher);
|
||||||
}
|
}
|
||||||
if (this.editMode && !this.cloneMode) {
|
if (this.editMode && !this.cloneMode) {
|
||||||
@@ -92,7 +92,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async deleteCipher() {
|
protected async deleteCipher() {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
return super.deleteCipher();
|
return super.deleteCipher();
|
||||||
}
|
}
|
||||||
return this.cipher.isDeleted ? this.apiService.deleteCipherAdmin(this.cipherId)
|
return this.cipher.isDeleted ? this.apiService.deleteCipherAdmin(this.cipherId)
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async reupload(attachment: AttachmentView) {
|
protected async reupload(attachment: AttachmentView) {
|
||||||
if (this.organization.canEditAnyCollection && this.showFixOldAttachments(attachment)) {
|
if (this.organization.canManageAllCollections && this.showFixOldAttachments(attachment)) {
|
||||||
await super.reuploadCipherAttachment(attachment, true);
|
await super.reuploadCipherAttachment(attachment, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadCipher() {
|
protected async loadCipher() {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
return await super.loadCipher();
|
return await super.loadCipher();
|
||||||
}
|
}
|
||||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||||
@@ -44,17 +44,17 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected saveCipherAttachment(file: File) {
|
protected saveCipherAttachment(file: File) {
|
||||||
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, this.organization.canEditAnyCollection);
|
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, this.organization.canManageAllCollections);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipherAttachment(attachmentId: string) {
|
protected deleteCipherAttachment(attachmentId: string) {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
return super.deleteCipherAttachment(attachmentId);
|
return super.deleteCipherAttachment(attachmentId);
|
||||||
}
|
}
|
||||||
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected showFixOldAttachments(attachment: AttachmentView) {
|
protected showFixOldAttachments(attachment: AttachmentView) {
|
||||||
return attachment.key == null && this.organization.canEditAnyCollection;
|
return attachment.key == null && this.organization.canManageAllCollections;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class CiphersComponent extends BaseCiphersComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load(filter: (cipher: CipherView) => boolean = null) {
|
async load(filter: (cipher: CipherView) => boolean = null) {
|
||||||
if (this.organization.canEditAnyCollection) {
|
if (this.organization.canManageAllCollections) {
|
||||||
this.accessEvents = this.organization.useEvents;
|
this.accessEvents = this.organization.useEvents;
|
||||||
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||||
} else {
|
} else {
|
||||||
@@ -54,7 +54,7 @@ export class CiphersComponent extends BaseCiphersComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async applyFilter(filter: (cipher: CipherView) => boolean = null) {
|
async applyFilter(filter: (cipher: CipherView) => boolean = null) {
|
||||||
if (this.organization.canViewAllCollections) {
|
if (this.organization.canManageAllCollections) {
|
||||||
await super.applyFilter(filter);
|
await super.applyFilter(filter);
|
||||||
} else {
|
} else {
|
||||||
const f = (c: CipherView) => c.organizationId === this.organization.id && (filter == null || filter(c));
|
const f = (c: CipherView) => c.organizationId === this.organization.id && (filter == null || filter(c));
|
||||||
@@ -70,13 +70,13 @@ export class CiphersComponent extends BaseCiphersComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipher(id: string) {
|
protected deleteCipher(id: string) {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
return super.deleteCipher(id, this.deleted);
|
return super.deleteCipher(id, this.deleted);
|
||||||
}
|
}
|
||||||
return this.deleted ? this.apiService.deleteCipherAdmin(id) : this.apiService.putDeleteCipherAdmin(id);
|
return this.deleted ? this.apiService.deleteCipherAdmin(id) : this.apiService.putDeleteCipherAdmin(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected showFixOldAttachments(c: CipherView) {
|
protected showFixOldAttachments(c: CipherView) {
|
||||||
return this.organization.canEditAnyCollection && c.hasOldAttachments;
|
return this.organization.canManageAllCollections && c.hasOldAttachments;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async loadCipher() {
|
protected async loadCipher() {
|
||||||
if (!this.organization.canViewAllCollections) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
return await super.loadCipher();
|
return await super.loadCipher();
|
||||||
}
|
}
|
||||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||||
@@ -36,21 +36,21 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected loadCipherCollections() {
|
protected loadCipherCollections() {
|
||||||
if (!this.organization.canViewAllCollections) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
return super.loadCipherCollections();
|
return super.loadCipherCollections();
|
||||||
}
|
}
|
||||||
return this.collectionIds;
|
return this.collectionIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadCollections() {
|
protected loadCollections() {
|
||||||
if (!this.organization.canViewAllCollections) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
return super.loadCollections();
|
return super.loadCollections();
|
||||||
}
|
}
|
||||||
return Promise.resolve(this.collections);
|
return Promise.resolve(this.collections);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected saveCollections() {
|
protected saveCollections() {
|
||||||
if (this.organization.canEditAnyCollection) {
|
if (this.organization.canManageAllCollections) {
|
||||||
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
|
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
|
||||||
return this.apiService.putCipherCollectionsAdmin(this.cipherId, request);
|
return this.apiService.putCipherCollectionsAdmin(this.cipherId, request);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class GroupingsComponent extends BaseGroupingsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadCollections() {
|
async loadCollections() {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
await super.loadCollections(this.organization.id);
|
await super.loadCollections(this.organization.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
||||||
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
|
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
|
||||||
if (!this.organization.canViewAllCollections) {
|
if (!this.organization.canManageAllCollections) {
|
||||||
await this.syncService.fullSync(false);
|
await this.syncService.fullSync(false);
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
@@ -223,7 +223,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
async editCipherCollections(cipher: CipherView) {
|
async editCipherCollections(cipher: CipherView) {
|
||||||
const [modal] = await this.modalService.openViewRef(CollectionsComponent, this.collectionsModalRef, comp => {
|
const [modal] = await this.modalService.openViewRef(CollectionsComponent, this.collectionsModalRef, comp => {
|
||||||
if (this.organization.canEditAnyCollection) {
|
if (this.organization.canManageAllCollections) {
|
||||||
comp.collectionIds = cipher.collectionIds;
|
comp.collectionIds = cipher.collectionIds;
|
||||||
comp.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
comp.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
||||||
}
|
}
|
||||||
@@ -240,7 +240,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
const component = await this.editCipher(null);
|
const component = await this.editCipher(null);
|
||||||
component.organizationId = this.organization.id;
|
component.organizationId = this.organization.id;
|
||||||
component.type = this.type;
|
component.type = this.type;
|
||||||
if (this.organization.canEditAnyCollection) {
|
if (this.organization.canManageAllCollections) {
|
||||||
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
||||||
}
|
}
|
||||||
if (this.collectionId != null) {
|
if (this.collectionId != null) {
|
||||||
@@ -273,7 +273,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
const component = await this.editCipher(cipher);
|
const component = await this.editCipher(cipher);
|
||||||
component.cloneMode = true;
|
component.cloneMode = true;
|
||||||
component.organizationId = this.organization.id;
|
component.organizationId = this.organization.id;
|
||||||
if (this.organization.canEditAnyCollection) {
|
if (this.organization.canManageAllCollections) {
|
||||||
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
||||||
}
|
}
|
||||||
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
|
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
|
||||||
|
|||||||
@@ -350,11 +350,8 @@ const routes: Routes = [
|
|||||||
canActivate: [OrganizationTypeGuardService],
|
canActivate: [OrganizationTypeGuardService],
|
||||||
data: {
|
data: {
|
||||||
permissions: [
|
permissions: [
|
||||||
Permissions.CreateNewCollections,
|
Permissions.ManageAssignedCollections,
|
||||||
Permissions.EditAnyCollection,
|
Permissions.ManageAllCollections,
|
||||||
Permissions.DeleteAnyCollection,
|
|
||||||
Permissions.EditAssignedCollections,
|
|
||||||
Permissions.DeleteAssignedCollections,
|
|
||||||
Permissions.AccessEventLogs,
|
Permissions.AccessEventLogs,
|
||||||
Permissions.ManageGroups,
|
Permissions.ManageGroups,
|
||||||
Permissions.ManageUsers,
|
Permissions.ManageUsers,
|
||||||
@@ -373,13 +370,7 @@ const routes: Routes = [
|
|||||||
canActivate: [OrganizationTypeGuardService],
|
canActivate: [OrganizationTypeGuardService],
|
||||||
data: {
|
data: {
|
||||||
titleId: 'collections',
|
titleId: 'collections',
|
||||||
permissions: [
|
permissions: [Permissions.ManageAssignedCollections, Permissions.ManageAllCollections],
|
||||||
Permissions.CreateNewCollections,
|
|
||||||
Permissions.EditAnyCollection,
|
|
||||||
Permissions.DeleteAnyCollection,
|
|
||||||
Permissions.EditAssignedCollections,
|
|
||||||
Permissions.DeleteAssignedCollections,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -443,6 +434,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{ path: '**', redirectTo: '' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import { RouterModule } from '@angular/router';
|
|||||||
import { ToasterModule } from 'angular2-toaster';
|
import { ToasterModule } from 'angular2-toaster';
|
||||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||||
|
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
import { AvatarComponent } from './components/avatar.component';
|
import { AvatarComponent } from './components/avatar.component';
|
||||||
import { NestedCheckboxComponent } from './components/nested-checkbox.component';
|
|
||||||
import { PasswordRepromptComponent } from './components/password-reprompt.component';
|
import { PasswordRepromptComponent } from './components/password-reprompt.component';
|
||||||
import { PasswordStrengthComponent } from './components/password-strength.component';
|
import { PasswordStrengthComponent } from './components/password-strength.component';
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ import { UserConfirmComponent as OrgUserConfirmComponent } from './organizations
|
|||||||
import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/manage/user-groups.component';
|
import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/manage/user-groups.component';
|
||||||
|
|
||||||
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
|
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
|
||||||
import { AdjustSubscription } from './organizations/settings/adjust-subscription.component';
|
import { AdjustSeatsComponent } from './organizations/settings/adjust-seats.component';
|
||||||
import { ChangePlanComponent } from './organizations/settings/change-plan.component';
|
import { ChangePlanComponent } from './organizations/settings/change-plan.component';
|
||||||
import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
|
import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
|
||||||
import { DownloadLicenseComponent } from './organizations/settings/download-license.component';
|
import { DownloadLicenseComponent } from './organizations/settings/download-license.component';
|
||||||
@@ -143,7 +144,6 @@ import { UpdateKeyComponent } from './settings/update-key.component';
|
|||||||
import { UpdateLicenseComponent } from './settings/update-license.component';
|
import { UpdateLicenseComponent } from './settings/update-license.component';
|
||||||
import { UserBillingComponent } from './settings/user-billing.component';
|
import { UserBillingComponent } from './settings/user-billing.component';
|
||||||
import { UserSubscriptionComponent } from './settings/user-subscription.component';
|
import { UserSubscriptionComponent } from './settings/user-subscription.component';
|
||||||
import { VaultTimeoutInputComponent } from './settings/vault-timeout-input.component';
|
|
||||||
import { VerifyEmailComponent } from './settings/verify-email.component';
|
import { VerifyEmailComponent } from './settings/verify-email.component';
|
||||||
|
|
||||||
import { BreachReportComponent } from './tools/breach-report.component';
|
import { BreachReportComponent } from './tools/breach-report.component';
|
||||||
@@ -158,7 +158,6 @@ import { ToolsComponent } from './tools/tools.component';
|
|||||||
import { UnsecuredWebsitesReportComponent } from './tools/unsecured-websites-report.component';
|
import { UnsecuredWebsitesReportComponent } from './tools/unsecured-websites-report.component';
|
||||||
import { WeakPasswordsReportComponent } from './tools/weak-passwords-report.component';
|
import { WeakPasswordsReportComponent } from './tools/weak-passwords-report.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 { BulkActionsComponent } from './vault/bulk-actions.component';
|
import { BulkActionsComponent } from './vault/bulk-actions.component';
|
||||||
@@ -304,10 +303,11 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
|||||||
AddCreditComponent,
|
AddCreditComponent,
|
||||||
AddEditComponent,
|
AddEditComponent,
|
||||||
AdjustPaymentComponent,
|
AdjustPaymentComponent,
|
||||||
AdjustSubscription,
|
AdjustSeatsComponent,
|
||||||
AdjustStorageComponent,
|
AdjustStorageComponent,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
ApiKeyComponent,
|
ApiKeyComponent,
|
||||||
|
AppComponent,
|
||||||
AttachmentsComponent,
|
AttachmentsComponent,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
AvatarComponent,
|
AvatarComponent,
|
||||||
@@ -357,7 +357,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
|||||||
LockComponent,
|
LockComponent,
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
NavbarComponent,
|
NavbarComponent,
|
||||||
NestedCheckboxComponent,
|
|
||||||
OptionsComponent,
|
OptionsComponent,
|
||||||
OrgAccountComponent,
|
OrgAccountComponent,
|
||||||
OrgAddEditComponent,
|
OrgAddEditComponent,
|
||||||
@@ -458,8 +457,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
|||||||
DisableSendPolicyComponent,
|
DisableSendPolicyComponent,
|
||||||
SendOptionsPolicyComponent,
|
SendOptionsPolicyComponent,
|
||||||
ResetPasswordPolicyComponent,
|
ResetPasswordPolicyComponent,
|
||||||
VaultTimeoutInputComponent,
|
|
||||||
AddEditCustomFieldsComponent,
|
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<div class="container page-content">
|
<div class="container page-content">
|
||||||
<app-callout type="warning" title="{{'sendDisabled' | i18n}}" *ngIf="disableSend">
|
<div class="row card border-warning mb-4" *ngIf="disableSend">
|
||||||
<span>{{'sendDisabledWarning' | i18n}}</span>
|
<div class="card-header bg-warning text-white">
|
||||||
</app-callout>
|
<i class="fa fa-warning fa-fw" aria-hidden="true"></i> {{'sendDisabled' | i18n}}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<span>{{'sendDisabledWarning' | i18n}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3 groupings">
|
<div class="col-3 groupings">
|
||||||
<div class="card vault-filters">
|
<div class="card vault-filters">
|
||||||
|
|||||||
@@ -18,20 +18,17 @@ export class OrganizationTypeGuardService implements CanActivate {
|
|||||||
const permissions = route.data == null ? null : route.data.permissions as Permissions[];
|
const permissions = route.data == null ? null : route.data.permissions as Permissions[];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
(permissions.indexOf(Permissions.AccessBusinessPortal) !== -1 && org.canAccessBusinessPortal) ||
|
||||||
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && org.canAccessEventLogs) ||
|
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && org.canAccessEventLogs) ||
|
||||||
(permissions.indexOf(Permissions.AccessImportExport) !== -1 && org.canAccessImportExport) ||
|
(permissions.indexOf(Permissions.AccessImportExport) !== -1 && org.canAccessImportExport) ||
|
||||||
(permissions.indexOf(Permissions.AccessReports) !== -1 && org.canAccessReports) ||
|
(permissions.indexOf(Permissions.AccessReports) !== -1 && org.canAccessReports) ||
|
||||||
(permissions.indexOf(Permissions.CreateNewCollections) !== -1 && org.canCreateNewCollections) ||
|
(permissions.indexOf(Permissions.ManageAllCollections) !== -1 && org.canManageAllCollections) ||
|
||||||
(permissions.indexOf(Permissions.EditAnyCollection) !== -1 && org.canEditAnyCollection) ||
|
(permissions.indexOf(Permissions.ManageAssignedCollections) !== -1 && org.canManageAssignedCollections) ||
|
||||||
(permissions.indexOf(Permissions.DeleteAnyCollection) !== -1 && org.canDeleteAnyCollection) ||
|
|
||||||
(permissions.indexOf(Permissions.EditAssignedCollections) !== -1 && org.canEditAssignedCollections) ||
|
|
||||||
(permissions.indexOf(Permissions.DeleteAssignedCollections) !== -1 && org.canDeleteAssignedCollections) ||
|
|
||||||
(permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) ||
|
(permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) ||
|
||||||
(permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) ||
|
(permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) ||
|
||||||
(permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) ||
|
(permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) ||
|
||||||
(permissions.indexOf(Permissions.ManageUsers) !== -1 && org.canManageUsers) ||
|
(permissions.indexOf(Permissions.ManageUsers) !== -1 && org.canManageUsers) ||
|
||||||
(permissions.indexOf(Permissions.ManageUsersPassword) !== -1 && org.canManageUsersPassword) ||
|
(permissions.indexOf(Permissions.ManageUsersPassword) !== -1 && org.canManageUsersPassword)
|
||||||
(permissions.indexOf(Permissions.ManageSso) !== -1 && org.canManageSso)
|
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,14 +91,12 @@ import { UserService as UserServiceAbstraction } from 'jslib-common/abstractions
|
|||||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||||
import { ModalService } from './modal.service';
|
import { ModalService } from './modal.service';
|
||||||
|
|
||||||
import { ThemeType } from 'jslib-common/enums/themeType';
|
|
||||||
|
|
||||||
const i18nService = new I18nService(window.navigator.language, 'locales');
|
const i18nService = new I18nService(window.navigator.language, 'locales');
|
||||||
const stateService = new StateService();
|
const stateService = new StateService();
|
||||||
const broadcasterService = new BroadcasterService();
|
const broadcasterService = new BroadcasterService();
|
||||||
const messagingService = new BroadcasterMessagingService(broadcasterService);
|
const messagingService = new BroadcasterMessagingService(broadcasterService);
|
||||||
const consoleLogService = new ConsoleLogService(false);
|
const consoleLogService = new ConsoleLogService(false);
|
||||||
const platformUtilsService = new WebPlatformUtilsService(i18nService, messagingService, consoleLogService, () => storageService);
|
const platformUtilsService = new WebPlatformUtilsService(i18nService, messagingService, consoleLogService);
|
||||||
const storageService: StorageServiceAbstraction = new HtmlStorageService(platformUtilsService);
|
const storageService: StorageServiceAbstraction = new HtmlStorageService(platformUtilsService);
|
||||||
const secureStorageService: StorageServiceAbstraction = new MemoryStorageService();
|
const secureStorageService: StorageServiceAbstraction = new MemoryStorageService();
|
||||||
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new WebCryptoFunctionService(window,
|
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new WebCryptoFunctionService(window,
|
||||||
@@ -121,12 +119,12 @@ const folderService = new FolderService(cryptoService, userService, apiService,
|
|||||||
i18nService, cipherService);
|
i18nService, cipherService);
|
||||||
const collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
|
const collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
|
||||||
searchService = new SearchService(cipherService, consoleLogService, i18nService);
|
searchService = new SearchService(cipherService, consoleLogService, i18nService);
|
||||||
const policyService = new PolicyService(userService, storageService, apiService);
|
const policyService = new PolicyService(userService, storageService);
|
||||||
const sendService = new SendService(cryptoService, userService, apiService, fileUploadService, storageService,
|
const sendService = new SendService(cryptoService, userService, apiService, fileUploadService, storageService,
|
||||||
i18nService, cryptoFunctionService);
|
i18nService, cryptoFunctionService);
|
||||||
const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
|
const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
|
||||||
cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, tokenService,
|
cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, tokenService,
|
||||||
policyService, null, async () => messagingService.send('logout', { expired: false }));
|
null, async () => messagingService.send('logout', { expired: false }));
|
||||||
const syncService = new SyncService(userService, apiService, settingsService,
|
const syncService = new SyncService(userService, apiService, settingsService,
|
||||||
folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService,
|
folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService,
|
||||||
sendService, async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
sendService, async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
||||||
@@ -163,16 +161,11 @@ export function initFactory(): Function {
|
|||||||
authService.init();
|
authService.init();
|
||||||
const htmlEl = window.document.documentElement;
|
const htmlEl = window.document.documentElement;
|
||||||
htmlEl.classList.add('locale_' + i18nService.translationLocale);
|
htmlEl.classList.add('locale_' + i18nService.translationLocale);
|
||||||
|
let theme = await storageService.get<string>(ConstantsService.themeKey);
|
||||||
// Initial theme is set in index.html which must be updated if there are any changes to theming logic
|
if (theme == null) {
|
||||||
platformUtilsService.onDefaultSystemThemeChange(async sysTheme => {
|
theme = 'light';
|
||||||
const bwTheme = await storageService.get<ThemeType>(ConstantsService.themeKey);
|
}
|
||||||
if (bwTheme === ThemeType.System) {
|
htmlEl.classList.add('theme_' + theme);
|
||||||
htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark);
|
|
||||||
htmlEl.classList.add('theme_' + sysTheme);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stateService.save(ConstantsService.disableFaviconKey,
|
stateService.save(ConstantsService.disableFaviconKey,
|
||||||
await storageService.get<boolean>(ConstantsService.disableFaviconKey));
|
await storageService.get<boolean>(ConstantsService.disableFaviconKey));
|
||||||
stateService.save('enableGravatars', await storageService.get<boolean>('enableGravatars'));
|
stateService.save('enableGravatars', await storageService.get<boolean>('enableGravatars'));
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||||
import { PayPalConfig } from 'jslib-common/abstractions/environment.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
@@ -17,6 +16,8 @@ import { PaymentMethodType } from 'jslib-common/enums/paymentMethodType';
|
|||||||
|
|
||||||
import { BitPayInvoiceRequest } from 'jslib-common/models/request/bitPayInvoiceRequest';
|
import { BitPayInvoiceRequest } from 'jslib-common/models/request/bitPayInvoiceRequest';
|
||||||
|
|
||||||
|
import { WebConstants } from '../../services/webConstants';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-credit',
|
selector: 'app-add-credit',
|
||||||
templateUrl: 'add-credit.component.html',
|
templateUrl: 'add-credit.component.html',
|
||||||
@@ -32,8 +33,8 @@ export class AddCreditComponent implements OnInit {
|
|||||||
@ViewChild('ppButtonForm', { read: ElementRef, static: true }) ppButtonFormRef: ElementRef;
|
@ViewChild('ppButtonForm', { read: ElementRef, static: true }) ppButtonFormRef: ElementRef;
|
||||||
|
|
||||||
paymentMethodType = PaymentMethodType;
|
paymentMethodType = PaymentMethodType;
|
||||||
ppButtonFormAction: string;
|
ppButtonFormAction = WebConstants.paypal.buttonActionProduction;
|
||||||
ppButtonBusinessId: string;
|
ppButtonBusinessId = WebConstants.paypal.businessIdProduction;
|
||||||
ppButtonCustomField: string;
|
ppButtonCustomField: string;
|
||||||
ppLoading = false;
|
ppLoading = false;
|
||||||
subject: string;
|
subject: string;
|
||||||
@@ -46,9 +47,10 @@ export class AddCreditComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(private userService: UserService, private apiService: ApiService,
|
constructor(private userService: UserService, private apiService: ApiService,
|
||||||
private platformUtilsService: PlatformUtilsService) {
|
private platformUtilsService: PlatformUtilsService) {
|
||||||
const payPalConfig = process.env.PAYPAL_CONFIG as PayPalConfig;
|
if (process.env.ENV !== 'cloud' || platformUtilsService.isDev()) {
|
||||||
this.ppButtonFormAction = payPalConfig.buttonAction;
|
this.ppButtonFormAction = WebConstants.paypal.buttonActionSandbox;
|
||||||
this.ppButtonBusinessId = payPalConfig.businessId;
|
this.ppButtonBusinessId = WebConstants.paypal.businessIdSandbox;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||||
<app-callout type="warning" *ngIf="showTwoFactorEmailWarning">
|
|
||||||
{{'changeEmailTwoFactorWarning' | i18n}}
|
|
||||||
</app-callout>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { ToasterService } from 'angular2-toaster';
|
import { ToasterService } from 'angular2-toaster';
|
||||||
@@ -14,18 +13,15 @@ import { UserService } from 'jslib-common/abstractions/user.service';
|
|||||||
import { EmailRequest } from 'jslib-common/models/request/emailRequest';
|
import { EmailRequest } from 'jslib-common/models/request/emailRequest';
|
||||||
import { EmailTokenRequest } from 'jslib-common/models/request/emailTokenRequest';
|
import { EmailTokenRequest } from 'jslib-common/models/request/emailTokenRequest';
|
||||||
|
|
||||||
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-change-email',
|
selector: 'app-change-email',
|
||||||
templateUrl: 'change-email.component.html',
|
templateUrl: 'change-email.component.html',
|
||||||
})
|
})
|
||||||
export class ChangeEmailComponent implements OnInit {
|
export class ChangeEmailComponent {
|
||||||
masterPassword: string;
|
masterPassword: string;
|
||||||
newEmail: string;
|
newEmail: string;
|
||||||
token: string;
|
token: string;
|
||||||
tokenSent = false;
|
tokenSent = false;
|
||||||
showTwoFactorEmailWarning = false;
|
|
||||||
|
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
|
|
||||||
@@ -33,12 +29,6 @@ export class ChangeEmailComponent implements OnInit {
|
|||||||
private toasterService: ToasterService, private cryptoService: CryptoService,
|
private toasterService: ToasterService, private cryptoService: CryptoService,
|
||||||
private messagingService: MessagingService, private userService: UserService) { }
|
private messagingService: MessagingService, private userService: UserService) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
const twoFactorProviders = await this.apiService.getTwoFactorProviders();
|
|
||||||
this.showTwoFactorEmailWarning = twoFactorProviders.data.some(p => p.type === TwoFactorProviderType.Email &&
|
|
||||||
p.enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
const hasEncKey = await this.cryptoService.hasEncKey();
|
const hasEncKey = await this.cryptoService.hasEncKey();
|
||||||
if (!hasEncKey) {
|
if (!hasEncKey) {
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
|||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
|
|
||||||
import { CipherData } from 'jslib-common/models/data/cipherData';
|
import { CipherData } from 'jslib-common/models/data';
|
||||||
import { Cipher } from 'jslib-common/models/domain/cipher';
|
import { Cipher, SymmetricCryptoKey } from 'jslib-common/models/domain';
|
||||||
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
|
|
||||||
import { EmergencyAccessViewResponse } from 'jslib-common/models/response/emergencyAccessResponse';
|
import { EmergencyAccessViewResponse } from 'jslib-common/models/response/emergencyAccessResponse';
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,13 @@
|
|||||||
<form (ngSubmit)="submit()" ngNativeValidate>
|
<form (ngSubmit)="submit()" ngNativeValidate>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<app-vault-timeout-input [vaultTimeouts]="vaultTimeouts" [formControl]="vaultTimeout" ngDefaultControl>
|
<div class="form-group">
|
||||||
</app-vault-timeout-input>
|
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label>
|
||||||
|
<select id="vaultTimeout" name="VaultTimeout" [(ngModel)]="vaultTimeout" class="form-control">
|
||||||
|
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
|
||||||
|
</select>
|
||||||
|
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -72,7 +77,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<small class="form-text text-muted">{{'enableGravatarsDesc' | i18n}}</small>
|
<small class="form-text text-muted">{{'enableGravatarsDesc' | i18n}}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="enableFullWidth" name="enableFullWidth"
|
<input class="form-check-input" type="checkbox" id="enableFullWidth" name="enableFullWidth"
|
||||||
[(ngModel)]="enableFullWidth">
|
[(ngModel)]="enableFullWidth">
|
||||||
@@ -82,17 +87,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<small class="form-text text-muted">{{'enableFullWidthDesc' | i18n}}</small>
|
<small class="form-text text-muted">{{'enableFullWidthDesc' | i18n}}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="theme">{{'theme' | i18n}}</label>
|
|
||||||
<select id="theme" name="theme" [(ngModel)]="theme" class="form-control">
|
|
||||||
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{o.name}}</option>
|
|
||||||
</select>
|
|
||||||
<small class="form-text text-muted">{{'themeDesc' | i18n}}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
{{'save' | i18n}}
|
{{'save' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
OnInit,
|
OnInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { FormControl } from '@angular/forms';
|
|
||||||
|
|
||||||
import { ToasterService } from 'angular2-toaster';
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.serv
|
|||||||
|
|
||||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||||
|
|
||||||
import { ThemeType } from 'jslib-common/enums/themeType';
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from 'jslib-common/misc/utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -23,20 +21,16 @@ import { Utils } from 'jslib-common/misc/utils';
|
|||||||
templateUrl: 'options.component.html',
|
templateUrl: 'options.component.html',
|
||||||
})
|
})
|
||||||
export class OptionsComponent implements OnInit {
|
export class OptionsComponent implements OnInit {
|
||||||
|
vaultTimeout: number = null;
|
||||||
vaultTimeoutAction: string = 'lock';
|
vaultTimeoutAction: string = 'lock';
|
||||||
disableIcons: boolean;
|
disableIcons: boolean;
|
||||||
enableGravatars: boolean;
|
enableGravatars: boolean;
|
||||||
enableFullWidth: boolean;
|
enableFullWidth: boolean;
|
||||||
theme: string = null;
|
|
||||||
locale: string;
|
locale: string;
|
||||||
vaultTimeouts: { name: string; value: number; }[];
|
vaultTimeouts: any[];
|
||||||
localeOptions: any[];
|
localeOptions: any[];
|
||||||
themeOptions: any[];
|
|
||||||
|
|
||||||
vaultTimeout: FormControl = new FormControl(null);
|
|
||||||
|
|
||||||
private startingLocale: string;
|
private startingLocale: string;
|
||||||
private startingTheme: string;
|
|
||||||
|
|
||||||
constructor(private storageService: StorageService, private stateService: StateService,
|
constructor(private storageService: StorageService, private stateService: StateService,
|
||||||
private i18nService: I18nService, private toasterService: ToasterService,
|
private i18nService: I18nService, private toasterService: ToasterService,
|
||||||
@@ -66,44 +60,26 @@ export class OptionsComponent implements OnInit {
|
|||||||
localeOptions.sort(Utils.getSortFunction(i18nService, 'name'));
|
localeOptions.sort(Utils.getSortFunction(i18nService, 'name'));
|
||||||
localeOptions.splice(0, 0, { name: i18nService.t('default'), value: null });
|
localeOptions.splice(0, 0, { name: i18nService.t('default'), value: null });
|
||||||
this.localeOptions = localeOptions;
|
this.localeOptions = localeOptions;
|
||||||
this.themeOptions = [
|
|
||||||
{ name: i18nService.t('themeLight'), value: null },
|
|
||||||
{ name: i18nService.t('themeDark'), value: ThemeType.Dark },
|
|
||||||
{ name: i18nService.t('themeSystem'), value: ThemeType.System },
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.vaultTimeout.setValue(await this.vaultTimeoutService.getVaultTimeout());
|
this.vaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
||||||
this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
|
this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
|
||||||
this.disableIcons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
|
this.disableIcons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
|
||||||
this.enableGravatars = await this.storageService.get<boolean>('enableGravatars');
|
this.enableGravatars = await this.storageService.get<boolean>('enableGravatars');
|
||||||
this.enableFullWidth = await this.storageService.get<boolean>('enableFullWidth');
|
this.enableFullWidth = await this.storageService.get<boolean>('enableFullWidth');
|
||||||
this.locale = this.startingLocale = await this.storageService.get<string>(ConstantsService.localeKey);
|
this.locale = this.startingLocale = await this.storageService.get<string>(ConstantsService.localeKey);
|
||||||
this.theme = this.startingTheme = await this.storageService.get<ThemeType>(ConstantsService.themeKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
if (!this.vaultTimeout.valid) {
|
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout != null ? this.vaultTimeout : null,
|
||||||
this.toasterService.popAsync('error', null, this.i18nService.t('vaultTimeoutToLarge'));
|
this.vaultTimeoutAction);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction);
|
|
||||||
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableIcons);
|
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableIcons);
|
||||||
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableIcons);
|
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableIcons);
|
||||||
await this.storageService.save('enableGravatars', this.enableGravatars);
|
await this.storageService.save('enableGravatars', this.enableGravatars);
|
||||||
await this.stateService.save('enableGravatars', this.enableGravatars);
|
await this.stateService.save('enableGravatars', this.enableGravatars);
|
||||||
await this.storageService.save('enableFullWidth', this.enableFullWidth);
|
await this.storageService.save('enableFullWidth', this.enableFullWidth);
|
||||||
this.messagingService.send('setFullWidth');
|
this.messagingService.send('setFullWidth');
|
||||||
if (this.theme !== this.startingTheme) {
|
|
||||||
await this.storageService.save(ConstantsService.themeKey, this.theme);
|
|
||||||
this.startingTheme = this.theme;
|
|
||||||
const effectiveTheme = await this.platformUtilsService.getEffectiveTheme();
|
|
||||||
const htmlEl = window.document.documentElement;
|
|
||||||
htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark);
|
|
||||||
htmlEl.classList.add('theme_' + effectiveTheme);
|
|
||||||
}
|
|
||||||
await this.storageService.save(ConstantsService.localeKey, this.locale);
|
await this.storageService.save(ConstantsService.localeKey, this.locale);
|
||||||
if (this.locale !== this.startingLocale) {
|
if (this.locale !== this.startingLocale) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
|||||||
@@ -9,14 +9,26 @@ import { PaymentMethodType } from 'jslib-common/enums/paymentMethodType';
|
|||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||||
|
|
||||||
import { ThemeType } from 'jslib-common/enums/themeType';
|
import { WebConstants } from '../../services/webConstants';
|
||||||
|
|
||||||
import ThemeVariables from 'src/scss/export.module.scss';
|
const StripeElementStyle = {
|
||||||
|
base: {
|
||||||
|
color: '#333333',
|
||||||
|
fontFamily: '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' +
|
||||||
|
'"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontSmoothing: 'antialiased',
|
||||||
|
},
|
||||||
|
invalid: {
|
||||||
|
color: '#333333',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const lightInputColor = ThemeVariables.lightInputColor;
|
const StripeElementClasses = {
|
||||||
const lightInputPlaceholderColor = ThemeVariables.lightInputPlaceholderColor;
|
focus: 'is-focused',
|
||||||
const darkInputColor = ThemeVariables.darkInputColor;
|
empty: 'is-empty',
|
||||||
const darkInputPlaceholderColor = ThemeVariables.darkInputPlaceholderColor;
|
invalid: 'is-invalid',
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-payment',
|
selector: 'app-payment',
|
||||||
@@ -49,51 +61,28 @@ export class PaymentComponent implements OnInit {
|
|||||||
private stripeCardNumberElement: any = null;
|
private stripeCardNumberElement: any = null;
|
||||||
private stripeCardExpiryElement: any = null;
|
private stripeCardExpiryElement: any = null;
|
||||||
private stripeCardCvcElement: any = null;
|
private stripeCardCvcElement: any = null;
|
||||||
private StripeElementStyle: any;
|
|
||||||
private StripeElementClasses: any;
|
|
||||||
|
|
||||||
constructor(private platformUtilsService: PlatformUtilsService, private apiService: ApiService) {
|
constructor(private platformUtilsService: PlatformUtilsService, private apiService: ApiService) {
|
||||||
this.stripeScript = window.document.createElement('script');
|
this.stripeScript = window.document.createElement('script');
|
||||||
this.stripeScript.src = 'https://js.stripe.com/v3/';
|
this.stripeScript.src = 'https://js.stripe.com/v3/';
|
||||||
this.stripeScript.async = true;
|
this.stripeScript.async = true;
|
||||||
this.stripeScript.onload = () => {
|
this.stripeScript.onload = () => {
|
||||||
this.stripe = (window as any).Stripe(process.env.STRIPE_KEY);
|
this.stripe = (window as any).Stripe(process.env.ENV === 'cloud' && !platformUtilsService.isDev() ?
|
||||||
|
WebConstants.stripeLiveKey : WebConstants.stripeTestKey);
|
||||||
this.stripeElements = this.stripe.elements();
|
this.stripeElements = this.stripe.elements();
|
||||||
this.setStripeElement();
|
this.setStripeElement();
|
||||||
};
|
};
|
||||||
this.btScript = window.document.createElement('script');
|
this.btScript = window.document.createElement('script');
|
||||||
this.btScript.src = `scripts/dropin.js?cache=${process.env.CACHE_TAG}`;
|
this.btScript.src = `scripts/dropin.js?cache=${process.env.CACHE_TAG}`;
|
||||||
this.btScript.async = true;
|
this.btScript.async = true;
|
||||||
this.StripeElementStyle = {
|
|
||||||
base: {
|
|
||||||
color: null,
|
|
||||||
fontFamily: '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' +
|
|
||||||
'"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
|
|
||||||
fontSize: '14px',
|
|
||||||
fontSmoothing: 'antialiased',
|
|
||||||
'::placeholder': {
|
|
||||||
color: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
invalid: {
|
|
||||||
color: null,
|
|
||||||
|
|
||||||
},
|
|
||||||
};
|
|
||||||
this.StripeElementClasses = {
|
|
||||||
focus: 'is-focused',
|
|
||||||
empty: 'is-empty',
|
|
||||||
invalid: 'is-invalid',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
ngOnInit() {
|
||||||
if (!this.showOptions) {
|
if (!this.showOptions) {
|
||||||
this.hidePaypal = this.method !== PaymentMethodType.PayPal;
|
this.hidePaypal = this.method !== PaymentMethodType.PayPal;
|
||||||
this.hideBank = this.method !== PaymentMethodType.BankAccount;
|
this.hideBank = this.method !== PaymentMethodType.BankAccount;
|
||||||
this.hideCredit = this.method !== PaymentMethodType.Credit;
|
this.hideCredit = this.method !== PaymentMethodType.Credit;
|
||||||
}
|
}
|
||||||
await this.setTheme();
|
|
||||||
window.document.head.appendChild(this.stripeScript);
|
window.document.head.appendChild(this.stripeScript);
|
||||||
if (!this.hidePaypal) {
|
if (!this.hidePaypal) {
|
||||||
window.document.head.appendChild(this.btScript);
|
window.document.head.appendChild(this.btScript);
|
||||||
@@ -137,7 +126,8 @@ export class PaymentComponent implements OnInit {
|
|||||||
if (this.method === PaymentMethodType.PayPal) {
|
if (this.method === PaymentMethodType.PayPal) {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
(window as any).braintree.dropin.create({
|
(window as any).braintree.dropin.create({
|
||||||
authorization: process.env.BRAINTREE_KEY,
|
authorization: process.env.ENV === 'cloud' ?
|
||||||
|
WebConstants.btProductionKey : WebConstants.btSandboxKey,
|
||||||
container: '#bt-dropin-container',
|
container: '#bt-dropin-container',
|
||||||
paymentOptionPriority: ['paypal'],
|
paymentOptionPriority: ['paypal'],
|
||||||
paypal: {
|
paypal: {
|
||||||
@@ -147,7 +137,6 @@ export class PaymentComponent implements OnInit {
|
|||||||
size: 'medium',
|
size: 'medium',
|
||||||
shape: 'pill',
|
shape: 'pill',
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
tagline: 'false',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, (createErr: any, instance: any) => {
|
}, (createErr: any, instance: any) => {
|
||||||
@@ -231,21 +220,21 @@ export class PaymentComponent implements OnInit {
|
|||||||
if (this.showMethods && this.method === PaymentMethodType.Card) {
|
if (this.showMethods && this.method === PaymentMethodType.Card) {
|
||||||
if (this.stripeCardNumberElement == null) {
|
if (this.stripeCardNumberElement == null) {
|
||||||
this.stripeCardNumberElement = this.stripeElements.create('cardNumber', {
|
this.stripeCardNumberElement = this.stripeElements.create('cardNumber', {
|
||||||
style: this.StripeElementStyle,
|
style: StripeElementStyle,
|
||||||
classes: this.StripeElementClasses,
|
classes: StripeElementClasses,
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.stripeCardExpiryElement == null) {
|
if (this.stripeCardExpiryElement == null) {
|
||||||
this.stripeCardExpiryElement = this.stripeElements.create('cardExpiry', {
|
this.stripeCardExpiryElement = this.stripeElements.create('cardExpiry', {
|
||||||
style: this.StripeElementStyle,
|
style: StripeElementStyle,
|
||||||
classes: this.StripeElementClasses,
|
classes: StripeElementClasses,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.stripeCardCvcElement == null) {
|
if (this.stripeCardCvcElement == null) {
|
||||||
this.stripeCardCvcElement = this.stripeElements.create('cardCvc', {
|
this.stripeCardCvcElement = this.stripeElements.create('cardCvc', {
|
||||||
style: this.StripeElementStyle,
|
style: StripeElementStyle,
|
||||||
classes: this.StripeElementClasses,
|
classes: StripeElementClasses,
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -255,17 +244,4 @@ export class PaymentComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setTheme() {
|
|
||||||
const theme = await this.platformUtilsService.getEffectiveTheme();
|
|
||||||
if (theme === ThemeType.Dark) {
|
|
||||||
this.StripeElementStyle.base.color = darkInputColor;
|
|
||||||
this.StripeElementStyle.base['::placeholder'].color = darkInputPlaceholderColor;
|
|
||||||
this.StripeElementStyle.invalid.color = darkInputColor;
|
|
||||||
} else {
|
|
||||||
this.StripeElementStyle.base.color = lightInputColor;
|
|
||||||
this.StripeElementStyle.base['::placeholder'].color = lightInputPlaceholderColor;
|
|
||||||
this.StripeElementStyle.invalid.color = lightInputColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="authed">
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="authed">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<ng-container *ngIf="!enabled">
|
<ng-container *ngIf="!enabled">
|
||||||
<img class="float-right mfaType0" alt="Authenticator app logo">
|
<img src="../../images/two-factor/0.png" class="float-right" alt="">
|
||||||
<p>{{'twoStepAuthenticatorDesc' | i18n}}</p>
|
<p>{{'twoStepAuthenticatorDesc' | i18n}}</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>1. {{'twoStepAuthenticatorDownloadApp' | i18n}}</strong>
|
<strong>1. {{'twoStepAuthenticatorDownloadApp' | i18n}}</strong>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<p>{{'twoStepLoginProviderEnabled' | i18n}}</p>
|
<p>{{'twoStepLoginProviderEnabled' | i18n}}</p>
|
||||||
{{'twoStepAuthenticatorReaddDesc' | i18n}}
|
{{'twoStepAuthenticatorReaddDesc' | i18n}}
|
||||||
</app-callout>
|
</app-callout>
|
||||||
<img class="float-right mfaType0" alt="Authenticator app logo">
|
<img src="../../images/two-factor/0.png" class="float-right" alt="">
|
||||||
<p>{{'twoStepAuthenticatorNeedApp' | i18n}}</p>
|
<p>{{'twoStepAuthenticatorNeedApp' | i18n}}</p>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ul class="fa-ul">
|
<ul class="fa-ul">
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<app-callout type="success" title="{{'enabled' | i18n}}" icon="fa-check-circle">
|
<app-callout type="success" title="{{'enabled' | i18n}}" icon="fa-check-circle">
|
||||||
{{'twoStepLoginProviderEnabled' | i18n}}
|
{{'twoStepLoginProviderEnabled' | i18n}}
|
||||||
</app-callout>
|
</app-callout>
|
||||||
<img class="float-right ml-3 mfaType2" alt="Duo logo">
|
<img src="../../images/two-factor/2.png" class="float-right ml-3" alt="">
|
||||||
<strong>{{'twoFactorDuoIntegrationKey' | i18n}}:</strong> {{ikey}}
|
<strong>{{'twoFactorDuoIntegrationKey' | i18n}}:</strong> {{ikey}}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{'twoFactorDuoSecretKey' | i18n}}:</strong> {{skey}}
|
<strong>{{'twoFactorDuoSecretKey' | i18n}}:</strong> {{skey}}
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<strong>{{'twoFactorDuoApiHostname' | i18n}}:</strong> {{host}}
|
<strong>{{'twoFactorDuoApiHostname' | i18n}}:</strong> {{host}}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!enabled">
|
<ng-container *ngIf="!enabled">
|
||||||
<img class="float-right ml-3 mfaType2" alt="Duo logo">
|
<img src="../../images/two-factor/2.png" class="float-right ml-3" alt="">
|
||||||
<p>{{'twoFactorDuoDesc' | i18n}}</p>
|
<p>{{'twoFactorDuoDesc' | i18n}}</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="ikey">{{'twoFactorDuoIntegrationKey' | i18n}}</label>
|
<label for="ikey">{{'twoFactorDuoIntegrationKey' | i18n}}</label>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<ng-container *ngIf="!enabled">
|
<ng-container *ngIf="!enabled">
|
||||||
<p class="d-flex">
|
<p class="d-flex">
|
||||||
<span class="mr-3">{{'twoFactorEmailDesc' | i18n}}</span>
|
<span class="mr-3">{{'twoFactorEmailDesc' | i18n}}</span>
|
||||||
<img class="float-right ml-auto mfaType1" alt="Email logo">
|
<img src="../../images/two-factor/1.png" class="float-right ml-auto" alt="">
|
||||||
</p>
|
</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">1. {{'twoFactorEmailEnterEmail' | i18n}}</label>
|
<label for="email">1. {{'twoFactorEmailEnterEmail' | i18n}}</label>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<ul class="list-group list-group-2fa">
|
<ul class="list-group list-group-2fa">
|
||||||
<li *ngFor="let p of providers" class="list-group-item d-flex align-items-center">
|
<li *ngFor="let p of providers" class="list-group-item d-flex align-items-center">
|
||||||
<div class="logo-2fa d-flex justify-content-center">
|
<div class="logo-2fa d-flex justify-content-center">
|
||||||
<img [class]="'mfaType' + p.type" [alt]="p.name + ' logo'">
|
<img [src]="'images/two-factor/' + p.type + '.png'" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-4">
|
<div class="mx-4">
|
||||||
<h3 class="mb-0">
|
<h3 class="mb-0">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<li>{{'twoFactorWebAuthnSupportWeb' | i18n}}</li>
|
<li>{{'twoFactorWebAuthnSupportWeb' | i18n}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</app-callout>
|
</app-callout>
|
||||||
<img class="float-right ml-5 mfaType7" alt="FIDO2 WebAuthn logo'">
|
<img src="../../images/two-factor/7.png" class="float-right ml-5" alt="">
|
||||||
<ul class="fa-ul">
|
<ul class="fa-ul">
|
||||||
<li *ngFor="let k of keys; let i = index" #removeKeyBtn [appApiAction]="k.removePromise">
|
<li *ngFor="let k of keys; let i = index" #removeKeyBtn [appApiAction]="k.removePromise">
|
||||||
<i class="fa-li fa fa-key"></i>
|
<i class="fa-li fa fa-key"></i>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<li>{{'twoFactorYubikeySupportMobile' | i18n}}</li>
|
<li>{{'twoFactorYubikeySupportMobile' | i18n}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</app-callout>
|
</app-callout>
|
||||||
<img class="float-right mfaType3" alt="YubiKey OTP security key logo">
|
<img src="../../images/two-factor/3.png" class="float-right" alt="">
|
||||||
<p>{{'twoFactorYubikeyAdd' | i18n}}:</p>
|
<p>{{'twoFactorYubikeyAdd' | i18n}}:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>{{'twoFactorYubikeyPlugIn' | i18n}}</li>
|
<li>{{'twoFactorYubikeyPlugIn' | i18n}}</li>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
<app-callout type="info" *ngIf="vaultTimeoutPolicy">
|
|
||||||
{{'vaultTimeoutPolicyInEffect' | i18n : vaultTimeoutPolicyHours : vaultTimeoutPolicyMinutes}}
|
|
||||||
</app-callout>
|
|
||||||
|
|
||||||
<div [formGroup]="form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label>
|
|
||||||
<select id="vaultTimeout" name="VaultTimeout" formControlName="vaultTimeout" class="form-control">
|
|
||||||
<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" *ngIf="showCustom" formGroupName="custom">
|
|
||||||
<label for="customVaultTimeout">{{'customVaultTimeout' | i18n}}</label>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-6">
|
|
||||||
<input id="hours" class="form-control" type="number" min="0" name="hours"
|
|
||||||
formControlName="hours">
|
|
||||||
<small>{{'hours' | i18n }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<input id="minutes" class="form-control" type="number" min="0" name="minutes"
|
|
||||||
formControlName="minutes">
|
|
||||||
<small>{{'minutes' | i18n }}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import {
|
|
||||||
NG_VALIDATORS,
|
|
||||||
NG_VALUE_ACCESSOR,
|
|
||||||
} from '@angular/forms';
|
|
||||||
|
|
||||||
import {
|
|
||||||
VaultTimeoutInputComponent as VaultTimeoutInputComponentBase
|
|
||||||
} from 'jslib-angular/components/settings/vault-timeout-input.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-vault-timeout-input',
|
|
||||||
templateUrl: 'vault-timeout-input.component.html',
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: NG_VALUE_ACCESSOR,
|
|
||||||
multi: true,
|
|
||||||
useExisting: VaultTimeoutInputComponent,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: NG_VALIDATORS,
|
|
||||||
multi: true,
|
|
||||||
useExisting: VaultTimeoutInputComponent,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {
|
|
||||||
}
|
|
||||||
@@ -11,10 +11,7 @@ import { Organization } from 'jslib-common/models/domain/organization';
|
|||||||
import { AddEditComponent as OrgAddEditComponent } from '../organizations/vault/add-edit.component';
|
import { AddEditComponent as OrgAddEditComponent } from '../organizations/vault/add-edit.component';
|
||||||
import { AddEditComponent } from '../vault/add-edit.component';
|
import { AddEditComponent } from '../vault/add-edit.component';
|
||||||
|
|
||||||
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
|
|
||||||
|
|
||||||
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 { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -29,8 +26,7 @@ export class CipherReportComponent {
|
|||||||
organization: Organization;
|
organization: Organization;
|
||||||
|
|
||||||
constructor(private modalService: ModalService, protected userService: UserService,
|
constructor(private modalService: ModalService, protected userService: UserService,
|
||||||
protected messagingService: MessagingService, protected passwordRepromptService: PasswordRepromptService,
|
protected messagingService: MessagingService, public requiresPaid: boolean) { }
|
||||||
public requiresPaid: boolean) { }
|
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -40,10 +36,6 @@ export class CipherReportComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async selectCipher(cipher: CipherView) {
|
async selectCipher(cipher: CipherView) {
|
||||||
if (!await this.repromptCipher(cipher)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = this.organization != null ? OrgAddEditComponent : AddEditComponent;
|
const type = this.organization != null ? OrgAddEditComponent : AddEditComponent;
|
||||||
|
|
||||||
const [modal, childComponent] = await this.modalService.openViewRef(type, this.cipherAddEditModalRef, (comp: OrgAddEditComponent | AddEditComponent) => {
|
const [modal, childComponent] = await this.modalService.openViewRef(type, this.cipherAddEditModalRef, (comp: OrgAddEditComponent | AddEditComponent) => {
|
||||||
@@ -93,8 +85,4 @@ export class CipherReportComponent {
|
|||||||
protected async setCiphers() {
|
protected async setCiphers() {
|
||||||
this.ciphers = [];
|
this.ciphers = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async repromptCipher(c: CipherView) {
|
|
||||||
return c.reprompt === CipherRepromptType.None || await this.passwordRepromptService.showPasswordPrompt();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,11 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>{{'exportVault' | i18n}}</h1>
|
<h1>{{'exportVault' | i18n}}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-callout type="error" title="{{'vaultExportDisabled' | i18n}}" *ngIf="disabledByPolicy">
|
|
||||||
{{'personalVaultExportPolicyInEffect' | i18n}}
|
|
||||||
</app-callout>
|
|
||||||
|
|
||||||
<p>{{'exportMasterPassword' | i18n}}</p>
|
<p>{{'exportMasterPassword' | i18n}}</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-6">
|
<div class="form-group col-6">
|
||||||
<label for="format">{{'fileFormat' | i18n}}</label>
|
<label for="format">{{'fileFormat' | i18n}}</label>
|
||||||
<select class="form-control" id="format" name="Format" [(ngModel)]="format" [disabled]="disabledByPolicy">
|
<select class="form-control" id="format" name="Format" [(ngModel)]="format">
|
||||||
<option value="json">.json</option>
|
<option value="json">.json</option>
|
||||||
<option value="csv">.csv</option>
|
<option value="csv">.csv</option>
|
||||||
<option value="encrypted_json">.json (Encrypted)</option>
|
<option value="encrypted_json">.json (Encrypted)</option>
|
||||||
@@ -22,11 +17,11 @@
|
|||||||
<div class="form-group col-6">
|
<div class="form-group col-6">
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
||||||
<input id="masterPassword" type="password" name="MasterPassword" class="form-control"
|
<input id="masterPassword" type="password" name="MasterPassword" class="form-control"
|
||||||
[(ngModel)]="masterPassword" required appInputVerbatim [disabled]="disabledByPolicy">
|
[(ngModel)]="masterPassword" required appInputVerbatim>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary" [disabled]="form.loading || disabledByPolicy">
|
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="form.loading"></i>
|
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
<span *ngIf="!form.loading">{{'exportVault' | i18n}}</span>
|
<span>{{'exportVault' | i18n}}</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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 { ExportComponent as BaseExportComponent } from 'jslib-angular/components/export.component';
|
import { ExportComponent as BaseExportComponent } from 'jslib-angular/components/export.component';
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-export',
|
selector: 'app-export',
|
||||||
@@ -18,8 +17,8 @@ export class ExportComponent extends BaseExportComponent {
|
|||||||
|
|
||||||
constructor(cryptoService: CryptoService, i18nService: I18nService,
|
constructor(cryptoService: CryptoService, i18nService: I18nService,
|
||||||
platformUtilsService: PlatformUtilsService, exportService: ExportService,
|
platformUtilsService: PlatformUtilsService, exportService: ExportService,
|
||||||
eventService: EventService, policyService: PolicyService) {
|
eventService: EventService) {
|
||||||
super(cryptoService, i18nService, platformUtilsService, exportService, eventService, policyService, window);
|
super(cryptoService, i18nService, platformUtilsService, exportService, eventService, window);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected saved() {
|
protected saved() {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from 'jslib-common/abstractions/cipher.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 { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -26,8 +25,8 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple
|
|||||||
|
|
||||||
constructor(protected cipherService: CipherService, protected auditService: AuditService,
|
constructor(protected cipherService: CipherService, protected auditService: AuditService,
|
||||||
modalService: ModalService, messagingService: MessagingService,
|
modalService: ModalService, messagingService: MessagingService,
|
||||||
userService: UserService, passwordRepromptService: PasswordRepromptService) {
|
userService: UserService) {
|
||||||
super(modalService, userService, messagingService, passwordRepromptService, true);
|
super(modalService, userService, messagingService, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
|
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from 'jslib-common/abstractions/cipher.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 { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -27,8 +26,8 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl
|
|||||||
cipherDocs = new Map<string, string>();
|
cipherDocs = new Map<string, string>();
|
||||||
|
|
||||||
constructor(protected cipherService: CipherService, modalService: ModalService,
|
constructor(protected cipherService: CipherService, modalService: ModalService,
|
||||||
messagingService: MessagingService, userService: UserService, passwordRepromptService: PasswordRepromptService) {
|
messagingService: MessagingService, userService: UserService) {
|
||||||
super(modalService, userService, messagingService, passwordRepromptService, true);
|
super(modalService, userService, messagingService, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
|
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from 'jslib-common/abstractions/cipher.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 { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -24,9 +23,8 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem
|
|||||||
passwordUseMap: Map<string, number>;
|
passwordUseMap: Map<string, number>;
|
||||||
|
|
||||||
constructor(protected cipherService: CipherService, modalService: ModalService,
|
constructor(protected cipherService: CipherService, modalService: ModalService,
|
||||||
messagingService: MessagingService, userService: UserService,
|
messagingService: MessagingService, userService: UserService) {
|
||||||
passwordRepromptService: PasswordRepromptService) {
|
super(modalService, userService, messagingService, true);
|
||||||
super(modalService, userService, messagingService, passwordRepromptService, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
|
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from 'jslib-common/abstractions/cipher.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 { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -22,8 +21,8 @@ import { CipherReportComponent } from './cipher-report.component';
|
|||||||
})
|
})
|
||||||
export class UnsecuredWebsitesReportComponent extends CipherReportComponent implements OnInit {
|
export class UnsecuredWebsitesReportComponent extends CipherReportComponent implements OnInit {
|
||||||
constructor(protected cipherService: CipherService, modalService: ModalService,
|
constructor(protected cipherService: CipherService, modalService: ModalService,
|
||||||
messagingService: MessagingService, userService: UserService, passwordRepromptService: PasswordRepromptService) {
|
messagingService: MessagingService, userService: UserService) {
|
||||||
super(modalService, userService, messagingService, passwordRepromptService, true);
|
super(modalService, userService, messagingService, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from 'jslib-common/abstractions/cipher.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 { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||||
@@ -28,9 +27,9 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen
|
|||||||
private passwordStrengthCache = new Map<string, number>();
|
private passwordStrengthCache = new Map<string, number>();
|
||||||
|
|
||||||
constructor(protected cipherService: CipherService, protected passwordGenerationService: PasswordGenerationService,
|
constructor(protected cipherService: CipherService, protected passwordGenerationService: PasswordGenerationService,
|
||||||
modalService: ModalService, messagingService: MessagingService, userService: UserService,
|
modalService: ModalService, messagingService: MessagingService,
|
||||||
passwordRepromptService: PasswordRepromptService) {
|
userService: UserService) {
|
||||||
super(modalService, userService, messagingService, passwordRepromptService, true);
|
super(modalService, userService, messagingService, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
<ng-container>
|
|
||||||
<h3 class="mt-4">{{'customFields' | i18n}}</h3>
|
|
||||||
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
|
|
||||||
<div class="row" cdkDrag *ngFor="let f of cipher.fields; let i = index; trackBy:trackByFunction">
|
|
||||||
<div class="col-5 form-group">
|
|
||||||
<div class="d-flex">
|
|
||||||
<label for="fieldName{{i}}">{{'name' | i18n}}</label>
|
|
||||||
<a class="ml-auto" href="https://help.bitwarden.com/article/custom-fields/"
|
|
||||||
target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
|
|
||||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name"
|
|
||||||
class="form-control" appInputVerbatim [disabled]="cipher.isDeleted || viewOnly">
|
|
||||||
</div>
|
|
||||||
<div class="col-7 form-group">
|
|
||||||
<label for="fieldValue{{i}}">{{'value' | i18n}}</label>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="input-group" *ngIf="f.type === fieldType.Text">
|
|
||||||
<input id="fieldValue{{i}}" class="form-control" type="text" name="Field.Value{{i}}"
|
|
||||||
[(ngModel)]="f.value" appInputVerbatim
|
|
||||||
[disabled]="cipher.isDeleted || viewOnly">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
|
||||||
(click)="copy(f.value, 'value', 'Field')">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-group" *ngIf="f.type === fieldType.Hidden">
|
|
||||||
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}"
|
|
||||||
name="Field.Value{{i}}" [(ngModel)]="f.value"
|
|
||||||
class="form-control text-monospace" appInputVerbatim autocomplete="new-password"
|
|
||||||
[disabled]="cipher.isDeleted || viewOnly || (!cipher.viewPassword && !f.newField)">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)"
|
|
||||||
[disabled]="!cipher.viewPassword && !f.newField">
|
|
||||||
<i class="fa fa-lg" aria-hidden="true"
|
|
||||||
[ngClass]="{'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue}">
|
|
||||||
</i>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
|
||||||
(click)="copy(f.value, 'value', f.type === fieldType.Hidden ? 'H_Field' : 'Field')"
|
|
||||||
[disabled]="!cipher.viewPassword && !f.newField">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-fill">
|
|
||||||
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox"
|
|
||||||
[(ngModel)]="f.value" *ngIf="f.type === fieldType.Boolean" appTrueFalseValue
|
|
||||||
trueValue="true" falseValue="false" [disabled]="cipher.isDeleted || viewOnly">
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-link text-danger ml-2" (click)="removeField(f)"
|
|
||||||
appA11yTitle="{{'remove' | i18n}}" *ngIf="!cipher.isDeleted && !viewOnly">
|
|
||||||
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-link text-muted cursor-move"
|
|
||||||
appA11yTitle="{{'dragToSort' | i18n}}" *ngIf="!cipher.isDeleted && !viewOnly">
|
|
||||||
<i class="fa fa-bars fa-lg" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a href="#" appStopClick (click)="addField()" class="d-inline-block mb-2"
|
|
||||||
*ngIf="!cipher.isDeleted && !viewOnly">
|
|
||||||
<i class="fa fa-plus-circle fa-fw" aria-hidden="true"></i> {{'newCustomField' | i18n}}
|
|
||||||
</a>
|
|
||||||
<div class="row" *ngIf="!cipher.isDeleted && !viewOnly">
|
|
||||||
<div class="col-5">
|
|
||||||
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label>
|
|
||||||
<select id="addFieldType" class="form-control" name="AddFieldType" [(ngModel)]="addFieldType">
|
|
||||||
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{o.name}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
Input,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import {
|
|
||||||
AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent
|
|
||||||
} from 'jslib-angular/components/add-edit-custom-fields.component';
|
|
||||||
|
|
||||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-vault-add-edit-custom-fields',
|
|
||||||
templateUrl: 'add-edit-custom-fields.component.html',
|
|
||||||
})
|
|
||||||
export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent {
|
|
||||||
@Input() viewOnly: boolean;
|
|
||||||
@Input() copy: (value: string, typeI18nKey: string, aType: string) => void;
|
|
||||||
|
|
||||||
constructor(i18nService: I18nService, eventService: EventService) {
|
|
||||||
super(i18nService, eventService);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-6 form-group totp d-flex align-items-end" [ngClass]="{'low': totpLow}">
|
<div class="col-6 form-group totp d-flex align-items-end" [ngClass]="{'low': totpLow}">
|
||||||
<div *ngIf="!cipher.login.totp || !totpCode">
|
<div *ngIf="!cipher.login.totp || !totpCode">
|
||||||
<img src="../../images/totp-countdown.png" id="totpImage" title="{{'verificationCodeTotp' | i18n}}"
|
<img src="../../images/totp-countdown.png" title="{{'verificationCodeTotp' | i18n}}"
|
||||||
class="ml-2">
|
class="ml-2">
|
||||||
<a href="#" appStopClick class="badge badge-primary ml-3" (click)="premiumRequired()"
|
<a href="#" appStopClick class="badge badge-primary ml-3" (click)="premiumRequired()"
|
||||||
*ngIf="!organization && !cipher.organizationId && !canAccessPremium">
|
*ngIf="!organization && !cipher.organizationId && !canAccessPremium">
|
||||||
@@ -388,8 +388,85 @@
|
|||||||
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes"
|
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes"
|
||||||
[disabled]="cipher.isDeleted || viewOnly" class="form-control"></textarea>
|
[disabled]="cipher.isDeleted || viewOnly" class="form-control"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<app-vault-add-edit-custom-fields [cipher]="cipher" [viewOnly]="viewOnly" [copy]="copy.bind(this)">
|
<h3 class="mt-4">{{'customFields' | i18n}}</h3>
|
||||||
</app-vault-add-edit-custom-fields>
|
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
|
||||||
|
<div class="row" cdkDrag *ngFor="let f of cipher.fields; let i = index; trackBy:trackByFunction">
|
||||||
|
<div class="col-5 form-group">
|
||||||
|
<div class="d-flex">
|
||||||
|
<label for="fieldName{{i}}">{{'name' | i18n}}</label>
|
||||||
|
<a class="ml-auto" href="https://help.bitwarden.com/article/custom-fields/"
|
||||||
|
target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
|
||||||
|
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name"
|
||||||
|
class="form-control" appInputVerbatim [disabled]="cipher.isDeleted || viewOnly">
|
||||||
|
</div>
|
||||||
|
<div class="col-7 form-group">
|
||||||
|
<label for="fieldValue{{i}}">{{'value' | i18n}}</label>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="input-group" *ngIf="f.type === fieldType.Text">
|
||||||
|
<input id="fieldValue{{i}}" class="form-control" type="text" name="Field.Value{{i}}"
|
||||||
|
[(ngModel)]="f.value" appInputVerbatim
|
||||||
|
[disabled]="cipher.isDeleted || viewOnly">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{'copyValue' | i18n}}"
|
||||||
|
(click)="copy(f.value, 'value', 'Field')">
|
||||||
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group" *ngIf="f.type === fieldType.Hidden">
|
||||||
|
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}"
|
||||||
|
name="Field.Value{{i}}" [(ngModel)]="f.value"
|
||||||
|
class="form-control text-monospace" appInputVerbatim autocomplete="new-password"
|
||||||
|
[disabled]="cipher.isDeleted || viewOnly || (!cipher.viewPassword && !f.newField)">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)"
|
||||||
|
[disabled]="!cipher.viewPassword && !f.newField">
|
||||||
|
<i class="fa fa-lg" aria-hidden="true"
|
||||||
|
[ngClass]="{'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue}">
|
||||||
|
</i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{'copyValue' | i18n}}"
|
||||||
|
(click)="copy(f.value, 'value', f.type === fieldType.Hidden ? 'H_Field' : 'Field')"
|
||||||
|
[disabled]="!cipher.viewPassword && !f.newField">
|
||||||
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-fill">
|
||||||
|
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox"
|
||||||
|
[(ngModel)]="f.value" *ngIf="f.type === fieldType.Boolean" appTrueFalseValue
|
||||||
|
trueValue="true" falseValue="false" [disabled]="cipher.isDeleted || viewOnly">
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-link text-danger ml-2" (click)="removeField(f)"
|
||||||
|
appA11yTitle="{{'remove' | i18n}}" *ngIf="!cipher.isDeleted && !viewOnly">
|
||||||
|
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-link text-muted cursor-move"
|
||||||
|
appA11yTitle="{{'dragToSort' | i18n}}" *ngIf="!cipher.isDeleted && !viewOnly">
|
||||||
|
<i class="fa fa-bars fa-lg" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="#" appStopClick (click)="addField()" class="d-inline-block mb-2"
|
||||||
|
*ngIf="!cipher.isDeleted && !viewOnly">
|
||||||
|
<i class="fa fa-plus-circle fa-fw" aria-hidden="true"></i> {{'newCustomField' | i18n}}
|
||||||
|
</a>
|
||||||
|
<div class="row" *ngIf="!cipher.isDeleted && !viewOnly">
|
||||||
|
<div class="col-5">
|
||||||
|
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label>
|
||||||
|
<select id="addFieldType" class="form-control" name="AddFieldType" [(ngModel)]="addFieldType">
|
||||||
|
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ng-container *ngIf="allowOwnershipAssignment()">
|
<ng-container *ngIf="allowOwnershipAssignment()">
|
||||||
<h3 class="mt-4">{{'ownership' | i18n}}</h3>
|
<h3 class="mt-4">{{'ownership' | i18n}}</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user