1
0
mirror of https://github.com/bitwarden/web synced 2025-12-11 22:03:21 +00:00

Compare commits

..

5 Commits

Author SHA1 Message Date
Joseph Flinn
cfa3d81cf8 Version Bump (#1200)
* Bumping version for September's release

* Manually updating the version in the package-lock.json
2021-09-21 14:50:02 -07:00
github-actions[bot]
12c0049b9e Autosync the updated translations (#1199)
Co-authored-by: github-actions <>
2021-09-21 13:57:51 -07:00
Vincent Salucci
7f7ed9da92 [SSO/Auto Enroll] Fixed typo for banner 2021-09-16 23:02:27 -05:00
Vincent Salucci
ae4ce3e575 Update jslib 2021-09-15 21:40:00 -05:00
Thomas Rittson
d5325164ff Update jslib 2021-09-10 08:50:58 +10:00
296 changed files with 10293 additions and 32016 deletions

View File

@@ -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' 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 .
@@ -237,14 +130,14 @@ jobs:
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 images - 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: Log out of Docker - name: Log out of Docker
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
@@ -252,8 +145,8 @@ jobs:
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
@@ -269,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: |
@@ -307,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
@@ -316,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
@@ -328,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
@@ -340,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
@@ -353,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
@@ -363,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:
@@ -395,74 +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
check-failures:
name: Check for failures
if: always()
runs-on: ubuntu-20.04
needs:
- cloc
- setup
- build-oss-selfhost
- build-cloud
- build-commercial-selfhost
- build-qa
- windows
steps:
- name: Check if any job failed
if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }}
env:
CLOC_STATUS: ${{ needs.cloc.result }}
SETUP_STATUS: ${{ needs.setup.result }}
BUILD_OSS_SELFHOST_STATUS: ${{ needs.build-oss-selfhost.result }}
BUILD_CLOUD_STATUS: ${{ needs.build-cloud.result }}
BUILD_COMMERCIAL_SELFHOST_STATUS: ${{ needs.build-commercial-selfhost.result }}
BUILD_QA: ${{ needs.build-qa.result }}
WINDOWS: ${{ needs.windows.result }}
run: |
if [ "$CLOC_STATUS" = "failure" ]; then
exit 1
elif [ "$SETUP_STATUS" = "failure" ]; then
exit 1
elif [ "$BUILD_OSS_SELFHOST_STATUS" = "failure" ]; then
exit 1
elif [ "$BUILD_CLOUD_STATUS" = "failure" ]; then
exit 1
elif [ "$BUILD_COMMERCIAL_SELFHOST_STATUS" = "failure" ]; then
exit 1
elif [ "$BUILD_QA" = "failure" ]; then
exit 1
elif [ "$WINDOWS" = "failure" ]; then
exit 1
fi
- name: Login to Azure - Prod Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
if: failure()
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
if: failure()
with:
keyvault: "bitwarden-prod-kv"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
with:
status: ${{ job.status }}

73
.github/workflows/deploy.yml vendored Normal file
View 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 }}

View File

@@ -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

View File

@@ -3,15 +3,19 @@ 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: |
@@ -25,160 +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}"
if [ "v$version" == "$previous_release_tag_version" ]; then echo "::set-output name=tag_version::$RELEASE_TAG_NAME_INPUT"
echo "[!] Already released v$version. Please bump version to continue" ;;
[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 exit 1
fi ;;
esac
env:
RELEASE_TAG_NAME_INPUT: ${{ github.event.inputs.release_tag_name_input }}
echo "::set-output name=package::$version" - name: Create Draft Release
echo "::set-output name=tag::v$version" id: create_release
uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # 1.1.4 - Repo Archived
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.RELEASE_TAG_NAME }}
release_name: Version ${{ env.RELEASE_NAME }}
draft: true
prerelease: false
ubuntu:
self-host: name: Ubuntu
name: Build self-host docker runs-on: ubuntu-latest
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 rc image - name: Restore
run: docker pull bitwarden/web:rc 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:rc bitwarden/web:latest
docker tag bitwarden/web:rc 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: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
git switch 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: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success
branch: rc
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: rc
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 }}

View File

@@ -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

View File

@@ -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(),
]);
}
}

View File

@@ -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],
}) })

View File

@@ -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>

View File

@@ -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'));
}
}

View File

@@ -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 { }

View File

@@ -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 {}

View File

@@ -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>

View File

@@ -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 {
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -7,8 +7,6 @@ import {
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster'; import { ToasterService } from 'angular2-toaster';
import { first } from 'rxjs/operators';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from 'jslib-common/abstractions/log.service';
@@ -68,8 +66,11 @@ export class ClientsComponent implements OnInit {
await this.load(); await this.load();
this.route.queryParams.pipe(first()).subscribe(async qParams => { const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
this.searchText = qParams.search; this.searchText = qParams.search;
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
}); });
}); });
} }

View File

@@ -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>

View File

@@ -1,6 +1,6 @@
<div class="container page-content"> <div class="container page-content">
<div class="row"> <div class="row">
<div class="col-md-3"> <div class="col-3">
<div class="card" *ngIf="provider"> <div class="card" *ngIf="provider">
<div class="card-header">{{'manage' | i18n}}</div> <div class="card-header">{{'manage' | i18n}}</div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
@@ -15,7 +15,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-9 mt-4 mt-md-0"> <div class="col-9">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
</div> </div>

View File

@@ -7,8 +7,6 @@ import {
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster'; import { ToasterService } from 'angular2-toaster';
import { first } from 'rxjs/operators';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
@@ -83,7 +81,7 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
await this.load(); await this.load();
this.route.queryParams.pipe(first()).subscribe(async qParams => { const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
this.searchText = qParams.search; this.searchText = qParams.search;
if (qParams.viewEvents != null) { if (qParams.viewEvents != null) {
const user = this.users.filter(u => u.id === qParams.viewEvents); const user = this.users.filter(u => u.id === qParams.viewEvents);
@@ -91,6 +89,9 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
this.events(user[0]); this.events(user[0]);
} }
} }
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
}); });
}); });
} }

View File

@@ -10,7 +10,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ProviderUserInviteRequest } from 'jslib-common/models/request/provider/providerUserInviteRequest'; import { ProviderUserInviteRequest } from 'jslib-common/models/request/provider/providerUserInviteRequest';
@@ -44,8 +43,7 @@ export class UserAddEditComponent implements OnInit {
userType = ProviderUserType; userType = ProviderUserType;
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService, private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService) { }
private logService: LogService) { }
async ngOnInit() { async ngOnInit() {
this.editMode = this.loading = this.providerUserId != null; this.editMode = this.loading = this.providerUserId != null;
@@ -56,9 +54,7 @@ export class UserAddEditComponent implements OnInit {
try { try {
const user = await this.apiService.getProviderUser(this.providerId, this.providerUserId); const user = await this.apiService.getProviderUser(this.providerId, this.providerUserId);
this.type = user.type; this.type = user.type;
} catch (e) { } catch { }
this.logService.error(e);
}
} else { } else {
this.title = this.i18nService.t('inviteUser'); this.title = this.i18nService.t('inviteUser');
} }
@@ -82,9 +78,7 @@ export class UserAddEditComponent implements OnInit {
this.toasterService.popAsync('success', null, this.toasterService.popAsync('success', null,
this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name)); this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name));
this.onSavedUser.emit(); this.onSavedUser.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
async delete() { async delete() {
@@ -104,9 +98,7 @@ export class UserAddEditComponent implements OnInit {
await this.deletePromise; await this.deletePromise;
this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', this.name)); this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', this.name));
this.onDeletedUser.emit(); this.onDeletedUser.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
} }

View File

@@ -7,7 +7,7 @@
</div> </div>
<form *ngIf="provider && !loading" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate> <form *ngIf="provider && !loading" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="row"> <div class="row">
<div class="col-md-6 mt-4 mt-md-0"> <div class="col-6">
<div class="form-group"> <div class="form-group">
<label for="name">{{'providerName' | i18n}}</label> <label for="name">{{'providerName' | i18n}}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="provider.name" <input id="name" class="form-control" type="text" name="Name" [(ngModel)]="provider.name"
@@ -19,7 +19,7 @@
[(ngModel)]="provider.billingEmail" [disabled]="selfHosted"> [(ngModel)]="provider.billingEmail" [disabled]="selfHosted">
</div> </div>
</div> </div>
<div class="col-md-6 mt-4 mt-md-0"> <div class="col-6">
<app-avatar data="{{provider.name}}" dynamic="true" size="75" fontSize="35"></app-avatar> <app-avatar data="{{provider.name}}" dynamic="true" size="75" fontSize="35"></app-avatar>
</div> </div>
</div> </div>

View File

@@ -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>

View File

@@ -8,11 +8,11 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="loading"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="loading">
<h2 class="mt-5">{{'generalInformation' | i18n}}</h2> <h2 class="mt-5">{{'generalInformation' | i18n}}</h2>
<div class="row"> <div class="row">
<div class="form-group col-md-6 mt-4 mt-md-0"> <div class="form-group col-6">
<label for="name">{{'providerName' | i18n}}</label> <label for="name">{{'providerName' | 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>
</div> </div>
<div class="form-group col-md-6 mt-4 mt-md-0"> <div class="form-group col-6">
<label for="billingEmail">{{'billingEmail' | i18n}}</label> <label for="billingEmail">{{'billingEmail' | i18n}}</label>
<input id="billingEmail" class="form-control" type="text" name="BillingEmail" [(ngModel)]="billingEmail" required> <input id="billingEmail" class="form-control" type="text" name="BillingEmail" [(ngModel)]="billingEmail" required>
</div> </div>

View File

@@ -11,8 +11,6 @@ import {
ToasterService, ToasterService,
} from 'angular2-toaster'; } from 'angular2-toaster';
import { first } from 'rxjs/operators';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
@@ -43,7 +41,12 @@ export class SetupComponent implements OnInit {
ngOnInit() { ngOnInit() {
document.body.classList.remove('layout_frontend'); document.body.classList.remove('layout_frontend');
this.route.queryParams.pipe(first()).subscribe(async qParams => { let fired = false;
this.route.queryParams.subscribe(async qParams => {
if (fired) {
return;
}
fired = true;
const error = qParams.providerId == null || qParams.email == null || qParams.token == null; const error = qParams.providerId == null || qParams.email == null || qParams.token == null;
if (error) { if (error) {

View File

@@ -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"
}
} }

View File

@@ -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"
} }
} }

View File

@@ -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"
} }
} }

View File

@@ -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
View 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

Submodule jslib updated: 815b436f7c...1c28396d1a

8761
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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>

View File

@@ -12,7 +12,6 @@ import {
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from 'jslib-common/abstractions/state.service';
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from 'jslib-common/abstractions/user.service';
@@ -37,7 +36,7 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
i18nService: I18nService, route: ActivatedRoute, i18nService: I18nService, route: ActivatedRoute,
private apiService: ApiService, userService: UserService, private apiService: ApiService, userService: UserService,
stateService: StateService, private cryptoService: CryptoService, stateService: StateService, private cryptoService: CryptoService,
private policyService: PolicyService, private logService: LogService) { private policyService: PolicyService) {
super(router, toasterService, i18nService, route, userService, stateService); super(router, toasterService, i18nService, route, userService, stateService);
} }
@@ -102,9 +101,7 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
const policies = await this.apiService.getPoliciesByToken(qParams.organizationId, qParams.token, const policies = await this.apiService.getPoliciesByToken(qParams.organizationId, qParams.token,
qParams.email, qParams.organizationUserId); qParams.email, qParams.organizationUserId);
policyList = this.policyService.mapPoliciesFromToken(policies); policyList = this.policyService.mapPoliciesFromToken(policies);
} catch (e) { } catch { }
this.logService.error(e);
}
if (policyList != null) { if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(policyList, qParams.organizationId); const result = this.policyService.getResetPasswordPolicyOptions(policyList, qParams.organizationId);

View File

@@ -3,7 +3,6 @@ import { Router } from '@angular/router';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { HintComponent as BaseHintComponent } from 'jslib-angular/components/hint.component'; import { HintComponent as BaseHintComponent } from 'jslib-angular/components/hint.component';
@@ -14,8 +13,7 @@ import { HintComponent as BaseHintComponent } from 'jslib-angular/components/hin
}) })
export class HintComponent extends BaseHintComponent { export class HintComponent extends BaseHintComponent {
constructor(router: Router, i18nService: I18nService, constructor(router: Router, i18nService: I18nService,
apiService: ApiService, platformUtilsService: PlatformUtilsService, apiService: ApiService, platformUtilsService: PlatformUtilsService) {
logService: LogService) { super(router, i18nService, apiService, platformUtilsService);
super(router, i18nService, apiService, platformUtilsService, logService);
} }
} }

View File

@@ -1,6 +1,6 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-10 col-md-5"> <div class="col-5">
<p class="text-center mb-4"> <p class="text-center mb-4">
<i class="fa fa-lock fa-4x text-muted" aria-hidden="true"></i> <i class="fa fa-lock fa-4x text-muted" aria-hidden="true"></i>
</p> </p>

View File

@@ -5,7 +5,6 @@ import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from 'jslib-common/abstractions/state.service';
@@ -27,9 +26,9 @@ export class LockComponent extends BaseLockComponent {
userService: UserService, cryptoService: CryptoService, userService: UserService, cryptoService: CryptoService,
storageService: StorageService, vaultTimeoutService: VaultTimeoutService, storageService: StorageService, vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService, private routerService: RouterService, environmentService: EnvironmentService, private routerService: RouterService,
stateService: StateService, apiService: ApiService, logService: LogService) { stateService: StateService, apiService: ApiService) {
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService, super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService,
storageService, vaultTimeoutService, environmentService, stateService, apiService, logService); storageService, vaultTimeoutService, environmentService, stateService, apiService);
} }
async ngOnInit() { async ngOnInit() {

View File

@@ -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-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-10 col-md-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">

View File

@@ -4,14 +4,11 @@ import {
Router, Router,
} from '@angular/router'; } from '@angular/router';
import { first } from 'rxjs/operators';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { 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';
@@ -35,17 +32,17 @@ export class LoginComponent extends BaseLoginComponent {
storageService: StorageService, stateService: StateService, storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService, passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService, private policyService: PolicyService, logService: LogService) { private apiService: ApiService, private policyService: PolicyService) {
super(authService, router, super(authService, router,
platformUtilsService, i18nService, platformUtilsService, i18nService,
stateService, environmentService, stateService, environmentService,
passwordGenerationService, cryptoFunctionService, passwordGenerationService, cryptoFunctionService,
storageService, logService); storageService);
this.onSuccessfulLoginNavigate = this.goAfterLogIn; this.onSuccessfulLoginNavigate = this.goAfterLogIn;
} }
async ngOnInit() { async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => { const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
if (qParams.email != null && qParams.email.indexOf('@') > -1) { if (qParams.email != null && qParams.email.indexOf('@') > -1) {
this.email = qParams.email; this.email = qParams.email;
} }
@@ -56,6 +53,9 @@ export class LoginComponent extends BaseLoginComponent {
{ route: '/settings/create-organization', qParams: { plan: qParams.org } }); { route: '/settings/create-organization', qParams: { plan: qParams.org } });
} }
await super.ngOnInit(); await super.ngOnInit();
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
}); });
const invite = await this.stateService.get<any>('orgInvitation'); const invite = await this.stateService.get<any>('orgInvitation');
@@ -65,9 +65,7 @@ export class LoginComponent extends BaseLoginComponent {
const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token, const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token,
invite.email, invite.organizationUserId); invite.email, invite.organizationUserId);
policyList = this.policyService.mapPoliciesFromToken(policies); policyList = this.policyService.mapPoliciesFromToken(policies);
} catch (e) { } catch { }
this.logService.error(e);
}
if (policyList != null) { if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(policyList, invite.organizationId); const result = this.policyService.getResetPasswordPolicyOptions(policyList, invite.organizationId);

View File

@@ -5,7 +5,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { DeleteRecoverRequest } from 'jslib-common/models/request/deleteRecoverRequest'; import { DeleteRecoverRequest } from 'jslib-common/models/request/deleteRecoverRequest';
@@ -18,8 +17,7 @@ export class RecoverDeleteComponent {
formPromise: Promise<any>; formPromise: Promise<any>;
constructor(private router: Router, private apiService: ApiService, constructor(private router: Router, private apiService: ApiService,
private toasterService: ToasterService, private i18nService: I18nService, private toasterService: ToasterService, private i18nService: I18nService) {
private logService: LogService) {
} }
async submit() { async submit() {
@@ -30,8 +28,6 @@ export class RecoverDeleteComponent {
await this.formPromise; await this.formPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('deleteRecoverEmailSent')); this.toasterService.popAsync('success', null, this.i18nService.t('deleteRecoverEmailSent'));
this.router.navigate(['/']); this.router.navigate(['/']);
} catch (e) { } catch { }
this.logService.error(e);
}
} }
} }

View File

@@ -7,7 +7,6 @@ import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { TwoFactorRecoveryRequest } from 'jslib-common/models/request/twoFactorRecoveryRequest'; import { TwoFactorRecoveryRequest } from 'jslib-common/models/request/twoFactorRecoveryRequest';
@@ -23,8 +22,7 @@ export class RecoverTwoFactorComponent {
constructor(private router: Router, private apiService: ApiService, constructor(private router: Router, private apiService: ApiService,
private toasterService: ToasterService, private i18nService: I18nService, private toasterService: ToasterService, private i18nService: I18nService,
private cryptoService: CryptoService, private authService: AuthService, private cryptoService: CryptoService, private authService: AuthService) { }
private logService: LogService) { }
async submit() { async submit() {
try { try {
@@ -37,8 +35,6 @@ export class RecoverTwoFactorComponent {
await this.formPromise; await this.formPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('twoStepRecoverDisabled')); this.toasterService.popAsync('success', null, this.i18nService.t('twoStepRecoverDisabled'));
this.router.navigate(['/']); this.router.navigate(['/']);
} catch (e) { } catch { }
this.logService.error(e);
}
} }
} }

View File

@@ -2,7 +2,7 @@
<header class="header" *ngIf="layout === 'enterprise2'"> <header class="header" *ngIf="layout === 'enterprise2'">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-7"> <div class="col-7">
<img alt="Bitwarden" class="logo mb-2" src="../../images/register-layout/logo-horizontal-white.png"> <img alt="Bitwarden" class="logo mb-2" src="../../images/register-layout/logo-horizontal-white.png">
</div> </div>
</div> </div>
@@ -10,7 +10,7 @@
</header> </header>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row"> <div class="row">
<div class="col-md-7" *ngIf="layout"> <div class="col-7" *ngIf="layout">
<div class="mt-5"> <div class="mt-5">
<div *ngIf="layout === 'enterprise2'"> <div *ngIf="layout === 'enterprise2'">
<h2>Companies globally trust Bitwarden for password management.</h2> <h2>Companies globally trust Bitwarden for password management.</h2>
@@ -38,9 +38,9 @@
</div> </div>
</div> </div>
</div> </div>
<div [ngClass]="{'col-md-6': layout, 'col-12': !layout}"> <div [ngClass]="{'col-5': layout, 'col-12': !layout}">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div [ngClass]="{'col-md-6': !layout, 'col-12': layout}"> <div [ngClass]="{'col-5': !layout, 'col-12': layout}">
<p class="lead text-center mb-4" *ngIf="!layout">{{'createAccount' | i18n}}</p> <p class="lead text-center mb-4" *ngIf="!layout">{{'createAccount' | i18n}}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">

View File

@@ -4,14 +4,11 @@ import {
Router, Router,
} from '@angular/router'; } from '@angular/router';
import { first } from 'rxjs/operators';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { 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';
@@ -41,13 +38,13 @@ export class RegisterComponent extends BaseRegisterComponent {
apiService: ApiService, private route: ActivatedRoute, apiService: ApiService, private route: ActivatedRoute,
stateService: StateService, platformUtilsService: PlatformUtilsService, stateService: StateService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, private policyService: PolicyService, passwordGenerationService: PasswordGenerationService, private policyService: PolicyService,
environmentService: EnvironmentService, logService: LogService) { environmentService: EnvironmentService) {
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService, super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
passwordGenerationService, environmentService, logService); passwordGenerationService, environmentService);
} }
async ngOnInit() { async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(qParams => { const queryParamsSub = this.route.queryParams.subscribe(qParams => {
this.referenceData = new ReferenceEventRequest(); this.referenceData = new ReferenceEventRequest();
if (qParams.email != null && qParams.email.indexOf('@') > -1) { if (qParams.email != null && qParams.email.indexOf('@') > -1) {
this.email = qParams.email; this.email = qParams.email;
@@ -71,6 +68,9 @@ export class RegisterComponent extends BaseRegisterComponent {
if (this.referenceData.id === '') { if (this.referenceData.id === '') {
this.referenceData.id = null; this.referenceData.id = null;
} }
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
}); });
const invite = await this.stateService.get<any>('orgInvitation'); const invite = await this.stateService.get<any>('orgInvitation');
if (invite != null) { if (invite != null) {
@@ -81,9 +81,7 @@ export class RegisterComponent extends BaseRegisterComponent {
const policiesData = policies.data.map(p => new PolicyData(p)); const policiesData = policies.data.map(p => new PolicyData(p));
this.policies = policiesData.map(p => new Policy(p)); this.policies = policiesData.map(p => new Policy(p));
} }
} catch (e) { } catch { }
this.logService.error(e);
}
} }
if (this.policies != null) { if (this.policies != null) {

View File

@@ -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>

View File

@@ -4,14 +4,11 @@ import {
Router, Router,
} from '@angular/router'; } from '@angular/router';
import { first } from 'rxjs/operators';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from 'jslib-common/abstractions/state.service';
@@ -31,16 +28,16 @@ export class SsoComponent extends BaseSsoComponent {
storageService: StorageService, stateService: StateService, storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService, apiService: ApiService, platformUtilsService: PlatformUtilsService, apiService: ApiService,
cryptoFunctionService: CryptoFunctionService, environmentService: EnvironmentService, cryptoFunctionService: CryptoFunctionService, environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService, logService: LogService) { passwordGenerationService: PasswordGenerationService) {
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService, super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService); apiService, cryptoFunctionService, environmentService, passwordGenerationService);
this.redirectUri = window.location.origin + '/sso-connector.html'; this.redirectUri = window.location.origin + '/sso-connector.html';
this.clientId = 'web'; this.clientId = 'web';
} }
async ngOnInit() { async ngOnInit() {
super.ngOnInit(); super.ngOnInit();
this.route.queryParams.pipe(first()).subscribe(async qParams => { const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
if (qParams.identifier != null) { if (qParams.identifier != null) {
this.identifier = qParams.identifier; this.identifier = qParams.identifier;
} else { } else {
@@ -49,6 +46,9 @@ export class SsoComponent extends BaseSsoComponent {
this.identifier = storedIdentifier; this.identifier = storedIdentifier;
} }
} }
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
}); });
} }

View File

@@ -1,7 +1,7 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate autocomplete="off"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-md-5" <div class="col-5"
[ngClass]="{'col-md-9': selectedProviderType === providerType.Duo || selectedProviderType === providerType.OrganizationDuo}"> [ngClass]="{'col-9': selectedProviderType === providerType.Duo || selectedProviderType === providerType.OrganizationDuo}">
<p class="lead text-center mb-4">{{title}}</p> <p class="lead text-center mb-4">{{title}}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">

View File

@@ -23,7 +23,6 @@ import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType'
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib-angular/components/two-factor.component'; import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib-angular/components/two-factor.component';
import { LogService } from 'jslib-common/abstractions/log.service';
import { TwoFactorOptionsComponent } from './two-factor-options.component'; import { TwoFactorOptionsComponent } from './two-factor-options.component';
@Component({ @Component({
@@ -37,9 +36,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
i18nService: I18nService, apiService: ApiService, i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, stateService: StateService, platformUtilsService: PlatformUtilsService, stateService: StateService,
environmentService: EnvironmentService, private modalService: ModalService, environmentService: EnvironmentService, private modalService: ModalService,
storageService: StorageService, route: ActivatedRoute, logService: LogService) { storageService: StorageService, route: ActivatedRoute) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService, super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
stateService, storageService, route, logService); stateService, storageService, route);
this.onSuccessfulLoginNavigate = this.goAfterLogIn; this.onSuccessfulLoginNavigate = this.goAfterLogIn;
} }

View File

@@ -1,6 +1,6 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-md-4"> <div class="col-4">
<p class="lead text-center mb-4">{{'updateMasterPassword' | i18n}}</p> <p class="lead text-center mb-4">{{'updateMasterPassword' | i18n}}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">

View File

@@ -3,12 +3,10 @@ import { Component } from '@angular/core';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { 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';
@@ -22,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, logService: LogService) {
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService, super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
userService, messagingService, apiService, syncService, logService); userService, messagingService, apiService);
} }
} }

View File

@@ -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>

View File

@@ -7,13 +7,10 @@ import {
Router, Router,
} from '@angular/router'; } from '@angular/router';
import { first } from 'rxjs/operators';
import { ToasterService } from 'angular2-toaster'; import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from 'jslib-common/abstractions/user.service';
import { VerifyEmailRequest } from 'jslib-common/models/request/verifyEmailRequest'; import { VerifyEmailRequest } from 'jslib-common/models/request/verifyEmailRequest';
@@ -25,11 +22,15 @@ import { VerifyEmailRequest } from 'jslib-common/models/request/verifyEmailReque
export class VerifyEmailTokenComponent implements OnInit { export class VerifyEmailTokenComponent implements OnInit {
constructor(private router: Router, private toasterService: ToasterService, constructor(private router: Router, private toasterService: ToasterService,
private i18nService: I18nService, private route: ActivatedRoute, private i18nService: I18nService, private route: ActivatedRoute,
private apiService: ApiService, private userService: UserService, private apiService: ApiService, private userService: UserService) { }
private logService: LogService) { }
ngOnInit() { ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => { let fired = false;
this.route.queryParams.subscribe(async qParams => {
if (fired) {
return;
}
fired = true;
if (qParams.userId != null && qParams.token != null) { if (qParams.userId != null && qParams.token != null) {
try { try {
await this.apiService.postAccountVerifyEmailToken( await this.apiService.postAccountVerifyEmailToken(
@@ -41,9 +42,7 @@ export class VerifyEmailTokenComponent implements OnInit {
this.toasterService.popAsync('success', null, this.i18nService.t('emailVerified')); this.toasterService.popAsync('success', null, this.i18nService.t('emailVerified'));
this.router.navigate(['/']); this.router.navigate(['/']);
return; return;
} catch (e) { } catch { }
this.logService.error(e);
}
} }
this.toasterService.popAsync('error', null, this.i18nService.t('emailVerifiedFailed')); this.toasterService.popAsync('error', null, this.i18nService.t('emailVerifiedFailed'));
this.router.navigate(['/']); this.router.navigate(['/']);

View File

@@ -7,13 +7,10 @@ import {
Router, Router,
} from '@angular/router'; } from '@angular/router';
import { first } from 'rxjs/operators';
import { ToasterService } from 'angular2-toaster'; import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { VerifyDeleteRecoverRequest } from 'jslib-common/models/request/verifyDeleteRecoverRequest'; import { VerifyDeleteRecoverRequest } from 'jslib-common/models/request/verifyDeleteRecoverRequest';
@@ -30,11 +27,16 @@ export class VerifyRecoverDeleteComponent implements OnInit {
constructor(private router: Router, private apiService: ApiService, constructor(private router: Router, private apiService: ApiService,
private toasterService: ToasterService, private i18nService: I18nService, private toasterService: ToasterService, private i18nService: I18nService,
private route: ActivatedRoute, private logService: LogService) { private route: ActivatedRoute) {
} }
ngOnInit() { ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => { let fired = false;
this.route.queryParams.subscribe(async qParams => {
if (fired) {
return;
}
fired = true;
if (qParams.userId != null && qParams.token != null && qParams.email != null) { if (qParams.userId != null && qParams.token != null && qParams.email != null) {
this.userId = qParams.userId; this.userId = qParams.userId;
this.token = qParams.token; this.token = qParams.token;
@@ -53,8 +55,6 @@ export class VerifyRecoverDeleteComponent implements OnInit {
this.toasterService.popAsync('success', this.i18nService.t('accountDeleted'), this.toasterService.popAsync('success', this.i18nService.t('accountDeleted'),
this.i18nService.t('accountDeletedDesc')); this.i18nService.t('accountDeletedDesc'));
this.router.navigate(['/']); this.router.navigate(['/']);
} catch (e) { } catch { }
this.logService.error(e);
}
} }
} }

View File

@@ -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],
}) })

View File

@@ -12,8 +12,6 @@ import {
ToasterService, ToasterService,
} from 'angular2-toaster'; } from 'angular2-toaster';
import { first } from 'rxjs/operators';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from 'jslib-common/abstractions/state.service';
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from 'jslib-common/abstractions/user.service';
@@ -37,7 +35,12 @@ export abstract class BaseAcceptComponent implements OnInit {
abstract unauthedHandler(qParams: any): Promise<void>; abstract unauthedHandler(qParams: any): Promise<void>;
ngOnInit() { ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => { let fired = false;
this.route.queryParams.subscribe(async qParams => {
if (fired) {
return;
}
fired = true;
await this.stateService.remove('loginRedirect'); await this.stateService.remove('loginRedirect');
let error = this.requiredParameters.some(e => qParams?.[e] == null || qParams[e] === ''); let error = this.requiredParameters.some(e => qParams?.[e] == null || qParams[e] === '');

View File

@@ -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()

View File

@@ -94,7 +94,7 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
protected toasterService: ToasterService, protected cryptoService: CryptoService, protected toasterService: ToasterService, protected cryptoService: CryptoService,
private storageService: StorageService, protected validationService: ValidationService, private storageService: StorageService, protected validationService: ValidationService,
protected modalService: ModalService, private logService: LogService, protected modalService: ModalService, private logService: LogService,
private searchPipe: SearchPipe, protected userNamePipe: UserNamePipe ) { } private searchPipe: SearchPipe, protected userNamePipe: UserNamePipe) { }
abstract edit(user: UserType): void; abstract edit(user: UserType): void;
abstract getUsers(): Promise<ListResponse<UserType>>; abstract getUsers(): Promise<ListResponse<UserType>>;
@@ -253,11 +253,10 @@ 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 (e) { } catch (e) {
this.logService.error(e); this.logService.error(`Handled exception: ${e}`);
} }
}); });
}); });
@@ -267,9 +266,7 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
try { try {
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer); const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
this.logService.info(`User's fingerprint: ${fingerprint.join('-')}`); this.logService.info(`User's fingerprint: ${fingerprint.join('-')}`);
} catch (e) { } catch { }
this.logService.error(e);
}
await confirmUser(publicKey); await confirmUser(publicKey);
} catch (e) { } catch (e) {
this.logService.error(`Handled exception: ${e}`); this.logService.error(`Handled exception: ${e}`);

View File

@@ -0,0 +1,138 @@
import {
Component,
Input,
OnChanges,
OnInit,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { Utils } from 'jslib-common/misc/utils';
@Component({
selector: 'app-avatar',
template: '<img [src]="sanitizer.bypassSecurityTrustResourceUrl(src)" title="{{data}}" ' +
'[ngClass]="{\'rounded-circle\': circle}">',
})
export class AvatarComponent implements OnChanges, OnInit {
@Input() data: string;
@Input() email: string;
@Input() size = 45;
@Input() charCount = 2;
@Input() textColor = '#ffffff';
@Input() fontSize = 20;
@Input() fontWeight = 300;
@Input() dynamic = false;
@Input() circle = false;
src: string;
constructor(public sanitizer: DomSanitizer, private cryptoFunctionService: CryptoFunctionService,
private stateService: StateService) { }
ngOnInit() {
if (!this.dynamic) {
this.generate();
}
}
ngOnChanges() {
if (this.dynamic) {
this.generate();
}
}
private async generate() {
const enableGravatars = await this.stateService.get<boolean>('enableGravatars');
if (enableGravatars && this.email != null) {
const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5');
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();
this.src = 'https://www.gravatar.com/avatar/' + hash + '?s=' + this.size + '&r=pg&d=retro';
} else {
let chars: string = null;
const upperData = this.data.toUpperCase();
if (this.charCount > 1) {
chars = this.getFirstLetters(upperData, this.charCount);
}
if (chars == null) {
chars = this.unicodeSafeSubstring(upperData, this.charCount);
}
// If the chars contain an emoji, only show it.
if (chars.match(Utils.regexpEmojiPresentation)) {
chars = chars.match(Utils.regexpEmojiPresentation)[0];
}
const charObj = this.getCharText(chars);
const color = this.stringToColor(upperData);
const svg = this.getSvg(this.size, color);
svg.appendChild(charObj);
const html = window.document.createElement('div').appendChild(svg).outerHTML;
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
this.src = 'data:image/svg+xml;base64,' + svgHtml;
}
}
private stringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
// tslint:disable-next-line
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = '#';
for (let i = 0; i < 3; i++) {
// tslint:disable-next-line
const value = (hash >> (i * 8)) & 0xFF;
color += ('00' + value.toString(16)).substr(-2);
}
return color;
}
private getFirstLetters(data: string, count: number): string {
const parts = data.split(' ');
if (parts.length > 1) {
let text = '';
for (let i = 0; i < count; i++) {
text += this.unicodeSafeSubstring(parts[i], 1);
}
return text;
}
return null;
}
private getSvg(size: number, color: string): HTMLElement {
const svgTag = window.document.createElement('svg');
svgTag.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgTag.setAttribute('pointer-events', 'none');
svgTag.setAttribute('width', size.toString());
svgTag.setAttribute('height', size.toString());
svgTag.style.backgroundColor = color;
svgTag.style.width = size + 'px';
svgTag.style.height = size + 'px';
return svgTag;
}
private getCharText(character: string): HTMLElement {
const textTag = window.document.createElement('text');
textTag.setAttribute('text-anchor', 'middle');
textTag.setAttribute('y', '50%');
textTag.setAttribute('x', '50%');
textTag.setAttribute('dy', '0.35em');
textTag.setAttribute('pointer-events', 'auto');
textTag.setAttribute('fill', this.textColor);
textTag.setAttribute('font-family', '"Open Sans","Helvetica Neue",Helvetica,Arial,' +
'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"');
textTag.textContent = character;
textTag.style.fontWeight = this.fontWeight.toString();
textTag.style.fontSize = this.fontSize + 'px';
return textTag;
}
private unicodeSafeSubstring(str: string, count: number) {
const characters = str.match(/./ug);
return characters != null ? characters.slice(0, count).join('') : '';
}
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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:

View File

@@ -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>

View File

@@ -11,7 +11,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { 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';
@@ -30,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();
@@ -49,8 +46,7 @@ export class CollectionAddEditComponent implements OnInit {
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService, private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService, private userService: UserService, private cryptoService: CryptoService, private userService: UserService) { }
private logService: LogService) { }
async ngOnInit() { async ngOnInit() {
const organization = await this.userService.getOrganization(this.organizationId); const organization = await this.userService.getOrganization(this.organizationId);
@@ -79,9 +75,7 @@ export class CollectionAddEditComponent implements OnInit {
} }
}); });
} }
} catch (e) { } catch { }
this.logService.error(e);
}
} else { } else {
this.title = this.i18nService.t('addCollection'); this.title = this.i18nService.t('addCollection');
} }
@@ -131,9 +125,7 @@ export class CollectionAddEditComponent implements OnInit {
this.toasterService.popAsync('success', null, this.toasterService.popAsync('success', null,
this.i18nService.t(this.editMode ? 'editedCollectionId' : 'createdCollectionId', this.name)); this.i18nService.t(this.editMode ? 'editedCollectionId' : 'createdCollectionId', this.name));
this.onSavedCollection.emit(); this.onSavedCollection.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
async delete() { async delete() {
@@ -153,8 +145,6 @@ export class CollectionAddEditComponent implements OnInit {
await this.deletePromise; await this.deletePromise;
this.toasterService.popAsync('success', null, this.i18nService.t('deletedCollectionId', this.name)); this.toasterService.popAsync('success', null, this.i18nService.t('deletedCollectionId', this.name));
this.onDeletedCollection.emit(); this.onDeletedCollection.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
} }

View File

@@ -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>

View File

@@ -7,12 +7,9 @@ import {
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster'; import { ToasterService } from 'angular2-toaster';
import { first } from 'rxjs/operators';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from 'jslib-common/abstractions/search.service';
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from 'jslib-common/abstractions/user.service';
@@ -21,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,
@@ -41,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;
@@ -58,40 +51,32 @@ export class CollectionsComponent implements OnInit {
private collectionService: CollectionService, private modalService: ModalService, private collectionService: CollectionService, private modalService: ModalService,
private toasterService: ToasterService, private i18nService: I18nService, private toasterService: ToasterService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private userService: UserService, private platformUtilsService: PlatformUtilsService, private userService: UserService,
private searchService: SearchService, private logService: LogService) { } private searchService: SearchService) { }
async ngOnInit() { async 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;
await this.load(); await this.load();
this.route.queryParams.pipe(first()).subscribe(async qParams => { const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
this.searchText = qParams.search; this.searchText = qParams.search;
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
}); });
}); });
} }
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,10 +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 (e) { } catch { }
this.logService.error(e);
this.toasterService.popAsync('error', null, this.i18nService.t('missingPermissions'));
}
} }
async users(collection: CollectionView) { async users(collection: CollectionView) {
@@ -192,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) {

View File

@@ -8,7 +8,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { EventService } from '../../services/event.service'; import { EventService } from '../../services/event.service';
@@ -43,7 +42,7 @@ export class EntityEventsComponent implements OnInit {
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private eventService: EventService, private toasterService: ToasterService, private eventService: EventService, private toasterService: ToasterService,
private userNamePipe: UserNamePipe, private logService: LogService) { } private userNamePipe: UserNamePipe) { }
async ngOnInit() { async ngOnInit() {
const defaultDates = this.eventService.getDefaultDateFilters(); const defaultDates = this.eventService.getDefaultDateFilters();
@@ -99,9 +98,7 @@ export class EntityEventsComponent implements OnInit {
this.morePromise = promise; this.morePromise = promise;
} }
response = await promise; response = await promise;
} catch (e) { } catch { }
this.logService.error(e);
}
this.continuationToken = response.continuationToken; this.continuationToken = response.continuationToken;
const events = await Promise.all(response.data.map(async r => { const events = await Promise.all(response.data.map(async r => {

View File

@@ -10,7 +10,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType'; import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType'; import { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
@@ -42,7 +41,7 @@ export class EntityUsersComponent implements OnInit {
private allUsers: OrganizationUserUserDetailsResponse[] = []; private allUsers: OrganizationUserUserDetailsResponse[] = [];
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService, private logService: LogService) { } private toasterService: ToasterService) { }
async ngOnInit() { async ngOnInit() {
await this.loadUsers(); await this.loadUsers();
@@ -131,8 +130,6 @@ export class EntityUsersComponent implements OnInit {
await this.formPromise; await this.formPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('updatedUsers')); this.toasterService.popAsync('success', null, this.i18nService.t('updatedUsers'));
this.onEditedUsers.emit(); this.onEditedUsers.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
} }

View File

@@ -11,7 +11,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CollectionData } from 'jslib-common/models/data/collectionData'; import { CollectionData } from 'jslib-common/models/data/collectionData';
@@ -43,7 +42,7 @@ export class GroupAddEditComponent implements OnInit {
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService, private collectionService: CollectionService, private toasterService: ToasterService, private collectionService: CollectionService,
private platformUtilsService: PlatformUtilsService, private logService: LogService) { } private platformUtilsService: PlatformUtilsService) { }
async ngOnInit() { async ngOnInit() {
this.editMode = this.loading = this.groupId != null; this.editMode = this.loading = this.groupId != null;
@@ -67,9 +66,7 @@ export class GroupAddEditComponent implements OnInit {
} }
}); });
} }
} catch (e) { } catch { }
this.logService.error(e);
}
} else { } else {
this.title = this.i18nService.t('addGroup'); this.title = this.i18nService.t('addGroup');
} }
@@ -115,9 +112,7 @@ export class GroupAddEditComponent implements OnInit {
this.toasterService.popAsync('success', null, this.toasterService.popAsync('success', null,
this.i18nService.t(this.editMode ? 'editedGroupId' : 'createdGroupId', this.name)); this.i18nService.t(this.editMode ? 'editedGroupId' : 'createdGroupId', this.name));
this.onSavedGroup.emit(); this.onSavedGroup.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
async delete() { async delete() {
@@ -137,8 +132,6 @@ export class GroupAddEditComponent implements OnInit {
await this.deletePromise; await this.deletePromise;
this.toasterService.popAsync('success', null, this.i18nService.t('deletedGroupId', this.name)); this.toasterService.popAsync('success', null, this.i18nService.t('deletedGroupId', this.name));
this.onDeletedGroup.emit(); this.onDeletedGroup.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
} }

View File

@@ -9,13 +9,10 @@ import {
Router, Router,
} from '@angular/router'; } from '@angular/router';
import { first } from 'rxjs/operators';
import { ToasterService } from 'angular2-toaster'; import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from 'jslib-common/abstractions/search.service';
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from 'jslib-common/abstractions/user.service';
@@ -52,7 +49,7 @@ export class GroupsComponent implements OnInit {
private i18nService: I18nService, private modalService: ModalService, private i18nService: I18nService, private modalService: ModalService,
private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService, private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService,
private userService: UserService, private router: Router, private userService: UserService, private router: Router,
private searchService: SearchService, private logService: LogService) { } private searchService: SearchService) { }
async ngOnInit() { async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => { this.route.parent.parent.params.subscribe(async params => {
@@ -63,8 +60,11 @@ export class GroupsComponent implements OnInit {
return; return;
} }
await this.load(); await this.load();
this.route.queryParams.pipe(first()).subscribe(async qParams => { const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
this.searchText = qParams.search; this.searchText = qParams.search;
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
}); });
}); });
} }
@@ -125,9 +125,7 @@ export class GroupsComponent implements OnInit {
await this.apiService.deleteGroup(this.organizationId, group.id); await this.apiService.deleteGroup(this.organizationId, group.id);
this.toasterService.popAsync('success', null, this.i18nService.t('deletedGroupId', group.name)); this.toasterService.popAsync('success', null, this.i18nService.t('deletedGroupId', group.name));
this.removeGroup(group); this.removeGroup(group);
} catch (e) { } catch { }
this.logService.error(e);
}
} }
async users(group: GroupResponse) { async users(group: GroupResponse) {

View File

@@ -1,6 +1,6 @@
<div class="container page-content"> <div class="container page-content">
<div class="row"> <div class="row">
<div class="col-12 col-md-4 col-lg-3 mt-4 mt-md-0"> <div class="col-3">
<div class="card" *ngIf="organization"> <div class="card" *ngIf="organization">
<div class="card-header">{{'manage' | i18n}}</div> <div class="card-header">{{'manage' | i18n}}</div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
@@ -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}}
@@ -31,7 +27,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-md-8 col-lg-9 mt-4 mt-md-0"> <div class="col-9">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
</div> </div>

View File

@@ -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;
}); });

View File

@@ -5,8 +5,6 @@ import {
ViewContainerRef, ViewContainerRef,
} from '@angular/core'; } from '@angular/core';
import { first } from 'rxjs/operators';
import { import {
ActivatedRoute, ActivatedRoute,
Router, Router,
@@ -122,7 +120,7 @@ export class PeopleComponent extends BasePeopleComponent<OrganizationUserUserDet
await this.load(); await this.load();
this.route.queryParams.pipe(first()).subscribe(async qParams => { const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
this.searchText = qParams.search; this.searchText = qParams.search;
if (qParams.viewEvents != null) { if (qParams.viewEvents != null) {
const user = this.users.filter(u => u.id === qParams.viewEvents); const user = this.users.filter(u => u.id === qParams.viewEvents);
@@ -130,6 +128,9 @@ export class PeopleComponent extends BasePeopleComponent<OrganizationUserUserDet
this.events(user[0]); this.events(user[0]);
} }
} }
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
}); });
}); });
} }

View File

@@ -9,11 +9,12 @@ import {
Router, Router,
} from '@angular/router'; } from '@angular/router';
import { first } from 'rxjs/operators';
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';
@@ -24,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({
@@ -39,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 => {
@@ -60,7 +68,7 @@ export class PoliciesComponent implements OnInit {
await this.load(); await this.load();
// Handle policies component launch from Event message // Handle policies component launch from Event message
this.route.queryParams.pipe(first()).subscribe(async qParams => { const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
if (qParams.policyId != null) { if (qParams.policyId != null) {
const policyIdFromEvents: string = qParams.policyId; const policyIdFromEvents: string = qParams.policyId;
for (const orgPolicy of this.orgPolicies) { for (const orgPolicy of this.orgPolicies) {
@@ -75,8 +83,15 @@ export class PoliciesComponent implements OnInit {
} }
} }
} }
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
}
}); });
}); });
// Remove when removing deprecation warning
this.enterpriseUrl = this.environmentService.getEnterpriseUrl();
} }
async load() { async load() {
@@ -100,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;
}
} }

View File

@@ -13,7 +13,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PolicyType } from 'jslib-common/enums/policyType'; import { PolicyType } from 'jslib-common/enums/policyType';
@@ -46,7 +45,7 @@ export class PolicyEditComponent {
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService, private componentFactoryResolver: ComponentFactoryResolver, private toasterService: ToasterService, private componentFactoryResolver: ComponentFactoryResolver,
private cdr: ChangeDetectorRef, private logService: LogService) { private cdr: ChangeDetectorRef) {
} }
async ngAfterViewInit() { async ngAfterViewInit() {
@@ -87,8 +86,6 @@ export class PolicyEditComponent {
await this.formPromise; await this.formPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('editedPolicyId', this.i18nService.t(this.policy.name))); this.toasterService.popAsync('success', null, this.i18nService.t('editedPolicyId', this.i18nService.t(this.policy.name)));
this.onSavedPolicy.emit(); this.onSavedPolicy.emit();
} catch (e) { } catch {}
this.logService.error(e);
}
} }
} }

View File

@@ -9,7 +9,6 @@ import {
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { 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';
@@ -40,7 +39,7 @@ export class ResetPasswordComponent implements OnInit {
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private passwordGenerationService: PasswordGenerationService, private platformUtilsService: PlatformUtilsService, private passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService, private cryptoService: CryptoService, private logService: LogService) { } private policyService: PolicyService, private cryptoService: CryptoService) { }
async ngOnInit() { async ngOnInit() {
// Get Enforced Policy Options // Get Enforced Policy Options
@@ -144,9 +143,7 @@ export class ResetPasswordComponent implements OnInit {
await this.formPromise; await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('resetPasswordSuccess')); this.platformUtilsService.showToast('success', null, this.i18nService.t('resetPasswordSuccess'));
this.onPasswordReset.emit(); this.onPasswordReset.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
updatePasswordStrength() { updatePasswordStrength() {

View File

@@ -76,19 +76,36 @@
{{'permissions' | i18n}} {{'permissions' | i18n}}
</h3> </h3>
<div class="row"> <div class="row">
<div class="col-12 col-md-6 mt-4 mt-md-0"> <div class="col-6">
<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 class="col-12 col-md-6 mt-4 mt-md-0"> </div>
</div>
<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"

View File

@@ -11,7 +11,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CollectionData } from 'jslib-common/models/data/collectionData'; import { CollectionData } from 'jslib-common/models/data/collectionData';
@@ -49,44 +48,13 @@ 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;
} }
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService, private collectionService: CollectionService, private toasterService: ToasterService, private collectionService: CollectionService,
private platformUtilsService: PlatformUtilsService, private logService: LogService) { } private platformUtilsService: PlatformUtilsService) { }
async ngOnInit() { async ngOnInit() {
this.editMode = this.loading = this.organizationUserId != null; this.editMode = this.loading = this.organizationUserId != null;
@@ -112,9 +80,7 @@ export class UserAddEditComponent implements OnInit {
} }
}); });
} }
} catch (e) { } catch { }
this.logService.error(e);
}
} else { } else {
this.title = this.i18nService.t('inviteUser'); this.title = this.i18nService.t('inviteUser');
} }
@@ -141,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;
} }
@@ -183,9 +181,7 @@ export class UserAddEditComponent implements OnInit {
this.toasterService.popAsync('success', null, this.toasterService.popAsync('success', null,
this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name)); this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name));
this.onSavedUser.emit(); this.onSavedUser.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
async delete() { async delete() {
@@ -205,8 +201,7 @@ export class UserAddEditComponent implements OnInit {
await this.deletePromise; await this.deletePromise;
this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', this.name)); this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', this.name));
this.onDeletedUser.emit(); this.onDeletedUser.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
} }

View File

@@ -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}}

View File

@@ -9,7 +9,6 @@ import {
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from 'jslib-common/services/constants.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from 'jslib-common/abstractions/storage.service';
@Component({ @Component({
@@ -25,10 +24,8 @@ 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) { }
private logService: LogService) { }
async ngOnInit() { async ngOnInit() {
try { try {
@@ -38,9 +35,7 @@ export class UserConfirmComponent implements OnInit {
this.fingerprint = fingerprint.join('-'); this.fingerprint = fingerprint.join('-');
} }
} }
} catch (e) { } catch { }
this.logService.error(e);
}
this.loading = false; this.loading = false;
} }

View File

@@ -10,7 +10,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationUserUpdateGroupsRequest } from 'jslib-common/models/request/organizationUserUpdateGroupsRequest'; import { OrganizationUserUpdateGroupsRequest } from 'jslib-common/models/request/organizationUserUpdateGroupsRequest';
import { GroupResponse } from 'jslib-common/models/response/groupResponse'; import { GroupResponse } from 'jslib-common/models/response/groupResponse';
@@ -32,7 +31,7 @@ export class UserGroupsComponent implements OnInit {
formPromise: Promise<any>; formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService, private logService: LogService) { } private toasterService: ToasterService) { }
async ngOnInit() { async ngOnInit() {
const groupsResponse = await this.apiService.getGroups(this.organizationId); const groupsResponse = await this.apiService.getGroups(this.organizationId);
@@ -51,9 +50,7 @@ export class UserGroupsComponent implements OnInit {
} }
}); });
} }
} catch (e) { } catch { }
this.logService.error(e);
}
this.loading = false; this.loading = false;
} }
@@ -79,8 +76,6 @@ export class UserGroupsComponent implements OnInit {
await this.formPromise; await this.formPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('editedGroupsForUser', this.name)); this.toasterService.popAsync('success', null, this.i18nService.t('editedGroupsForUser', this.name));
this.onSavedUser.emit(); this.onSavedUser.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
} }

View File

@@ -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) { if (this.data != null) {
this.loadData();
}
}
loadData() {
this.data.patchValue(this.policyResponse.data ?? {}); this.data.patchValue(this.policyResponse.data ?? {});
} }
buildRequestData() {
if (this.data != null) {
return this.data.value;
}
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);
} }

View File

@@ -7,13 +7,13 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-md-6 mt-4 mt-md-0 form-group"> <div class="col-6 form-group">
<label for="minComplexity">{{'minComplexityScore' | i18n}}</label> <label for="minComplexity">{{'minComplexityScore' | i18n}}</label>
<select id="minComplexity" name="minComplexity" formControlName="minComplexity" class="form-control"> <select id="minComplexity" name="minComplexity" formControlName="minComplexity" class="form-control">
<option *ngFor="let o of passwordScores" [ngValue]="o.value">{{o.name}}</option> <option *ngFor="let o of passwordScores" [ngValue]="o.value">{{o.name}}</option>
</select> </select>
</div> </div>
<div class="col-12 col-md-6 mt-4 mt-md-0 form-group"> <div class="col-6 form-group">
<label for="minLength">{{'minLength' | i18n}}</label> <label for="minLength">{{'minLength' | i18n}}</label>
<input id="minLength" class="form-control" type="number" min="8" name="minLength" <input id="minLength" class="form-control" type="number" min="8" name="minLength"
formControlName="minLength"> formControlName="minLength">

View File

@@ -7,7 +7,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-md-6 mt-4 mt-md-0 form-group mb-0"> <div class="col-6 form-group mb-0">
<label for="defaultType">{{'defaultType' | i18n}}</label> <label for="defaultType">{{'defaultType' | i18n}}</label>
<select id="defaultType" name="defaultType" formControlName="defaultType" class="form-control"> <select id="defaultType" name="defaultType" formControlName="defaultType" class="form-control">
<option *ngFor="let o of defaultTypes" [ngValue]="o.value">{{o.name}}</option> <option *ngFor="let o of defaultTypes" [ngValue]="o.value">{{o.name}}</option>
@@ -16,19 +16,19 @@
</div> </div>
<h3 class="mt-4">{{'password' | i18n}}</h3> <h3 class="mt-4">{{'password' | i18n}}</h3>
<div class="row"> <div class="row">
<div class="col-12 col-md-6 mt-4 mt-md-0 form-group"> <div class="col-6 form-group">
<label for="minLength">{{'minLength' | i18n}}</label> <label for="minLength">{{'minLength' | i18n}}</label>
<input id="minLength" class="form-control" type="number" name="minLength" min="5" max="128" <input id="minLength" class="form-control" type="number" name="minLength" min="5" max="128"
formControlName="minLength"> formControlName="minLength">
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-md-6 mt-4 mt-md-0 form-group"> <div class="col-6 form-group">
<label for="minNumbers">{{'minNumbers' | i18n}}</label> <label for="minNumbers">{{'minNumbers' | i18n}}</label>
<input id="minNumbers" class="form-control" type="number" name="minNumbers" min="0" max="9" <input id="minNumbers" class="form-control" type="number" name="minNumbers" min="0" max="9"
formControlName="minNumbers"> formControlName="minNumbers">
</div> </div>
<div class="col-12 col-md-6 mt-4 mt-md-0 form-group"> <div class="col-6 form-group">
<label for="minSpecial">{{'minSpecial' | i18n}}</label> <label for="minSpecial">{{'minSpecial' | i18n}}</label>
<input id="minSpecial" class="form-control" type="number" name="minSpecial" min="0" max="9" <input id="minSpecial" class="form-control" type="number" name="minSpecial" min="0" max="9"
formControlName="minSpecial"> formControlName="minSpecial">
@@ -53,7 +53,7 @@
</div> </div>
<h3 class="mt-4">{{'passphrase' | i18n}}</h3> <h3 class="mt-4">{{'passphrase' | i18n}}</h3>
<div class="row"> <div class="row">
<div class="col-12 col-md-6 mt-4 mt-md-0 form-group"> <div class="col-6 form-group">
<label for="minNumberWords">{{'minimumNumberOfWords' | i18n}}</label> <label for="minNumberWords">{{'minimumNumberOfWords' | i18n}}</label>
<input id="minNumberWords" class="form-control" type="number" name="minNumberWords" min="3" max="20" <input id="minNumberWords" class="form-control" type="number" name="minNumberWords" min="3" max="20"
formControlName="minNumberWords"> formControlName="minNumberWords">

View File

@@ -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);

View File

@@ -7,7 +7,7 @@
</div> </div>
<form *ngIf="org && !loading" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate> <form *ngIf="org && !loading" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="row"> <div class="row">
<div class="col-12 col-md-6 mt-4 mt-md-0"> <div class="col-6">
<div class="form-group"> <div class="form-group">
<label for="name">{{'organizationName' | i18n}}</label> <label for="name">{{'organizationName' | i18n}}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="org.name" <input id="name" class="form-control" type="text" name="Name" [(ngModel)]="org.name"
@@ -29,7 +29,7 @@
[(ngModel)]="org.identifier"> [(ngModel)]="org.identifier">
</div> </div>
</div> </div>
<div class="col-12 col-md-6 mt-4 mt-md-0"> <div class="col-6">
<app-avatar data="{{org.name}}" dynamic="true" size="75" fontSize="35"></app-avatar> <app-avatar data="{{org.name}}" dynamic="true" size="75" fontSize="35"></app-avatar>
</div> </div>
</div> </div>

View File

@@ -12,7 +12,6 @@ import { ModalService } from 'jslib-angular/services/modal.service';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from 'jslib-common/abstractions/sync.service';
@@ -51,7 +50,7 @@ export class AccountComponent {
private apiService: ApiService, private i18nService: I18nService, private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService, private route: ActivatedRoute, private toasterService: ToasterService, private route: ActivatedRoute,
private syncService: SyncService, private platformUtilsService: PlatformUtilsService, private syncService: SyncService, private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService, private logService: LogService) { } private cryptoService: CryptoService) { }
async ngOnInit() { async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost(); this.selfHosted = this.platformUtilsService.isSelfHost();
@@ -60,9 +59,7 @@ export class AccountComponent {
try { try {
this.org = await this.apiService.getOrganization(this.organizationId); this.org = await this.apiService.getOrganization(this.organizationId);
this.canUseApi = this.org.useApi; this.canUseApi = this.org.useApi;
} catch (e) { } catch { }
this.logService.error(e);
}
}); });
this.loading = false; this.loading = false;
} }
@@ -87,9 +84,7 @@ export class AccountComponent {
}); });
await this.formPromise; await this.formPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('organizationUpdated')); this.toasterService.popAsync('success', null, this.i18nService.t('organizationUpdated'));
} catch (e) { } catch { }
this.logService.error(e);
}
} }
async submitTaxInfo() { async submitTaxInfo() {

View 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">&times;</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}} &times; {{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>

View 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;
}
}

View File

@@ -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}} &times; {{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}} &times;
{{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>

View File

@@ -1,70 +0,0 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { 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, private logService: LogService) { }
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 (e) {
this.logService.error(e);
}
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;
}
}

View File

@@ -5,7 +5,8 @@ import {
Output, Output,
} from '@angular/core'; } from '@angular/core';
import { LogService } from 'jslib-common/abstractions/log.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PlanType } from 'jslib-common/enums/planType'; import { PlanType } from 'jslib-common/enums/planType';
import { ProductType } from 'jslib-common/enums/productType'; import { ProductType } from 'jslib-common/enums/productType';
@@ -23,14 +24,12 @@ export class ChangePlanComponent {
defaultUpgradePlan: PlanType = PlanType.FamiliesAnnually; defaultUpgradePlan: PlanType = PlanType.FamiliesAnnually;
defaultUpgradeProduct: ProductType = ProductType.Families; defaultUpgradeProduct: ProductType = ProductType.Families;
constructor(private logService: LogService) { } constructor(private apiService: ApiService, private platformUtilsService: PlatformUtilsService) { }
async submit() { async submit() {
try { try {
this.onChanged.emit(); this.onChanged.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
cancel() { cancel() {

View File

@@ -6,7 +6,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordVerificationRequest } from 'jslib-common/models/request/passwordVerificationRequest'; import { PasswordVerificationRequest } from 'jslib-common/models/request/passwordVerificationRequest';
@@ -22,7 +21,7 @@ export class DeleteOrganizationComponent {
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService, private cryptoService: CryptoService, private toasterService: ToasterService, private cryptoService: CryptoService,
private router: Router, private logService: LogService) { } private router: Router) { }
async submit() { async submit() {
if (this.masterPassword == null || this.masterPassword === '') { if (this.masterPassword == null || this.masterPassword === '') {
@@ -39,8 +38,6 @@ export class DeleteOrganizationComponent {
this.toasterService.popAsync('success', this.i18nService.t('organizationDeleted'), this.toasterService.popAsync('success', this.i18nService.t('organizationDeleted'),
this.i18nService.t('organizationDeletedDesc')); this.i18nService.t('organizationDeletedDesc'));
this.router.navigate(['/']); this.router.navigate(['/']);
} catch (e) { } catch { }
this.logService.error(e);
}
} }
} }

View File

@@ -4,7 +4,7 @@
aria-hidden="true">&times;</span></button> aria-hidden="true">&times;</span></button>
<h3 class="card-body-header">{{'downloadLicense' | i18n}}</h3> <h3 class="card-body-header">{{'downloadLicense' | i18n}}</h3>
<div class="row"> <div class="row">
<div class="form-group col-12 col-md-6"> <div class="form-group col-6">
<div class="d-flex"> <div class="d-flex">
<label for="installationId">{{'enterInstallationId' | i18n}}</label> <label for="installationId">{{'enterInstallationId' | i18n}}</label>
<a class="ml-auto" target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}" <a class="ml-auto" target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"

View File

@@ -6,7 +6,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 { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
@Component({ @Component({
@@ -21,8 +20,7 @@ export class DownloadLicenseComponent {
installationId: string; installationId: string;
formPromise: Promise<any>; formPromise: Promise<any>;
constructor(private apiService: ApiService, private platformUtilsService: PlatformUtilsService, constructor(private apiService: ApiService, private platformUtilsService: PlatformUtilsService) { }
private logService: LogService) { }
async submit() { async submit() {
if (this.installationId == null || this.installationId === '') { if (this.installationId == null || this.installationId === '') {
@@ -35,9 +33,7 @@ export class DownloadLicenseComponent {
const licenseString = JSON.stringify(license, null, 2); const licenseString = JSON.stringify(license, null, 2);
this.platformUtilsService.saveFile(window, licenseString, null, 'bitwarden_organization_license.json'); this.platformUtilsService.saveFile(window, licenseString, null, 'bitwarden_organization_license.json');
this.onDownloaded.emit(); this.onDownloaded.emit();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
cancel() { cancel() {

View File

@@ -8,7 +8,6 @@ import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserBillingComponent } from '../../settings/user-billing.component'; import { UserBillingComponent } from '../../settings/user-billing.component';
@@ -19,9 +18,8 @@ import { UserBillingComponent } from '../../settings/user-billing.component';
}) })
export class OrganizationBillingComponent extends UserBillingComponent implements OnInit { export class OrganizationBillingComponent extends UserBillingComponent implements OnInit {
constructor(apiService: ApiService, i18nService: I18nService, toasterService: ToasterService, constructor(apiService: ApiService, i18nService: I18nService, toasterService: ToasterService,
private route: ActivatedRoute, platformUtilsService: PlatformUtilsService, private route: ActivatedRoute, platformUtilsService: PlatformUtilsService) {
logService: LogService) { super(apiService, i18nService, toasterService, platformUtilsService);
super(apiService, i18nService, toasterService, platformUtilsService, logService);
} }
async ngOnInit() { async ngOnInit() {

View File

@@ -23,8 +23,20 @@
<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>
<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 class="row" *ngIf="!selfHosted">
<div class="col-4"> <div class="col-4">
<dl> <dl>
<dt>{{'billingPlan' | i18n}}</dt> <dt>{{'billingPlan' | i18n}}</dt>
@@ -34,25 +46,22 @@
<dd> <dd>
<span class="text-capitalize">{{subscription.status || '-'}}</span> <span class="text-capitalize">{{subscription.status || '-'}}</span>
<span class="badge badge-warning" <span class="badge badge-warning"
*ngIf="subscriptionMarkedForCancel">{{'pendingCancellation' | *ngIf="subscriptionMarkedForCancel">{{'pendingCancellation' | i18n}}</span>
i18n}}</span>
</dd> </dd>
<dt>{{'nextCharge' | i18n}}</dt> <dt>{{'nextCharge' | i18n}}</dt>
<dd>{{nextInvoice ? ((nextInvoice.date | date: 'mediumDate') + ', ' + (nextInvoice.amount | <dd>{{nextInvoice ? ((nextInvoice.date | date: 'mediumDate') + ', ' + (nextInvoice.amount | currency:'$'))
currency:'$'))
: '-'}} : '-'}}
</dd> </dd>
</ng-container> </ng-container>
</dl> </dl>
</div> </div>
<div class="col-8"> <div class="col-8" *ngIf="subscription">
<strong class="d-block mb-1">{{'details' | i18n}}</strong> <strong class="d-block mb-1">{{'details' | i18n}}</strong>
<table class="table"> <table class="table">
<tbody> <tbody>
<tr *ngFor="let i of subscription.items"> <tr *ngFor="let i of subscription.items">
<td> <td>
{{i.name}} {{i.quantity > 1 ? '&times;' + i.quantity : ''}} @ {{i.amount | {{i.name}} {{i.quantity > 1 ? '&times;' + i.quantity : ''}} @ {{i.amount | currency:'$'}}
currency:'$'}}
</td> </td>
<td> <td>
{{(i.quantity * i.amount) | currency:'$'}} /{{i.interval | i18n}} {{(i.quantity * i.amount) | currency:'$'}} /{{i.interval | i18n}}
@@ -61,22 +70,63 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<ng-container *ngIf="userOrg?.providerId != null"> </div>
<div class="col-sm"> <ng-container *ngIf="selfHosted">
<dl> <div>
<dt>{{'provider' | i18n}}</dt> <button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
<dd>{{'yourProviderIs' | i18n : userOrg.providerName}}</dd> {{'updateLicense' | i18n}}
</dl> </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">&times;</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>
</ng-container> </ng-container>
<ng-container *ngIf="!selfHosted">
<div class="d-flex">
<button type="button" class="btn btn-outline-secondary" (click)="changePlan()" *ngIf="!showChangePlan">
{{'changeBillingPlan' | i18n}}
</button>
<button type="button" class="btn btn-outline-secondary ml-1" (click)="downloadLicense()"
*ngIf="canDownloadLicense" [disabled]="showDownloadLicense">
{{'downloadLicense' | i18n}}
</button>
<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> </div>
<h2 class="spaced-header">{{'manageSubscription' | i18n}}</h2> <app-change-plan [organizationId]="organizationId" (onChanged)="closeChangePlan(true)"
<p class="mb-4">{{subscriptionDesc}}</p> (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>
@@ -102,59 +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>
<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> </div>
{{'yourProviderIs' | i18n : userOrg.providerName}}
</ng-container> </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">&times;</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>
</ng-container> </ng-container>

View File

@@ -10,11 +10,10 @@ import { OrganizationSubscriptionResponse } from 'jslib-common/models/response/o
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { 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({
@@ -27,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;
@@ -44,7 +42,7 @@ export class OrganizationSubscriptionComponent implements OnInit {
constructor(private apiService: ApiService, private platformUtilsService: PlatformUtilsService, constructor(private apiService: ApiService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService, private toasterService: ToasterService, private i18nService: I18nService, private toasterService: ToasterService,
private messagingService: MessagingService, private route: ActivatedRoute, private messagingService: MessagingService, private route: ActivatedRoute,
private userService: UserService, private logService: LogService) { private userService: UserService) {
this.selfHosted = platformUtilsService.isSelfHost(); this.selfHosted = platformUtilsService.isSelfHost();
} }
@@ -83,9 +81,7 @@ export class OrganizationSubscriptionComponent implements OnInit {
await this.reinstatePromise; await this.reinstatePromise;
this.toasterService.popAsync('success', null, this.i18nService.t('reinstated')); this.toasterService.popAsync('success', null, this.i18nService.t('reinstated'));
this.load(); this.load();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
async cancel() { async cancel() {
@@ -104,9 +100,7 @@ export class OrganizationSubscriptionComponent implements OnInit {
await this.cancelPromise; await this.cancelPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('canceledSubscription')); this.toasterService.popAsync('success', null, this.i18nService.t('canceledSubscription'));
this.load(); this.load();
} catch (e) { } catch { }
this.logService.error(e);
}
} }
async changePlan() { async changePlan() {
@@ -148,9 +142,17 @@ export class OrganizationSubscriptionComponent implements OnInit {
} }
} }
subscriptionAdjusted() { adjustSeats(add: boolean) {
this.adjustSeatsAdd = add;
this.showAdjustSeats = true;
}
closeSeats(load: boolean) {
this.showAdjustSeats = false;
if (load) {
this.load(); this.load();
} }
}
adjustStorage(add: boolean) { adjustStorage(add: boolean) {
this.adjustStorageAdd = add; this.adjustStorageAdd = add;
@@ -203,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;
} }
@@ -219,14 +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.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());
}
}
} }

View File

@@ -1,6 +1,6 @@
<div class="container page-content"> <div class="container page-content">
<div class="row"> <div class="row">
<div class="col-md-4 col-lg-3 mt-4 mt-md-0"> <div class="col-3">
<div class="card"> <div class="card">
<div class="card-header">{{'settings' | i18n}}</div> <div class="card-header">{{'settings' | i18n}}</div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
@@ -19,7 +19,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-8 col-lg-9 mt-4 mt-md-0"> <div class="col-9">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
</div> </div>

View File

@@ -5,12 +5,12 @@ import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service'; import { EventService } from 'jslib-common/abstractions/event.service';
import { ExportService } from 'jslib-common/abstractions/export.service'; import { ExportService } from 'jslib-common/abstractions/export.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { 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',
@@ -18,23 +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) {
logService: LogService) { super(cryptoService, i18nService, platformUtilsService, exportService, eventService);
super(cryptoService, i18nService, platformUtilsService, exportService, eventService, policyService,
logService);
} }
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);
} }

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