mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 12:37:39 +00:00
chore: 🚀 Migrate monorepo from Yarn to pnpm + Turbo integration + Configuration cleanup (#10165)
This PR modernizes the ZITADEL monorepo build system by migrating from Yarn to pnpm, introducing Turbo for improved build orchestration, and cleaning up configuration inconsistencies across all apps and packages. ### 🎯 Key Improvements #### 📦 **Package Manager Migration (Yarn → pnpm)** - **Performance**: Faster installs with pnpm's efficient symlink-based node_modules structure - **Disk space**: Significant reduction in disk usage through content-addressable storage - **Lockfile**: More reliable dependency resolution with pnpm-lock.yaml - **Workspace support**: Better monorepo dependency management #### ⚡ **Turbo Integration** - **Build orchestration**: Dependency-aware task execution across the monorepo - **Intelligent caching**: Dramatically faster builds on CI/CD and local development - **Parallel execution**: Optimal task scheduling based on dependency graphs - **Vercel optimization**: Enhanced build performance and caching on Vercel deployments #### 🧹 **Configuration Cleanup & Unification** - **Removed config packages**: Eliminated `@zitadel/*-config` packages and inlined configurations - **Simplified dependencies**: Reduced complexity in package.json files across all apps - **Consistent tooling**: Unified prettier, ESLint, and TypeScript configurations - **Standalone support**: Improved prepare-standalone.js script for subtree deployments ### 📋 Detailed Changes #### **🔧 Build System & Dependencies** - ✅ Updated all package.json scripts to use `pnpm` instead of `yarn` - ✅ Replaced `yarn.lock` with pnpm-lock.yaml and regenerated dependencies - ✅ Added Turbo configuration (turbo.json) to root and individual packages - ✅ Configured proper dependency chains: `@zitadel/proto#generate` → `@zitadel/client#build` → `console#build` - ✅ Added missing `@bufbuild/protobuf` dependency to console app for TypeScript compilation #### **🚀 CI/CD & Workflows** - ✅ Updated all GitHub Actions workflows to use `pnpm/action-setup@v4` - ✅ Migrated build processes to use Turbo with directory-based filters (`--filter=./console`) - ✅ **New**: Added `docs.yml` workflow for building documentation locally (helpful for contributors without Vercel access) - ✅ Fixed dependency resolution issues in lint workflows - ✅ Ensured proto generation always runs before builds and linting #### **📚 Documentation & Proto Generation** - ✅ **Robust plugin management**: Enhanced plugin-download.sh with retry logic and error handling - ✅ **Vercel compatibility**: Fixed protoc-gen-connect-openapi plugin availability in Vercel builds - ✅ **API docs generation**: Resolved Docusaurus build errors with OpenAPI plugin configuration - ✅ **Type safety**: Improved TypeScript type extraction patterns in Angular components #### **🛠️ Developer Experience** - ✅ Updated all README files to reference pnpm commands - ✅ Improved Makefile targets to use Turbo for consistent builds - ✅ Enhanced standalone build process for login app subtree deployments - ✅ Added debug utilities for troubleshooting build issues #### **🗂️ File Structure & Cleanup** - ✅ Removed obsolete configuration packages and their references - ✅ Cleaned up Docker files to remove non-existent package copies - ✅ Updated workspace references and import paths - ✅ Streamlined turbo.json configurations across all packages ### 🎉 Benefits 1. **⚡ Faster Builds**: Turbo's caching and parallel execution significantly reduce build times 2. **🔄 Better Caching**: Improved cache hits on Vercel and CI/CD environments 3. **🛠️ Simplified Maintenance**: Unified tooling and configuration management 4. **📈 Developer Productivity**: Faster local development with optimized dependency resolution 5. **🚀 Enhanced CI/CD**: More reliable and faster automated builds and deployments 6. **📖 Better Documentation**: Comprehensive build documentation and troubleshooting guides ### 🧪 Testing - ✅ All apps build successfully with new pnpm + Turbo setup - ✅ Proto generation works correctly across console, login, and docs - ✅ GitHub Actions workflows pass with new configuration - ✅ Vercel deployments work with enhanced plugin management - ✅ Local development workflow verified and documented This migration sets a solid foundation for future development while maintaining backward compatibility and improving the overall developer experience. --------- Co-authored-by: Elio Bischof <elio@zitadel.com>
This commit is contained in:
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -33,6 +33,12 @@ jobs:
|
|||||||
node_version: "20"
|
node_version: "20"
|
||||||
buf_version: "latest"
|
buf_version: "latest"
|
||||||
|
|
||||||
|
docs:
|
||||||
|
uses: ./.github/workflows/docs.yml
|
||||||
|
with:
|
||||||
|
node_version: "20"
|
||||||
|
buf_version: "latest"
|
||||||
|
|
||||||
version:
|
version:
|
||||||
uses: ./.github/workflows/version.yml
|
uses: ./.github/workflows/version.yml
|
||||||
with:
|
with:
|
||||||
@@ -87,7 +93,7 @@ jobs:
|
|||||||
actions: write
|
actions: write
|
||||||
id-token: write
|
id-token: write
|
||||||
with:
|
with:
|
||||||
ignore-run-cache: ${{ github.event_name == 'workflow_dispatch' || fromJSON(github.run_attempt) > 1 }}
|
ignore-run-cache: ${{ github.event_name == 'workflow_dispatch' || fromJSON(github.run_attempt) > 1 }}
|
||||||
node_version: "20"
|
node_version: "20"
|
||||||
secrets:
|
secrets:
|
||||||
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
||||||
@@ -126,7 +132,16 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
needs:
|
needs:
|
||||||
[version, core-unit-test, core-integration-test, lint, container, login-container, login-quality, e2e]
|
[
|
||||||
|
version,
|
||||||
|
core-unit-test,
|
||||||
|
core-integration-test,
|
||||||
|
lint,
|
||||||
|
container,
|
||||||
|
login-container,
|
||||||
|
login-quality,
|
||||||
|
e2e,
|
||||||
|
]
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||||
secrets:
|
secrets:
|
||||||
GCR_JSON_KEY_BASE64: ${{ secrets.GCR_JSON_KEY_BASE64 }}
|
GCR_JSON_KEY_BASE64: ${{ secrets.GCR_JSON_KEY_BASE64 }}
|
||||||
|
137
.github/workflows/compile.yml
vendored
137
.github/workflows/compile.yml
vendored
@@ -33,96 +33,59 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
goos: [linux, darwin, windows]
|
goos: [linux, darwin, windows]
|
||||||
goarch: [amd64, arm64]
|
goarch: [amd64, arm64]
|
||||||
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
-
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
timeout-minutes: 1
|
|
||||||
name: restore console
|
|
||||||
with:
|
|
||||||
path: ${{ inputs.console_cache_path }}
|
|
||||||
key: ${{ inputs.console_cache_key }}
|
|
||||||
fail-on-cache-miss: true
|
|
||||||
-
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
timeout-minutes: 1
|
|
||||||
name: restore core
|
|
||||||
with:
|
|
||||||
path: ${{ inputs.core_cache_path }}
|
|
||||||
key: ${{ inputs.core_cache_key }}
|
|
||||||
fail-on-cache-miss: true
|
|
||||||
-
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version-file: 'go.mod'
|
|
||||||
-
|
|
||||||
name: compile
|
|
||||||
timeout-minutes: 5
|
|
||||||
run: |
|
|
||||||
GOOS="${{matrix.goos}}" \
|
|
||||||
GOARCH="${{matrix.goarch}}" \
|
|
||||||
VERSION="${{ inputs.version }}" \
|
|
||||||
COMMIT_SHA="${{ github.sha }}" \
|
|
||||||
make compile_pipeline
|
|
||||||
-
|
|
||||||
name: create folder
|
|
||||||
run: |
|
|
||||||
mkdir zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
|
|
||||||
mv zitadel zitadel-${{ matrix.goos }}-${{ matrix.goarch }}/
|
|
||||||
cp LICENSE zitadel-${{ matrix.goos }}-${{ matrix.goarch }}/
|
|
||||||
cp README.md zitadel-${{ matrix.goos }}-${{ matrix.goarch }}/
|
|
||||||
tar -czvf zitadel-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
|
|
||||||
-
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
|
|
||||||
path: zitadel-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
|
||||||
|
|
||||||
login:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
steps:
|
||||||
-
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
- uses: actions/cache/restore@v4
|
||||||
-
|
timeout-minutes: 1
|
||||||
uses: depot/setup-action@v1
|
name: restore console
|
||||||
-
|
with:
|
||||||
run: make login_standalone_out
|
path: ${{ inputs.console_cache_path }}
|
||||||
env:
|
key: ${{ inputs.console_cache_key }}
|
||||||
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
fail-on-cache-miss: true
|
||||||
LOGIN_BAKE_CLI: depot bake
|
- uses: actions/cache/restore@v4
|
||||||
DEPOT_PROJECT_ID: w47wkxzdtw
|
timeout-minutes: 1
|
||||||
NODE_VERSION: ${{ inputs.node_version }}
|
name: restore core
|
||||||
-
|
with:
|
||||||
name: move files
|
path: ${{ inputs.core_cache_path }}
|
||||||
run: |
|
key: ${{ inputs.core_cache_key }}
|
||||||
cp login/LICENSE login/apps/login/standalone/
|
fail-on-cache-miss: true
|
||||||
cp login/README.md login/apps/login/standalone/
|
- uses: actions/setup-go@v5
|
||||||
tar -czvf login.tar.gz -C login/apps/login/standalone .
|
with:
|
||||||
-
|
go-version-file: "go.mod"
|
||||||
uses: actions/upload-artifact@v4
|
- name: compile
|
||||||
with:
|
timeout-minutes: 5
|
||||||
name: login
|
run: |
|
||||||
path: login.tar.gz
|
GOOS="${{matrix.goos}}" \
|
||||||
|
GOARCH="${{matrix.goarch}}" \
|
||||||
|
VERSION="${{ inputs.version }}" \
|
||||||
|
COMMIT_SHA="${{ github.sha }}" \
|
||||||
|
make compile_pipeline
|
||||||
|
- name: create folder
|
||||||
|
run: |
|
||||||
|
mkdir zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
|
mv zitadel zitadel-${{ matrix.goos }}-${{ matrix.goarch }}/
|
||||||
|
cp LICENSE zitadel-${{ matrix.goos }}-${{ matrix.goarch }}/
|
||||||
|
cp README.md zitadel-${{ matrix.goos }}-${{ matrix.goarch }}/
|
||||||
|
tar -czvf zitadel-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: zitadel-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
|
path: zitadel-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
||||||
|
|
||||||
checksums:
|
checksums:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [executable, login]
|
needs: [executable]
|
||||||
steps:
|
steps:
|
||||||
-
|
- uses: actions/download-artifact@v4
|
||||||
uses: actions/download-artifact@v4
|
with:
|
||||||
with:
|
path: executables
|
||||||
path: executables
|
- name: move files one folder up
|
||||||
-
|
run: mv */*.tar.gz . && find . -type d -empty -delete
|
||||||
name: move files one folder up
|
working-directory: executables
|
||||||
run: mv */*.tar.gz . && find . -type d -empty -delete
|
- run: sha256sum * > checksums.txt
|
||||||
working-directory: executables
|
working-directory: executables
|
||||||
-
|
- uses: actions/upload-artifact@v4
|
||||||
run: sha256sum * > checksums.txt
|
with:
|
||||||
working-directory: executables
|
name: checksums.txt
|
||||||
-
|
path: executables/checksums.txt
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: checksums.txt
|
|
||||||
path: executables/checksums.txt
|
|
||||||
|
73
.github/workflows/console.yml
vendored
73
.github/workflows/console.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: Build console
|
name: Build console
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
node_version:
|
node_version:
|
||||||
@@ -13,7 +13,7 @@ on:
|
|||||||
cache_key:
|
cache_key:
|
||||||
value: ${{ jobs.build.outputs.cache_key }}
|
value: ${{ jobs.build.outputs.cache_key }}
|
||||||
cache_path:
|
cache_path:
|
||||||
value: ${{ jobs.build.outputs.cache_path }}
|
value: ${{ jobs.build.outputs.cache_path }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
cache_path: console/dist/console
|
cache_path: console/dist/console
|
||||||
@@ -25,38 +25,37 @@ jobs:
|
|||||||
cache_path: ${{ env.cache_path }}
|
cache_path: ${{ env.cache_path }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
- uses: actions/cache/restore@v4
|
||||||
-
|
timeout-minutes: 1
|
||||||
uses: actions/cache/restore@v4
|
continue-on-error: true
|
||||||
timeout-minutes: 1
|
id: cache
|
||||||
continue-on-error: true
|
with:
|
||||||
id: cache
|
key: console-${{ hashFiles('console', 'proto', '!console/dist') }}
|
||||||
with:
|
restore-keys: |
|
||||||
key: console-${{ hashFiles('console', 'proto', '!console/dist') }}
|
console-
|
||||||
restore-keys: |
|
path: ${{ env.cache_path }}
|
||||||
console-
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
path: ${{ env.cache_path }}
|
uses: bufbuild/buf-setup-action@v1
|
||||||
-
|
with:
|
||||||
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
github_token: ${{ github.token }}
|
||||||
uses: bufbuild/buf-setup-action@v1
|
version: ${{ inputs.buf_version }}
|
||||||
with:
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
github_token: ${{ github.token }}
|
uses: pnpm/action-setup@v4
|
||||||
version: ${{ inputs.buf_version }}
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
-
|
uses: actions/setup-node@v4
|
||||||
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
with:
|
||||||
uses: actions/setup-node@v4
|
node-version: ${{ inputs.node_version }}
|
||||||
with:
|
cache: "pnpm"
|
||||||
node-version: ${{ inputs.node_version }}
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
cache: 'yarn'
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
cache-dependency-path: console/yarn.lock
|
name: Install dependencies
|
||||||
-
|
run: pnpm install
|
||||||
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
run: make console_build
|
name: Build console with Turbo
|
||||||
-
|
run: pnpm turbo build --filter=./console
|
||||||
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
uses: actions/cache/save@v4
|
uses: actions/cache/save@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.cache_path }}
|
path: ${{ env.cache_path }}
|
||||||
key: ${{ steps.cache.outputs.cache-primary-key }}
|
key: ${{ steps.cache.outputs.cache-primary-key }}
|
||||||
|
|
||||||
|
61
.github/workflows/docs.yml
vendored
Normal file
61
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Build docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
node_version:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
buf_version:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
outputs:
|
||||||
|
cache_key:
|
||||||
|
value: ${{ jobs.build.outputs.cache_key }}
|
||||||
|
cache_path:
|
||||||
|
value: ${{ jobs.build.outputs.cache_path }}
|
||||||
|
|
||||||
|
env:
|
||||||
|
cache_path: docs/build
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
outputs:
|
||||||
|
cache_key: ${{ steps.cache.outputs.cache-primary-key }}
|
||||||
|
cache_path: ${{ env.cache_path }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache/restore@v4
|
||||||
|
timeout-minutes: 1
|
||||||
|
continue-on-error: true
|
||||||
|
id: cache
|
||||||
|
with:
|
||||||
|
key: docs-${{ hashFiles('docs', 'proto', '!docs/build', '!docs/node_modules', '!docs/protoc-gen-connect-openapi') }}
|
||||||
|
restore-keys: |
|
||||||
|
docs-
|
||||||
|
path: ${{ env.cache_path }}
|
||||||
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
|
uses: bufbuild/buf-setup-action@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ github.token }}
|
||||||
|
version: ${{ inputs.buf_version }}
|
||||||
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ inputs.node_version }}
|
||||||
|
cache: "pnpm"
|
||||||
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
|
name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
|
name: Build docs with Turbo
|
||||||
|
run: pnpm turbo build --filter=./docs
|
||||||
|
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.cache_path }}
|
||||||
|
key: ${{ steps.cache.outputs.cache-primary-key }}
|
37
.github/workflows/e2e.yml
vendored
37
.github/workflows/e2e.yml
vendored
@@ -12,44 +12,47 @@ jobs:
|
|||||||
browser: [firefox, chrome]
|
browser: [firefox, chrome]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Checkout Repository
|
||||||
name: Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
-
|
- uses: actions/download-artifact@v4
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
path: .artifacts
|
path: .artifacts
|
||||||
name: zitadel-linux-amd64
|
name: zitadel-linux-amd64
|
||||||
-
|
- name: Unpack executable
|
||||||
name: Unpack executable
|
|
||||||
run: |
|
run: |
|
||||||
tar -xvf .artifacts/zitadel-linux-amd64.tar.gz
|
tar -xvf .artifacts/zitadel-linux-amd64.tar.gz
|
||||||
mv zitadel-linux-amd64/zitadel ./zitadel
|
mv zitadel-linux-amd64/zitadel ./zitadel
|
||||||
-
|
- name: Set up QEMU
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
-
|
- name: Set up Docker Buildx
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
-
|
- uses: pnpm/action-setup@v4
|
||||||
name: Start DB and ZITADEL
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: "pnpm"
|
||||||
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
- name: Install Cypress binary
|
||||||
|
run: cd ./e2e && pnpm exec cypress install
|
||||||
|
- name: Start DB and ZITADEL
|
||||||
run: |
|
run: |
|
||||||
cd ./e2e
|
cd ./e2e
|
||||||
ZITADEL_IMAGE=zitadel:local docker compose up --detach --wait
|
ZITADEL_IMAGE=zitadel:local docker compose up --detach --wait
|
||||||
-
|
- name: Cypress run
|
||||||
name: Cypress run
|
|
||||||
uses: cypress-io/github-action@v6
|
uses: cypress-io/github-action@v6
|
||||||
env:
|
env:
|
||||||
CYPRESS_BASE_URL: http://localhost:8080/ui/console
|
CYPRESS_BASE_URL: http://localhost:8080/ui/console
|
||||||
CYPRESS_WEBHOOK_HANDLER_HOST: host.docker.internal
|
CYPRESS_WEBHOOK_HANDLER_HOST: host.docker.internal
|
||||||
CYPRESS_DATABASE_CONNECTION_URL: 'postgresql://root@localhost:26257/zitadel'
|
CYPRESS_DATABASE_CONNECTION_URL: "postgresql://root@localhost:26257/zitadel"
|
||||||
CYPRESS_BACKEND_URL: http://localhost:8080
|
CYPRESS_BACKEND_URL: http://localhost:8080
|
||||||
with:
|
with:
|
||||||
working-directory: e2e
|
working-directory: e2e
|
||||||
browser: ${{ matrix.browser }}
|
browser: ${{ matrix.browser }}
|
||||||
config-file: cypress.config.ts
|
config-file: cypress.config.ts
|
||||||
-
|
install: false
|
||||||
uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: production-tests-${{ matrix.browser }}
|
name: production-tests-${{ matrix.browser }}
|
||||||
|
88
.github/workflows/lint.yml
vendored
88
.github/workflows/lint.yml
vendored
@@ -20,7 +20,6 @@ on:
|
|||||||
type: string
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
lint-skip:
|
lint-skip:
|
||||||
name: lint skip
|
name: lint skip
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -36,64 +35,53 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
steps:
|
steps:
|
||||||
-
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
- uses: bufbuild/buf-setup-action@v1
|
||||||
-
|
with:
|
||||||
uses: bufbuild/buf-setup-action@v1
|
version: ${{ inputs.buf_version }}
|
||||||
with:
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
version: ${{ inputs.buf_version }}
|
- name: lint
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
uses: bufbuild/buf-lint-action@v1
|
||||||
-
|
- uses: bufbuild/buf-breaking-action@v1
|
||||||
name: lint
|
with:
|
||||||
uses: bufbuild/buf-lint-action@v1
|
against: "https://github.com/${{ github.repository }}.git#branch=${{ github.base_ref }}"
|
||||||
-
|
|
||||||
uses: bufbuild/buf-breaking-action@v1
|
|
||||||
with:
|
|
||||||
against: "https://github.com/${{ github.repository }}.git#branch=${{ github.base_ref }}"
|
|
||||||
|
|
||||||
console:
|
console:
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
name: console
|
name: console
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Checkout
|
||||||
name: Checkout
|
uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
- uses: pnpm/action-setup@v4
|
||||||
-
|
- uses: actions/setup-node@v4
|
||||||
uses: actions/setup-node@v4
|
with:
|
||||||
with:
|
node-version: ${{ inputs.node_version }}
|
||||||
node-version: ${{ inputs.node_version }}
|
cache: "pnpm"
|
||||||
cache: 'yarn'
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
cache-dependency-path: console/yarn.lock
|
- run: pnpm install --filter=console
|
||||||
-
|
- name: lint
|
||||||
run: cd console && yarn install
|
run: make console_lint
|
||||||
-
|
|
||||||
name: lint
|
|
||||||
run: make console_lint
|
|
||||||
|
|
||||||
core:
|
core:
|
||||||
name: core
|
name: core
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Checkout
|
||||||
name: Checkout
|
uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
- uses: actions/setup-go@v5
|
||||||
-
|
with:
|
||||||
uses: actions/setup-go@v5
|
go-version-file: "go.mod"
|
||||||
with:
|
- uses: actions/cache/restore@v4
|
||||||
go-version-file: 'go.mod'
|
timeout-minutes: 1
|
||||||
-
|
name: restore core
|
||||||
uses: actions/cache/restore@v4
|
with:
|
||||||
timeout-minutes: 1
|
path: ${{ inputs.core_cache_path }}
|
||||||
name: restore core
|
key: ${{ inputs.core_cache_key }}
|
||||||
with:
|
fail-on-cache-miss: true
|
||||||
path: ${{ inputs.core_cache_path }}
|
- uses: golangci/golangci-lint-action@v6
|
||||||
key: ${{ inputs.core_cache_key }}
|
with:
|
||||||
fail-on-cache-miss: true
|
version: ${{ inputs.go_lint_version }}
|
||||||
-
|
github-token: ${{ github.token }}
|
||||||
uses: golangci/golangci-lint-action@v6
|
only-new-issues: true
|
||||||
with:
|
|
||||||
version: ${{ inputs.go_lint_version }}
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
only-new-issues: true
|
|
||||||
|
13
.github/workflows/login-quality.yml
vendored
13
.github/workflows/login-quality.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
ignore-run-cache:
|
ignore-run-cache:
|
||||||
description: 'Ignore run caches'
|
description: "Ignore run caches"
|
||||||
type: boolean
|
type: boolean
|
||||||
required: true
|
required: true
|
||||||
node_version:
|
node_version:
|
||||||
@@ -44,7 +44,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
tar -xvf .artifacts/zitadel-linux-amd64.tar.gz
|
tar -xvf .artifacts/zitadel-linux-amd64.tar.gz
|
||||||
mv zitadel-linux-amd64/zitadel ./zitadel
|
mv zitadel-linux-amd64/zitadel ./zitadel
|
||||||
- run: make login_quality
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ inputs.node_version }}
|
||||||
|
cache: "pnpm"
|
||||||
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
- name: Run login quality checks with Turbo
|
||||||
|
run: pnpm turbo test:unit --filter=@zitadel/login
|
||||||
env:
|
env:
|
||||||
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
||||||
LOGIN_BAKE_CLI: depot bake
|
LOGIN_BAKE_CLI: depot bake
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -84,7 +84,11 @@ go.work.sum
|
|||||||
.netlify
|
.netlify
|
||||||
|
|
||||||
load-test/node_modules
|
load-test/node_modules
|
||||||
load-test/yarn-error.log
|
load-test/pnpm-debug.log
|
||||||
load-test/dist
|
load-test/dist
|
||||||
load-test/output/*
|
load-test/output/*
|
||||||
.vercel
|
.vercel
|
||||||
|
|
||||||
|
# Turbo
|
||||||
|
.turbo/
|
||||||
|
**/.turbo/
|
||||||
|
2
.npmrc
Normal file
2
.npmrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
auto-install-peers = true
|
||||||
|
ignore-scripts = "postman-code-generators"
|
324
CONTRIBUTING.md
324
CONTRIBUTING.md
@@ -30,6 +30,67 @@ Help shaping the future of ZITADEL:
|
|||||||
|
|
||||||
Follow [@zitadel](https://twitter.com/zitadel) on twitter
|
Follow [@zitadel](https://twitter.com/zitadel) on twitter
|
||||||
|
|
||||||
|
## Quick Start for Contributors
|
||||||
|
|
||||||
|
ZITADEL uses **pnpm** as package manager and **Turbo** for build orchestration across the monorepo. Here are the most common commands you'll need:
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Node version v20.x](https://nodejs.org/en/download/)
|
||||||
|
- [pnpm version 9.x](https://pnpm.io/installation)
|
||||||
|
- [Docker](https://docs.docker.com/engine/install/) for running databases and services
|
||||||
|
|
||||||
|
### Common Development Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install all dependencies across the monorepo
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Start the backend database and ZITADEL server
|
||||||
|
docker compose --file ./e2e/docker-compose.yaml up --detach zitadel
|
||||||
|
|
||||||
|
# Develop the Console (Angular app)
|
||||||
|
pnpm turbo dev --filter=console
|
||||||
|
|
||||||
|
# Develop the Login UI (Next.js app)
|
||||||
|
pnpm turbo dev --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Develop the Documentation (Docusaurus)
|
||||||
|
pnpm turbo dev --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Build everything
|
||||||
|
pnpm turbo build
|
||||||
|
|
||||||
|
# Lint and fix code across all packages
|
||||||
|
pnpm turbo lint
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
pnpm turbo test
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
docker compose --file ./e2e/docker-compose.yaml down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monorepo Structure
|
||||||
|
|
||||||
|
The repository is organized as follows:
|
||||||
|
|
||||||
|
| Package | Description | Technology | Development Command |
|
||||||
|
| ----------------- | --------------------------- | ------------------- | --------------------------------------------- |
|
||||||
|
| `console` | Management UI (post-login) | Angular, TypeScript | `pnpm turbo dev --filter=console` |
|
||||||
|
| `@zitadel/login` | Authentication UI | Next.js, React | `pnpm turbo dev --filter=@zitadel/login` |
|
||||||
|
| `zitadel-docs` | Documentation site | Docusaurus | `pnpm turbo dev --filter=zitadel-docs` |
|
||||||
|
| `@zitadel/client` | TypeScript client library | TypeScript | `pnpm turbo build --filter=@zitadel/client` |
|
||||||
|
| `@zitadel/proto` | Protocol buffer definitions | Protobuf | `pnpm turbo generate --filter=@zitadel/proto` |
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
1. **Start the backend**: `docker compose --file ./e2e/docker-compose.yaml up --detach zitadel`
|
||||||
|
2. **Choose your focus**: Run one of the development commands above
|
||||||
|
3. **Make changes**: Edit code with live reload feedback
|
||||||
|
4. **Test your changes**: Use the appropriate test commands
|
||||||
|
5. **Cleanup**: `docker compose --file ./e2e/docker-compose.yaml down`
|
||||||
|
|
||||||
## How to contribute
|
## How to contribute
|
||||||
|
|
||||||
We strongly recommend to [talk to us](https://zitadel.com/contact) before you start contributing to streamline our and your work.
|
We strongly recommend to [talk to us](https://zitadel.com/contact) before you start contributing to streamline our and your work.
|
||||||
@@ -108,13 +169,15 @@ Please make sure you cover your changes with tests before marking a Pull Request
|
|||||||
|
|
||||||
The code consists of the following parts:
|
The code consists of the following parts:
|
||||||
|
|
||||||
| name | description | language | where to find |
|
| name | description | language | where to find |
|
||||||
| --------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------- |
|
| --------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
|
||||||
| backend | Service that serves the grpc(-web) and RESTful API | [go](https://go.dev) | [API implementation](./internal/api/grpc) |
|
| backend | Service that serves the grpc(-web) and RESTful API | [go](https://go.dev) | [API implementation](./internal/api/grpc) |
|
||||||
| console | Frontend the user interacts with after log in | [Angular](https://angular.io), [Typescript](https://www.typescriptlang.org) | [./console](./console) |
|
| console | Frontend the user interacts with after log in | [Angular](https://angular.io), [Typescript](https://www.typescriptlang.org) | [./console](./console) |
|
||||||
| login | Server side rendered frontend the user interacts with during login | [go](https://go.dev), [go templates](https://pkg.go.dev/html/template) | [./internal/api/ui/login](./internal/api/ui/login) |
|
| login | Modern authentication UI built with Next.js | [Next.js](https://nextjs.org), [React](https://reactjs.org), [TypeScript](https://www.typescriptlang.org) | [./login](./login) |
|
||||||
| API definitions | Specifications of the API | [Protobuf](https://developers.google.com/protocol-buffers) | [./proto/zitadel](./proto/zitadel) |
|
| API definitions | Specifications of the API | [Protobuf](https://developers.google.com/protocol-buffers) | [./proto/zitadel](./proto/zitadel) |
|
||||||
| docs | Project documentation made with docusaurus | [Docusaurus](https://docusaurus.io/) | [./docs](./docs) |
|
| docs | Project documentation made with docusaurus | [Docusaurus](https://docusaurus.io/) | [./docs](./docs) |
|
||||||
|
|
||||||
|
**Important**: This repository uses **pnpm** as package manager and **Turbo** for build orchestration. All frontend packages (console, login, docs) are managed as a monorepo with shared dependencies and optimized builds.
|
||||||
|
|
||||||
Please validate and test the code before you contribute.
|
Please validate and test the code before you contribute.
|
||||||
|
|
||||||
@@ -261,40 +324,39 @@ export ZITADEL_IMAGE=zitadel:local GOOS=linux
|
|||||||
make docker_image
|
make docker_image
|
||||||
|
|
||||||
# If you made changes in the e2e directory, make sure you reformat the files
|
# If you made changes in the e2e directory, make sure you reformat the files
|
||||||
make console_lint
|
pnpm turbo lint --filter=e2e
|
||||||
|
|
||||||
# Run the tests
|
# Run the tests
|
||||||
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml run --service-ports e2e
|
docker compose --file ./e2e/docker-compose.yaml run --service-ports e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
When you are happy with your changes, you can cleanup your environment.
|
When you are happy with your changes, you can cleanup your environment.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop and remove the docker containers for zitadel and the database
|
# Stop and remove the docker containers for zitadel and the database
|
||||||
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml down
|
docker compose --file ./e2e/docker-compose.yaml down
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Run Local End-to-End Tests Against Your Dev Server Console
|
#### Run Local End-to-End Tests Against Your Dev Server Console
|
||||||
|
|
||||||
If you also make [changes to the console](#console), you can run the test suite against your locally built backend code and frontend server.
|
If you also make [changes to the console](#console), you can run the test suite against your locally built backend code and frontend server.
|
||||||
But you will have to install the relevant node dependencies.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Install dependencies (from repository root)
|
||||||
(cd ./e2e && npm install)
|
pnpm install
|
||||||
|
|
||||||
# Run the tests interactively
|
# Run the tests interactively
|
||||||
(cd ./e2e && npm run open:golangangular)
|
cd ./e2e && pnpm run open:golangangular
|
||||||
|
|
||||||
# Run the tests non-interactively
|
# Run the tests non-interactively
|
||||||
(cd ./e2e && npm run e2e:golangangular)
|
cd ./e2e && pnpm run e2e:golangangular
|
||||||
```
|
```
|
||||||
|
|
||||||
When you are happy with your changes, you can cleanup your environment.
|
When you are happy with your changes, you can cleanup your environment.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop and remove the docker containers for zitadel and the database
|
# Stop and remove the docker containers for zitadel and the database
|
||||||
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml down
|
docker compose --file ./e2e/docker-compose.yaml down
|
||||||
```
|
```
|
||||||
|
|
||||||
### Console
|
### Console
|
||||||
@@ -304,15 +366,15 @@ Using [Docker Compose](https://docs.docker.com/compose/), you run [PostgreSQL](h
|
|||||||
You use the ZITADEL container as backend for your console.
|
You use the ZITADEL container as backend for your console.
|
||||||
The console is run in your [Node](https://nodejs.org/en/about/) environment using [a local development server for Angular](https://angular.io/cli/serve#ng-serve), so you have fast feedback about your changes.
|
The console is run in your [Node](https://nodejs.org/en/about/) environment using [a local development server for Angular](https://angular.io/cli/serve#ng-serve), so you have fast feedback about your changes.
|
||||||
|
|
||||||
We use angular-eslint/Prettier for linting/formatting, so please run `yarn lint:fix` before committing. (VSCode users, check out [this ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [this Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) to fix lint and formatting issues in development)
|
We use **pnpm** as package manager and **Turbo** for build orchestration. Use angular-eslint/Prettier for linting/formatting, so please run `pnpm turbo lint --filter=console` before committing. (VSCode users, check out [this ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [this Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) to fix lint and formatting issues in development)
|
||||||
|
|
||||||
Once you are happy with your changes, you run end-to-end tests and tear everything down.
|
Once you are happy with your changes, you run end-to-end tests and tear everything down.
|
||||||
|
|
||||||
The commands in this section are tested against the following software versions:
|
The commands in this section are tested against the following software versions:
|
||||||
|
|
||||||
- [Docker version 20.10.17](https://docs.docker.com/engine/install/)
|
- [Docker version 20.10.17](https://docs.docker.com/engine/install/)
|
||||||
- [Node version v16.17.0](https://nodejs.org/en/download/)
|
- [Node version v20.x](https://nodejs.org/en/download/)
|
||||||
- [npm version 8.18.0](https://docs.npmjs.com/try-the-latest-stable-version-of-npm)
|
- [pnpm version 9.x](https://pnpm.io/installation)
|
||||||
- [Cypress runtime dependencies](https://docs.cypress.io/guides/continuous-integration/introduction#Dependencies)
|
- [Cypress runtime dependencies](https://docs.cypress.io/guides/continuous-integration/introduction#Dependencies)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -328,11 +390,9 @@ The commands in this section are tested against the following software versions:
|
|||||||
Run the database and the latest backend locally.
|
Run the database and the latest backend locally.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Change to the console directory
|
# Start from the root of the repository
|
||||||
cd ./console
|
|
||||||
|
|
||||||
# You just need the db and the zitadel services to develop the console against.
|
# You just need the db and the zitadel services to develop the console against.
|
||||||
docker compose --file ../e2e/docker-compose.yaml up --detach zitadel
|
docker compose --file ./e2e/docker-compose.yaml up --detach zitadel
|
||||||
```
|
```
|
||||||
|
|
||||||
When the backend is ready, you have the latest zitadel exposed at http://localhost:8080.
|
When the backend is ready, you have the latest zitadel exposed at http://localhost:8080.
|
||||||
@@ -351,69 +411,244 @@ To allow console access via http://localhost:4200, you have to configure the ZIT
|
|||||||
You can run the local console development server now.
|
You can run the local console development server now.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install npm dependencies
|
# Install dependencies (from repository root)
|
||||||
yarn install
|
pnpm install
|
||||||
|
|
||||||
# Generate source files from Protos
|
# Option 1: Run console development server with Turbo (recommended)
|
||||||
yarn generate
|
pnpm turbo dev --filter=console
|
||||||
|
|
||||||
# Start the server
|
# Option 2: Run console development server directly
|
||||||
yarn start
|
cd ./console && pnpm start
|
||||||
|
|
||||||
|
# Option 3: Build and serve console (production build)
|
||||||
|
pnpm turbo build --filter=console
|
||||||
|
cd ./console && pnpm serve
|
||||||
|
|
||||||
# If you don't want to develop against http://localhost:8080, you can use another environment
|
# If you don't want to develop against http://localhost:8080, you can use another environment
|
||||||
ENVIRONMENT_JSON_URL=https://my-cloud-instance-abcdef.zitadel.cloud/ui/console/assets/environment.json yarn start
|
ENVIRONMENT_JSON_URL=https://my-cloud-instance-abcdef.zitadel.cloud/ui/console/assets/environment.json pnpm turbo dev --filter=console
|
||||||
```
|
```
|
||||||
|
|
||||||
Navigate to http://localhost:4200/.
|
Navigate to http://localhost:4200/.
|
||||||
Make some changes to the source code and see how the browser is automatically updated.
|
Make some changes to the source code and see how the browser is automatically updated.
|
||||||
|
|
||||||
|
#### Console Development Scripts
|
||||||
|
|
||||||
|
Here are the most useful scripts for console development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate protobuf files (happens automatically with Turbo dependencies)
|
||||||
|
pnpm turbo generate --filter=console
|
||||||
|
|
||||||
|
# Run development server with live reload
|
||||||
|
pnpm turbo dev --filter=console
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
pnpm turbo build --filter=console
|
||||||
|
|
||||||
|
# Lint and fix code
|
||||||
|
pnpm turbo lint --filter=console
|
||||||
|
|
||||||
|
# Run unit tests
|
||||||
|
pnpm turbo test --filter=console
|
||||||
|
|
||||||
|
# Run all console-related tasks
|
||||||
|
pnpm turbo dev lint test --filter=console
|
||||||
|
```
|
||||||
|
|
||||||
After making changes to the code, you should run the end-to-end-tests.
|
After making changes to the code, you should run the end-to-end-tests.
|
||||||
Open another shell.
|
Open another shell.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Reformat your console code
|
# Reformat your console code using Turbo
|
||||||
yarn lint:fix
|
pnpm turbo lint --filter=console
|
||||||
|
|
||||||
# Change to the e2e directory
|
# Change to the e2e directory
|
||||||
cd .. && cd e2e/
|
cd ./e2e
|
||||||
|
|
||||||
# If you made changes in the e2e directory, make sure you reformat the files here too
|
# If you made changes in the e2e directory, make sure you reformat the files here too
|
||||||
npm run lint:fix
|
pnpm run lint:fix
|
||||||
|
|
||||||
# Install npm dependencies
|
# Install pnpm dependencies
|
||||||
npm install
|
pnpm install
|
||||||
|
|
||||||
# Run all e2e tests
|
# Run all e2e tests
|
||||||
npm run e2e:angular -- --headed
|
pnpm run e2e:angular -- --headed
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also open the test suite interactively for fast feedback on specific tests.
|
You can also open the test suite interactively for fast feedback on specific tests.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run tests interactively
|
# Run tests interactively
|
||||||
npm run open:angular
|
pnpm run open:angular
|
||||||
```
|
```
|
||||||
|
|
||||||
If you also make [changes to the backend code](#backend--login), you can run the test against your locally built backend code and frontend server
|
If you also make [changes to the backend code](#backend--login), you can run the test against your locally built backend code and frontend server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run open:golangangular
|
pnpm run open:golangangular
|
||||||
npm run e2e:golangangular
|
pnpm run e2e:golangangular
|
||||||
```
|
```
|
||||||
|
|
||||||
When you are happy with your changes, you can format your code and cleanup your environment
|
When you are happy with your changes, you can format your code and cleanup your environment
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop and remove the docker containers for zitadel and the database
|
# Stop and remove the docker containers for zitadel and the database
|
||||||
docker compose down
|
docker compose --file ./e2e/docker-compose.yaml down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Login UI
|
||||||
|
|
||||||
|
The Login UI is a Next.js application that provides the user interface for authentication flows. It's located in the `./login` directory and uses pnpm and Turbo for development.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
- [Node version v20.x](https://nodejs.org/en/download/)
|
||||||
|
- [pnpm version 9.x](https://pnpm.io/installation)
|
||||||
|
- [Docker](https://docs.docker.com/engine/install/) for running the backend
|
||||||
|
|
||||||
|
#### Development Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start from the root of the repository
|
||||||
|
# Start the database and ZITADEL backend
|
||||||
|
docker compose --file ./e2e/docker-compose.yaml up --detach zitadel
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Option 1: Run login development server with Turbo (recommended)
|
||||||
|
pnpm turbo dev --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Option 2: Run login development server directly
|
||||||
|
cd ./login && pnpm dev
|
||||||
|
|
||||||
|
# Option 3: Build and serve login (production build)
|
||||||
|
pnpm turbo build --filter=@zitadel/login
|
||||||
|
cd ./login && pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
The login UI will be available at http://localhost:3000.
|
||||||
|
|
||||||
|
#### Login Development Scripts
|
||||||
|
|
||||||
|
Here are the most useful scripts for login development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate protobuf files (happens automatically with Turbo dependencies)
|
||||||
|
pnpm turbo generate --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Run development server with live reload
|
||||||
|
pnpm turbo dev --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
pnpm turbo build --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Lint and fix code
|
||||||
|
pnpm turbo lint --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Run unit tests
|
||||||
|
pnpm turbo test:unit --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Run integration tests
|
||||||
|
pnpm turbo test:integration --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Run acceptance tests
|
||||||
|
pnpm turbo test:acceptance --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Run all login-related tasks
|
||||||
|
pnpm turbo dev lint test:unit --filter=@zitadel/login
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Login Architecture
|
||||||
|
|
||||||
|
The login application consists of multiple packages:
|
||||||
|
|
||||||
|
- `@zitadel/login` - Main Next.js application
|
||||||
|
- `@zitadel/client` - TypeScript client library for ZITADEL APIs
|
||||||
|
- `@zitadel/proto` - Protocol buffer definitions and generated code
|
||||||
|
|
||||||
|
The build process uses Turbo to orchestrate dependencies:
|
||||||
|
|
||||||
|
1. Proto generation (`@zitadel/proto#generate`)
|
||||||
|
2. Client library build (`@zitadel/client#build`)
|
||||||
|
3. Login application build (`@zitadel/login#build`)
|
||||||
|
|
||||||
|
#### Testing the Login UI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run unit tests
|
||||||
|
pnpm turbo test:unit --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Run integration tests (requires running backend)
|
||||||
|
pnpm turbo test:integration --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Run acceptance tests
|
||||||
|
pnpm turbo test:acceptance --filter=@zitadel/login
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
pnpm turbo test:unit test:integration test:acceptance --filter=@zitadel/login
|
||||||
|
```
|
||||||
|
|
||||||
|
When you are happy with your changes, cleanup your environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop and remove the docker containers
|
||||||
|
docker compose --file ./e2e/docker-compose.yaml down
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contribute docs
|
## Contribute docs
|
||||||
|
|
||||||
Project documentation is made with docusaurus and is located under [./docs](./docs).
|
Project documentation is made with Docusaurus and is located under [./docs](./docs). The documentation uses **pnpm** and **Turbo** for development and build processes.
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies (from repository root)
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Option 1: Run docs development server with Turbo (recommended)
|
||||||
|
pnpm turbo dev --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Option 2: Run docs development server directly
|
||||||
|
cd ./docs && pnpm start
|
||||||
|
|
||||||
|
# Option 3: Build and serve docs (production build)
|
||||||
|
pnpm turbo build --filter=zitadel-docs
|
||||||
|
cd ./docs && pnpm serve
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docs Development Scripts
|
||||||
|
|
||||||
|
Here are the most useful scripts for docs development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate API documentation and configuration docs
|
||||||
|
pnpm turbo generate --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Run development server with live reload
|
||||||
|
pnpm turbo dev --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
pnpm turbo build --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Lint and fix code
|
||||||
|
pnpm turbo lint --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Run all docs-related tasks
|
||||||
|
pnpm turbo dev lint build --filter=zitadel-docs
|
||||||
|
```
|
||||||
|
|
||||||
|
The docs build process automatically:
|
||||||
|
|
||||||
|
1. Downloads required protoc plugins
|
||||||
|
2. Generates gRPC documentation from proto files
|
||||||
|
3. Generates API documentation from OpenAPI specs
|
||||||
|
4. Copies configuration files
|
||||||
|
5. Builds the Docusaurus site
|
||||||
|
|
||||||
### Local testing
|
### Local testing
|
||||||
|
|
||||||
Please refer to the [README](./docs/README.md) for more information and local testing.
|
The documentation server will be available at http://localhost:3000 with live reload for fast development feedback.
|
||||||
|
|
||||||
### Style guide
|
### Style guide
|
||||||
|
|
||||||
@@ -449,7 +684,8 @@ You may edit the texts in these files or create a new file for additional langua
|
|||||||
Please make sure that the languages within the files remain in their own language, e.g. German must always be `Deutsch.
|
Please make sure that the languages within the files remain in their own language, e.g. German must always be `Deutsch.
|
||||||
If you have added support for a new language, please also ensure that it is added in the list of languages in all the other language files.
|
If you have added support for a new language, please also ensure that it is added in the list of languages in all the other language files.
|
||||||
|
|
||||||
You also have to add some changes to the following files:
|
You also have to add some changes to the following files:
|
||||||
|
|
||||||
- [Register Local File](./console/src/app/app.module.ts)
|
- [Register Local File](./console/src/app/app.module.ts)
|
||||||
- [Add Supported Language](./console/src/app/utils/language.ts)
|
- [Add Supported Language](./console/src/app/utils/language.ts)
|
||||||
- [Customized Text Docs](./docs/docs/guides/manage/customize/texts.md)
|
- [Customized Text Docs](./docs/docs/guides/manage/customize/texts.md)
|
||||||
|
10
Makefile
10
Makefile
@@ -97,18 +97,17 @@ console_move:
|
|||||||
|
|
||||||
.PHONY: console_dependencies
|
.PHONY: console_dependencies
|
||||||
console_dependencies:
|
console_dependencies:
|
||||||
cd console && \
|
pnpm install
|
||||||
yarn install --immutable
|
|
||||||
|
|
||||||
.PHONY: console_client
|
.PHONY: console_client
|
||||||
console_client:
|
console_client:
|
||||||
cd console && \
|
cd console && \
|
||||||
yarn generate
|
pnpm generate
|
||||||
|
|
||||||
.PHONY: console_build
|
.PHONY: console_build
|
||||||
console_build: console_dependencies console_client
|
console_build: console_dependencies console_client
|
||||||
cd console && \
|
cd console && \
|
||||||
yarn build
|
pnpm build
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
@@ -166,8 +165,7 @@ core_integration_test: core_integration_server_start core_integration_test_packa
|
|||||||
|
|
||||||
.PHONY: console_lint
|
.PHONY: console_lint
|
||||||
console_lint:
|
console_lint:
|
||||||
cd console && \
|
pnpm turbo lint --filter=./console
|
||||||
yarn lint
|
|
||||||
|
|
||||||
.PHONY: core_lint
|
.PHONY: core_lint
|
||||||
core_lint:
|
core_lint:
|
||||||
|
@@ -107,10 +107,11 @@ FROM node:20-buster AS console-deps
|
|||||||
|
|
||||||
WORKDIR /zitadel/console
|
WORKDIR /zitadel/console
|
||||||
|
|
||||||
COPY console/package.json .
|
COPY pnpm-lock.yaml .
|
||||||
COPY console/yarn.lock .
|
COPY pnpm-workspace.yaml .
|
||||||
|
COPY console/package.json console/
|
||||||
|
|
||||||
RUN yarn install --frozen-lockfile
|
RUN corepack enable pnpm && pnpm install --frozen-lockfile --filter=console
|
||||||
|
|
||||||
# #######################################
|
# #######################################
|
||||||
# generate console client
|
# generate console client
|
||||||
@@ -127,7 +128,7 @@ COPY console/package.json .
|
|||||||
COPY console/buf.*.yaml .
|
COPY console/buf.*.yaml .
|
||||||
COPY proto ../proto
|
COPY proto ../proto
|
||||||
|
|
||||||
RUN yarn generate
|
RUN pnpm generate
|
||||||
|
|
||||||
# #######################################
|
# #######################################
|
||||||
# Gather all console files
|
# Gather all console files
|
||||||
@@ -145,7 +146,7 @@ COPY console/tsconfig* .
|
|||||||
# Build console
|
# Build console
|
||||||
# #######################################
|
# #######################################
|
||||||
FROM console-gathered AS console
|
FROM console-gathered AS console
|
||||||
RUN yarn build
|
RUN pnpm build
|
||||||
|
|
||||||
# ##############################################################################
|
# ##############################################################################
|
||||||
# build the executable
|
# build the executable
|
||||||
@@ -264,7 +265,7 @@ FROM console-gathered AS lint-console
|
|||||||
|
|
||||||
COPY console/.eslintrc.js .
|
COPY console/.eslintrc.js .
|
||||||
COPY console/.prettier* .
|
COPY console/.prettier* .
|
||||||
RUN yarn lint
|
RUN pnpm lint
|
||||||
|
|
||||||
# #######################################
|
# #######################################
|
||||||
# core
|
# core
|
||||||
|
2
console/.gitignore
vendored
2
console/.gitignore
vendored
@@ -36,7 +36,7 @@ speed-measure-plugin*.json
|
|||||||
/coverage
|
/coverage
|
||||||
/libpeerconnection.log
|
/libpeerconnection.log
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
yarn-error.log
|
pnpm-debug.log
|
||||||
testem.log
|
testem.log
|
||||||
/typings
|
/typings
|
||||||
|
|
||||||
|
@@ -1,27 +1,137 @@
|
|||||||
# Console
|
# Console Angular App
|
||||||
|
|
||||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.20.
|
This is the ZITADEL Console Angular application.
|
||||||
|
|
||||||
## Development server
|
## Development
|
||||||
|
|
||||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
### Prerequisites
|
||||||
|
|
||||||
## Code scaffolding
|
- Node.js 18 or later
|
||||||
|
- pnpm (latest)
|
||||||
|
|
||||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
### Installation
|
||||||
|
|
||||||
## Build
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
|
### Proto Generation
|
||||||
|
|
||||||
## Running unit tests
|
The Console app uses **dual proto generation** with Turbo dependency management:
|
||||||
|
|
||||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
1. **`@zitadel/proto` generation**: Modern ES modules with `@bufbuild/protobuf` for v2 APIs
|
||||||
|
2. **Local `buf.gen.yaml` generation**: Traditional protobuf JavaScript classes for v1 APIs
|
||||||
|
|
||||||
## Running end-to-end tests
|
The Console app's `turbo.json` ensures that `@zitadel/proto#generate` runs before the Console's own generation, providing both:
|
||||||
|
|
||||||
Please refer to the [contributing guide](../CONTRIBUTING.md#console)
|
- Modern schemas from `@zitadel/proto` (e.g., `UserSchema`, `DetailsSchema`)
|
||||||
|
- Legacy classes from `src/app/proto/generated` (e.g., `User`, `Project`)
|
||||||
|
|
||||||
## Further help
|
Generated files:
|
||||||
|
|
||||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
- **`@zitadel/proto`**: Modern ES modules in `login/packages/zitadel-proto/`
|
||||||
|
- **Local generation**: Traditional protobuf files in `src/app/proto/generated/`
|
||||||
|
- TypeScript definition files (`.d.ts`)
|
||||||
|
- JavaScript files (`.js`)
|
||||||
|
- gRPC client files (`*ServiceClientPb.ts`)
|
||||||
|
- OpenAPI/Swagger JSON files (`.swagger.json`)
|
||||||
|
|
||||||
|
To generate proto files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run generate
|
||||||
|
```
|
||||||
|
|
||||||
|
This automatically runs both generations in the correct order via Turbo dependencies.
|
||||||
|
|
||||||
|
### Development Server
|
||||||
|
|
||||||
|
To start the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
1. Fetch the environment configuration from the server
|
||||||
|
2. Serve the app on the default port
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
To build for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
1. Generate proto files (via `prebuild` script)
|
||||||
|
2. Build the Angular app with production optimizations
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
To run linting and formatting checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
To auto-fix formatting issues:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run lint:fix
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
- `src/app/proto/generated/` - Generated proto files (Angular-specific format)
|
||||||
|
- `buf.gen.yaml` - Local proto generation configuration
|
||||||
|
- `turbo.json` - Turbo dependency configuration for proto generation
|
||||||
|
- `prebuild.development.js` - Development environment configuration script
|
||||||
|
|
||||||
|
## Proto Generation Details
|
||||||
|
|
||||||
|
The Console app uses **dual proto generation** managed by Turbo dependencies:
|
||||||
|
|
||||||
|
### Dependency Chain
|
||||||
|
|
||||||
|
The Console app has the following build dependencies managed by Turbo:
|
||||||
|
|
||||||
|
1. `@zitadel/proto#generate` - Generates modern protobuf files
|
||||||
|
2. `@zitadel/client#build` - Builds the TypeScript gRPC client library
|
||||||
|
3. `console#generate` - Generates Console-specific protobuf files
|
||||||
|
4. `console#build` - Builds the Angular application
|
||||||
|
|
||||||
|
This ensures that the Console always has access to the latest client library and protobuf definitions.
|
||||||
|
|
||||||
|
### Legacy v1 API (Traditional Protobuf)
|
||||||
|
|
||||||
|
- Uses local `buf.gen.yaml` configuration
|
||||||
|
- Generates traditional Google protobuf JavaScript classes extending `jspb.Message`
|
||||||
|
- Uses plugins: `protocolbuffers/js`, `grpc/web`, `grpc-ecosystem/openapiv2`
|
||||||
|
- Output: `src/app/proto/generated/`
|
||||||
|
- Used for: Most existing Console functionality
|
||||||
|
|
||||||
|
### Modern v2 API (ES Modules)
|
||||||
|
|
||||||
|
- Uses `@zitadel/proto` package generation
|
||||||
|
- Generates modern ES modules with `@bufbuild/protobuf`
|
||||||
|
- Uses plugin: `@bufbuild/es` with ES modules and JSON types
|
||||||
|
- Output: `login/packages/zitadel-proto/`
|
||||||
|
- Used for: New user v2 API and services
|
||||||
|
|
||||||
|
### Dependency Management
|
||||||
|
|
||||||
|
The Console's `turbo.json` ensures proper execution order:
|
||||||
|
|
||||||
|
1. `@zitadel/proto#generate` runs first (modern ES modules)
|
||||||
|
2. Console's local generation runs second (traditional protobuf)
|
||||||
|
3. Build/lint/start tasks depend on both generations being complete
|
||||||
|
|
||||||
|
This approach allows the Console app to use both v1 and v2 APIs while maintaining proper build dependencies.
|
||||||
|
|
||||||
|
## Legacy Information
|
||||||
|
|
||||||
|
This project was originally generated with Angular CLI version 8.3.20 and has been updated over time.
|
||||||
|
@@ -3,12 +3,12 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
|
"dev": "node prebuild.development.js && ng serve",
|
||||||
"start": "node prebuild.development.js && ng serve",
|
"start": "node prebuild.development.js && ng serve",
|
||||||
"build": "ng build --configuration production --base-href=/ui/console/",
|
"build": "ng build --configuration production --base-href=/ui/console/",
|
||||||
"prelint": "npm run generate",
|
|
||||||
"lint": "ng lint && prettier --check src",
|
"lint": "ng lint && prettier --check src",
|
||||||
"lint:fix": "prettier --write src",
|
"lint:fix": "prettier --write src",
|
||||||
"generate": "buf generate ../proto --include-imports --include-wkt"
|
"generate": "pnpm exec buf generate ../proto --include-imports --include-wkt"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"@angular/platform-browser-dynamic": "^16.2.12",
|
"@angular/platform-browser-dynamic": "^16.2.12",
|
||||||
"@angular/router": "^16.2.12",
|
"@angular/router": "^16.2.12",
|
||||||
"@angular/service-worker": "^16.2.12",
|
"@angular/service-worker": "^16.2.12",
|
||||||
|
"@bufbuild/protobuf": "^2.2.2",
|
||||||
"@connectrpc/connect": "^2.0.0",
|
"@connectrpc/connect": "^2.0.0",
|
||||||
"@connectrpc/connect-web": "^2.0.0",
|
"@connectrpc/connect-web": "^2.0.0",
|
||||||
"@ctrl/ngx-codemirror": "^6.1.0",
|
"@ctrl/ngx-codemirror": "^6.1.0",
|
||||||
@@ -31,8 +32,8 @@
|
|||||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||||
"@ngx-translate/core": "^15.0.0",
|
"@ngx-translate/core": "^15.0.0",
|
||||||
"@zitadel/client": "1.2.0",
|
"@zitadel/client": "workspace:*",
|
||||||
"@zitadel/proto": "1.2.0",
|
"@zitadel/proto": "workspace:*",
|
||||||
"angular-oauth2-oidc": "^15.0.1",
|
"angular-oauth2-oidc": "^15.0.1",
|
||||||
"angularx-qrcode": "^16.0.2",
|
"angularx-qrcode": "^16.0.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
@@ -139,7 +139,7 @@ export class FeaturesComponent {
|
|||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
// to save special flags they have to be handled here
|
// to save special flags they have to be handled here
|
||||||
req.loginV2 = {
|
req['loginV2'] = {
|
||||||
required: toggleStates.loginV2.enabled,
|
required: toggleStates.loginV2.enabled,
|
||||||
baseUri: toggleStates.loginV2.baseUri,
|
baseUri: toggleStates.loginV2.baseUri,
|
||||||
};
|
};
|
||||||
|
@@ -89,7 +89,7 @@ export class ActionTwoAddTargetDialogComponent {
|
|||||||
nanos: 0,
|
nanos: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const targetType: Extract<MessageInitShape<typeof CreateTargetRequestSchema>['targetType'], { case: TargetTypes }> =
|
const targetType: MessageInitShape<typeof CreateTargetRequestSchema>['targetType'] =
|
||||||
type === 'restWebhook'
|
type === 'restWebhook'
|
||||||
? { case: type, value: { interruptOnError } }
|
? { case: type, value: { interruptOnError } }
|
||||||
: type === 'restCall'
|
: type === 'restCall'
|
||||||
|
@@ -22,9 +22,8 @@ const CACHE_WARNING_MS = 5 * 60 * 1000; // 5 minutes
|
|||||||
templateUrl: './oidc-webkeys.component.html',
|
templateUrl: './oidc-webkeys.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class OidcWebKeysComponent implements OnInit {
|
export class OidcWebKeysComponent {
|
||||||
protected readonly refresh = new Subject<true>();
|
protected readonly refresh = new Subject<true>();
|
||||||
protected readonly webKeysEnabled$: Observable<boolean>;
|
|
||||||
protected readonly webKeys$: Observable<WebKey[]>;
|
protected readonly webKeys$: Observable<WebKey[]>;
|
||||||
protected readonly inactiveWebKeys$: Observable<WebKey[]>;
|
protected readonly inactiveWebKeys$: Observable<WebKey[]>;
|
||||||
protected readonly nextWebKeyCandidate$: Observable<WebKey | undefined>;
|
protected readonly nextWebKeyCandidate$: Observable<WebKey | undefined>;
|
||||||
@@ -34,17 +33,12 @@ export class OidcWebKeysComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly webKeysService: WebKeysService,
|
private readonly webKeysService: WebKeysService,
|
||||||
private readonly featureService: NewFeatureService,
|
|
||||||
private readonly toast: ToastService,
|
private readonly toast: ToastService,
|
||||||
private readonly timestampToDatePipe: TimestampToDatePipe,
|
private readonly timestampToDatePipe: TimestampToDatePipe,
|
||||||
private readonly dialog: MatDialog,
|
private readonly dialog: MatDialog,
|
||||||
private readonly destroyRef: DestroyRef,
|
private readonly destroyRef: DestroyRef,
|
||||||
private readonly router: Router,
|
|
||||||
private readonly route: ActivatedRoute,
|
|
||||||
) {
|
) {
|
||||||
this.webKeysEnabled$ = this.getWebKeysEnabled().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
const webKeys$ = this.getWebKeys().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
||||||
|
|
||||||
const webKeys$ = this.getWebKeys(this.webKeysEnabled$).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
|
||||||
|
|
||||||
this.webKeys$ = webKeys$.pipe(map((webKeys) => webKeys.filter((webKey) => webKey.state !== State.INACTIVE)));
|
this.webKeys$ = webKeys$.pipe(map((webKeys) => webKeys.filter((webKey) => webKey.state !== State.INACTIVE)));
|
||||||
this.inactiveWebKeys$ = webKeys$.pipe(map((webKeys) => webKeys.filter((webKey) => webKey.state === State.INACTIVE)));
|
this.inactiveWebKeys$ = webKeys$.pipe(map((webKeys) => webKeys.filter((webKey) => webKey.state === State.INACTIVE)));
|
||||||
@@ -52,34 +46,7 @@ export class OidcWebKeysComponent implements OnInit {
|
|||||||
this.nextWebKeyCandidate$ = this.getNextWebKeyCandidate(this.webKeys$);
|
this.nextWebKeyCandidate$ = this.getNextWebKeyCandidate(this.webKeys$);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
private getWebKeys() {
|
||||||
// redirect away from this page if web keys are not enabled
|
|
||||||
// this also preloads the web keys enabled state
|
|
||||||
this.webKeysEnabled$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(async (webKeysEnabled) => {
|
|
||||||
if (webKeysEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.router.navigate([], {
|
|
||||||
relativeTo: this.route,
|
|
||||||
queryParamsHandling: 'merge',
|
|
||||||
queryParams: {
|
|
||||||
id: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private getWebKeysEnabled() {
|
|
||||||
return defer(() => this.featureService.getInstanceFeatures()).pipe(
|
|
||||||
map((features) => features.webKey?.enabled ?? false),
|
|
||||||
catchError((err) => {
|
|
||||||
this.toast.showError(err);
|
|
||||||
return of(false);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getWebKeys(webKeysEnabled$: Observable<boolean>) {
|
|
||||||
return this.refresh.pipe(
|
return this.refresh.pipe(
|
||||||
startWith(true),
|
startWith(true),
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
@@ -87,12 +54,6 @@ export class OidcWebKeysComponent implements OnInit {
|
|||||||
}),
|
}),
|
||||||
map(({ webKeys }) => webKeys),
|
map(({ webKeys }) => webKeys),
|
||||||
catchError(async (err) => {
|
catchError(async (err) => {
|
||||||
const webKeysEnabled = await firstValueFrom(webKeysEnabled$);
|
|
||||||
// suppress errors if web keys are not enabled
|
|
||||||
if (!webKeysEnabled) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toast.showError(err);
|
this.toast.showError(err);
|
||||||
return [];
|
return [];
|
||||||
}),
|
}),
|
||||||
|
@@ -204,7 +204,7 @@ export class UserCreateV2Component implements OnInit {
|
|||||||
|
|
||||||
if (authenticationFactor.factor === 'initialPassword') {
|
if (authenticationFactor.factor === 'initialPassword') {
|
||||||
const { password } = authenticationFactor.form.getRawValue();
|
const { password } = authenticationFactor.form.getRawValue();
|
||||||
humanReq.passwordType = {
|
humanReq['passwordType'] = {
|
||||||
case: 'password',
|
case: 'password',
|
||||||
value: {
|
value: {
|
||||||
password,
|
password,
|
||||||
|
@@ -8,12 +8,14 @@ import { Gender, HumanProfile, HumanProfileSchema } from '@zitadel/proto/zitadel
|
|||||||
import { filter, startWith } from 'rxjs/operators';
|
import { filter, startWith } from 'rxjs/operators';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { Profile } from '@zitadel/proto/zitadel/user_pb';
|
import { Profile } from '@zitadel/proto/zitadel/user_pb';
|
||||||
import { create } from '@bufbuild/protobuf';
|
//@ts-ignore
|
||||||
|
import { create } from '@zitadel/client';
|
||||||
|
|
||||||
function toHumanProfile(profile: HumanProfile | Profile): HumanProfile {
|
function toHumanProfile(profile: HumanProfile | Profile): HumanProfile {
|
||||||
if (profile.$typeName === 'zitadel.user.v2.HumanProfile') {
|
if (profile.$typeName === 'zitadel.user.v2.HumanProfile') {
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
return create(HumanProfileSchema, {
|
return create(HumanProfileSchema, {
|
||||||
givenName: profile.firstName,
|
givenName: profile.firstName,
|
||||||
familyName: profile.lastName,
|
familyName: profile.lastName,
|
||||||
|
@@ -36,10 +36,10 @@ import { AuthenticationService } from 'src/app/services/authentication.service';
|
|||||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
import { UserState as UserStateV1 } from 'src/app/proto/generated/zitadel/user_pb';
|
import { UserState as UserStateV1 } from 'src/app/proto/generated/zitadel/user_pb';
|
||||||
|
|
||||||
type Query = Exclude<
|
type ListUsersRequest = MessageInitShape<typeof ListUsersRequestSchema>;
|
||||||
Exclude<MessageInitShape<typeof ListUsersRequestSchema>['queries'], undefined>[number]['query'],
|
type QueriesArray = NonNullable<ListUsersRequest['queries']>;
|
||||||
undefined
|
type QueryWrapper = QueriesArray extends readonly (infer T)[] ? T : never;
|
||||||
>;
|
type Query = NonNullable<QueryWrapper extends { query?: infer Q } ? Q : never>;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'cnsl-user-table',
|
selector: 'cnsl-user-table',
|
||||||
|
@@ -229,94 +229,3 @@ export class UserService {
|
|||||||
return this.grpcService.userNew.setPassword(create(SetPasswordRequestSchema, req));
|
return this.grpcService.userNew.setPassword(create(SetPasswordRequestSchema, req));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function userToV2(user: User): UserV2 {
|
|
||||||
const details = user.getDetails();
|
|
||||||
return create(UserSchema, {
|
|
||||||
userId: user.getId(),
|
|
||||||
details: details && detailsToV2(details),
|
|
||||||
state: user.getState() as number as UserState,
|
|
||||||
username: user.getUserName(),
|
|
||||||
loginNames: user.getLoginNamesList(),
|
|
||||||
preferredLoginName: user.getPreferredLoginName(),
|
|
||||||
type: typeToV2(user),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function detailsToV2(details: ObjectDetails): Details {
|
|
||||||
const changeDate = details.getChangeDate();
|
|
||||||
return create(DetailsSchema, {
|
|
||||||
sequence: BigInt(details.getSequence()),
|
|
||||||
changeDate: changeDate && timestampToV2(changeDate),
|
|
||||||
resourceOwner: details.getResourceOwner(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function timestampToV2(timestamp: Timestamp): TimestampV2 {
|
|
||||||
return create(TimestampSchema, {
|
|
||||||
seconds: BigInt(timestamp.getSeconds()),
|
|
||||||
nanos: timestamp.getNanos(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function typeToV2(user: User): UserV2['type'] {
|
|
||||||
const human = user.getHuman();
|
|
||||||
if (human) {
|
|
||||||
return { case: 'human', value: humanToV2(user, human) };
|
|
||||||
}
|
|
||||||
|
|
||||||
const machine = user.getMachine();
|
|
||||||
if (machine) {
|
|
||||||
return { case: 'machine', value: machineToV2(machine) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { case: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
function humanToV2(user: User, human: Human): HumanUser {
|
|
||||||
const profile = human.getProfile();
|
|
||||||
const email = human.getEmail()?.getEmail();
|
|
||||||
const phone = human.getPhone();
|
|
||||||
const passwordChanged = human.getPasswordChanged();
|
|
||||||
|
|
||||||
return create(HumanUserSchema, {
|
|
||||||
userId: user.getId(),
|
|
||||||
state: user.getState() as number as UserState,
|
|
||||||
username: user.getUserName(),
|
|
||||||
loginNames: user.getLoginNamesList(),
|
|
||||||
preferredLoginName: user.getPreferredLoginName(),
|
|
||||||
profile: profile && humanProfileToV2(profile),
|
|
||||||
email: { email },
|
|
||||||
phone: phone && humanPhoneToV2(phone),
|
|
||||||
passwordChangeRequired: false,
|
|
||||||
passwordChanged: passwordChanged && timestampToV2(passwordChanged),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function humanProfileToV2(profile: Profile): HumanProfile {
|
|
||||||
return create(HumanProfileSchema, {
|
|
||||||
givenName: profile.getFirstName(),
|
|
||||||
familyName: profile.getLastName(),
|
|
||||||
nickName: profile.getNickName(),
|
|
||||||
displayName: profile.getDisplayName(),
|
|
||||||
preferredLanguage: profile.getPreferredLanguage(),
|
|
||||||
gender: profile.getGender() as number as Gender,
|
|
||||||
avatarUrl: profile.getAvatarUrl(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function humanPhoneToV2(phone: Phone): HumanPhone {
|
|
||||||
return create(HumanPhoneSchema, {
|
|
||||||
phone: phone.getPhone(),
|
|
||||||
isVerified: phone.getIsPhoneVerified(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function machineToV2(machine: Machine): MachineUser {
|
|
||||||
return create(MachineUserSchema, {
|
|
||||||
name: machine.getName(),
|
|
||||||
description: machine.getDescription(),
|
|
||||||
hasSecret: machine.getHasSecret(),
|
|
||||||
accessTokenType: machine.getAccessTokenType() as number as AccessTokenType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
28
console/turbo.json
Normal file
28
console/turbo.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"generate": {
|
||||||
|
"dependsOn": ["@zitadel/proto#generate"],
|
||||||
|
"outputs": ["src/app/proto/generated/**"]
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["generate", "@zitadel/client#build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"dependsOn": ["generate", "@zitadel/client#build"],
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"dependsOn": ["generate", "@zitadel/client#build"],
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"dependsOn": ["generate"],
|
||||||
|
"outputs": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9336
console/yarn.lock
9336
console/yarn.lock
File diff suppressed because it is too large
Load Diff
3
docs/.gitignore
vendored
3
docs/.gitignore
vendored
@@ -24,7 +24,6 @@ docs/apis/resources
|
|||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
pnpm-debug.log*
|
||||||
yarn-error.log*
|
|
||||||
.vercel
|
.vercel
|
||||||
/protoc-gen-connect-openapi*
|
/protoc-gen-connect-openapi*
|
||||||
|
@@ -2,45 +2,84 @@
|
|||||||
|
|
||||||
This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.
|
This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.
|
||||||
|
|
||||||
|
The documentation is part of the ZITADEL monorepo and uses **pnpm** and **Turbo** for development and build processes.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From the repository root
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Start development server (with Turbo)
|
||||||
|
pnpm turbo dev --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Or start directly from docs directory
|
||||||
|
cd docs && pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
The site will be available at http://localhost:3000
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
All scripts can be run from the repository root using Turbo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development server with live reload
|
||||||
|
pnpm turbo dev --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
pnpm turbo build --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Generate API documentation and configuration docs
|
||||||
|
pnpm turbo generate --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Lint and fix code
|
||||||
|
pnpm turbo lint --filter=zitadel-docs
|
||||||
|
|
||||||
|
# Serve production build locally
|
||||||
|
cd docs && pnpm serve
|
||||||
|
```
|
||||||
|
|
||||||
## Add new Sites to existing Topics
|
## Add new Sites to existing Topics
|
||||||
|
|
||||||
To add a new site to the already existing structure simply save the `md` file into the corresponding folder and append the sites id int the file `sidebars.js`.
|
To add a new site to the already existing structure simply save the `md` file into the corresponding folder and append the sites id int the file `sidebars.js`.
|
||||||
|
|
||||||
If you are introducing new APIs (gRPC), you need to add a new entry to `docusaurus.config.js` under the `plugins` section.
|
If you are introducing new APIs (gRPC), you need to add a new entry to `docusaurus.config.js` under the `plugins` section.
|
||||||
|
|
||||||
## Installation
|
## Build Process
|
||||||
|
|
||||||
Install dependencies with
|
The documentation build process automatically:
|
||||||
|
|
||||||
```
|
|
||||||
yarn install
|
|
||||||
```
|
|
||||||
|
|
||||||
then run
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn generate
|
|
||||||
```
|
|
||||||
|
|
||||||
|
1. **Downloads required protoc plugins** - Ensures `protoc-gen-connect-openapi` is available
|
||||||
|
2. **Generates gRPC documentation** - Creates API docs from proto files
|
||||||
|
3. **Generates API documentation** - Creates OpenAPI specification docs
|
||||||
|
4. **Copies configuration files** - Includes configuration examples
|
||||||
|
5. **Builds the Docusaurus site** - Generates the final static site
|
||||||
|
|
||||||
## Local Development
|
## Local Development
|
||||||
|
|
||||||
Start a local development server with
|
### Standard Development
|
||||||
|
|
||||||
```
|
```bash
|
||||||
yarn start
|
# Install dependencies
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Start development server
|
||||||
|
pnpm start
|
||||||
```
|
```
|
||||||
|
|
||||||
When working on the API docs, run a local development server with
|
### API Documentation Development
|
||||||
|
|
||||||
```
|
When working on the API docs, run a local development server with:
|
||||||
yarn start:api
|
|
||||||
|
```bash
|
||||||
|
pnpm start:api
|
||||||
```
|
```
|
||||||
|
|
||||||
## Container Image
|
## Container Image
|
||||||
|
|
||||||
If you just want to start docusaurus locally without installing node you can fallback to our container image.
|
If you just want to start docusaurus locally without installing node you can fallback to our container image.
|
||||||
Execute the following commands from the repository root to build and start a local version of ZITADEL
|
Execute the following commands from the repository root to build and start a local version of ZITADEL
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker build -f docs/Dockerfile . -t zitadel-docs
|
docker build -f docs/Dockerfile . -t zitadel-docs
|
||||||
|
@@ -264,7 +264,7 @@ module.exports = {
|
|||||||
outputDir: "docs/apis/resources/auth",
|
outputDir: "docs/apis/resources/auth",
|
||||||
sidebarOptions: {
|
sidebarOptions: {
|
||||||
groupPathsBy: "tag",
|
groupPathsBy: "tag",
|
||||||
categoryLinkSource: "tag",
|
categoryLinkSource: "auto",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mgmt: {
|
mgmt: {
|
||||||
@@ -272,7 +272,7 @@ module.exports = {
|
|||||||
outputDir: "docs/apis/resources/mgmt",
|
outputDir: "docs/apis/resources/mgmt",
|
||||||
sidebarOptions: {
|
sidebarOptions: {
|
||||||
groupPathsBy: "tag",
|
groupPathsBy: "tag",
|
||||||
categoryLinkSource: "tag",
|
categoryLinkSource: "auto",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
@@ -280,7 +280,7 @@ module.exports = {
|
|||||||
outputDir: "docs/apis/resources/admin",
|
outputDir: "docs/apis/resources/admin",
|
||||||
sidebarOptions: {
|
sidebarOptions: {
|
||||||
groupPathsBy: "tag",
|
groupPathsBy: "tag",
|
||||||
categoryLinkSource: "tag",
|
categoryLinkSource: "auto",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
system: {
|
system: {
|
||||||
@@ -288,7 +288,7 @@ module.exports = {
|
|||||||
outputDir: "docs/apis/resources/system",
|
outputDir: "docs/apis/resources/system",
|
||||||
sidebarOptions: {
|
sidebarOptions: {
|
||||||
groupPathsBy: "tag",
|
groupPathsBy: "tag",
|
||||||
categoryLinkSource: "tag",
|
categoryLinkSource: "auto",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
user_v2: {
|
user_v2: {
|
||||||
|
@@ -4,20 +4,23 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docusaurus": "docusaurus",
|
"docusaurus": "docusaurus",
|
||||||
|
"dev": "docusaurus start",
|
||||||
"start": "docusaurus start",
|
"start": "docusaurus start",
|
||||||
"start:api": "yarn run generate && docusaurus start",
|
"start:api": "pnpm run generate && docusaurus start",
|
||||||
"build": "yarn run generate && docusaurus build",
|
"build": "pnpm run ensure-plugins && pnpm run generate && docusaurus build",
|
||||||
"swizzle": "docusaurus swizzle",
|
"swizzle": "docusaurus swizzle",
|
||||||
"deploy": "docusaurus deploy",
|
"deploy": "docusaurus deploy",
|
||||||
"clear": "docusaurus clear",
|
"clear": "docusaurus clear",
|
||||||
"serve": "docusaurus serve",
|
"serve": "docusaurus serve",
|
||||||
"write-translations": "docusaurus write-translations",
|
"write-translations": "docusaurus write-translations",
|
||||||
"write-heading-ids": "docusaurus write-heading-ids",
|
"write-heading-ids": "docusaurus write-heading-ids",
|
||||||
"generate": "yarn run generate:grpc && yarn run generate:apidocs && yarn run generate:configdocs",
|
"ensure-plugins": "if [ ! -f \"protoc-gen-connect-openapi/protoc-gen-connect-openapi\" ]; then sh ./plugin-download.sh; fi",
|
||||||
"generate:grpc": "buf generate ../proto",
|
"debug-plugins": "echo \"PWD: $(pwd)\" && echo \"Plugin file exists: $(test -f protoc-gen-connect-openapi/protoc-gen-connect-openapi && echo 'yes' || echo 'no')\" && echo \"Plugin executable: $(test -x protoc-gen-connect-openapi/protoc-gen-connect-openapi && echo 'yes' || echo 'no')\" && ls -la protoc-gen-connect-openapi/ || echo 'Plugin directory not found'",
|
||||||
|
"generate": "pnpm run generate:grpc && pnpm run generate:apidocs && pnpm run generate:configdocs",
|
||||||
|
"generate:grpc": "pnpm run ensure-plugins && buf generate ../proto",
|
||||||
"generate:apidocs": "docusaurus gen-api-docs all",
|
"generate:apidocs": "docusaurus gen-api-docs all",
|
||||||
"generate:configdocs": "cp -r ../cmd/defaults.yaml ./docs/self-hosting/manage/configure/ && cp -r ../cmd/setup/steps.yaml ./docs/self-hosting/manage/configure/",
|
"generate:configdocs": "cp -r ../cmd/defaults.yaml ./docs/self-hosting/manage/configure/ && cp -r ../cmd/setup/steps.yaml ./docs/self-hosting/manage/configure/",
|
||||||
"generate:re-gen": "yarn generate:clean-all && yarn generate",
|
"generate:re-gen": "yarn generate:clean-all && pnpm generate",
|
||||||
"generate:clean-all": "docusaurus clean-api-docs all",
|
"generate:clean-all": "docusaurus clean-api-docs all",
|
||||||
"postinstall": "sh ./plugin-download.sh"
|
"postinstall": "sh ./plugin-download.sh"
|
||||||
},
|
},
|
||||||
@@ -64,5 +67,5 @@
|
|||||||
"@docusaurus/types": "^3.8.1",
|
"@docusaurus/types": "^3.8.1",
|
||||||
"tailwindcss": "^3.2.4"
|
"tailwindcss": "^3.2.4"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
"packageManager": "pnpm@9.1.2+sha256.19c17528f9ca20bd442e4ca42f00f1b9808a9cb419383cd04ba32ef19322aba7"
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,28 @@
|
|||||||
echo $(uname -m)
|
#!/bin/bash
|
||||||
mkdir protoc-gen-connect-openapi
|
set -e
|
||||||
|
|
||||||
|
echo "Downloading protoc-gen-connect-openapi plugin..."
|
||||||
|
echo "Architecture: $(uname -m)"
|
||||||
|
echo "OS: $(uname)"
|
||||||
|
|
||||||
|
# Create directory if it doesn't exist
|
||||||
|
mkdir -p protoc-gen-connect-openapi
|
||||||
cd ./protoc-gen-connect-openapi/
|
cd ./protoc-gen-connect-openapi/
|
||||||
|
|
||||||
|
# Skip download if plugin already exists and is executable
|
||||||
|
if [ -f "protoc-gen-connect-openapi" ] && [ -x "protoc-gen-connect-openapi" ]; then
|
||||||
|
echo "Plugin already exists and is executable"
|
||||||
|
./protoc-gen-connect-openapi --version || echo "Plugin version check failed, but file exists"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up any partial downloads
|
||||||
|
rm -f protoc-gen-connect-openapi.tar.gz protoc-gen-connect-openapi
|
||||||
|
|
||||||
|
# Determine download URL based on OS and architecture
|
||||||
if [ "$(uname)" = "Darwin" ]; then
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
curl -L -o protoc-gen-connect-openapi.tar.gz https://github.com/sudorandom/protoc-gen-connect-openapi/releases/download/v0.18.0/protoc-gen-connect-openapi_0.18.0_darwin_all.tar.gz
|
echo "Downloading for Darwin..."
|
||||||
|
URL="https://github.com/sudorandom/protoc-gen-connect-openapi/releases/download/v0.18.0/protoc-gen-connect-openapi_0.18.0_darwin_all.tar.gz"
|
||||||
else
|
else
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
case $ARCH in
|
case $ARCH in
|
||||||
@@ -17,6 +37,34 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
curl -L -o protoc-gen-connect-openapi.tar.gz https://github.com/sudorandom/protoc-gen-connect-openapi/releases/download/v0.18.0/protoc-gen-connect-openapi_0.18.0_linux_${ARCH}.tar.gz
|
echo "Downloading for Linux ${ARCH}..."
|
||||||
|
URL="https://github.com/sudorandom/protoc-gen-connect-openapi/releases/download/v0.18.0/protoc-gen-connect-openapi_0.18.0_linux_${ARCH}.tar.gz"
|
||||||
fi
|
fi
|
||||||
tar -xvf protoc-gen-connect-openapi.tar.gz
|
|
||||||
|
# Download with retries
|
||||||
|
echo "Downloading from: $URL"
|
||||||
|
curl -L -o protoc-gen-connect-openapi.tar.gz "$URL" || {
|
||||||
|
echo "Download failed, trying with different curl options..."
|
||||||
|
curl -L --fail --retry 3 --retry-delay 1 -o protoc-gen-connect-openapi.tar.gz "$URL"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Extracting plugin..."
|
||||||
|
tar -xzf protoc-gen-connect-openapi.tar.gz
|
||||||
|
|
||||||
|
# Verify extraction
|
||||||
|
if [ ! -f "protoc-gen-connect-openapi" ]; then
|
||||||
|
echo "ERROR: Plugin binary not found after extraction"
|
||||||
|
ls -la
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure the plugin is executable
|
||||||
|
chmod +x protoc-gen-connect-openapi
|
||||||
|
|
||||||
|
# Verify plugin works
|
||||||
|
echo "Plugin installed successfully"
|
||||||
|
ls -la protoc-gen-connect-openapi
|
||||||
|
./protoc-gen-connect-openapi --version || echo "Plugin version check failed, but installation completed"
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm -f protoc-gen-connect-openapi.tar.gz
|
45
docs/turbo.json
Normal file
45
docs/turbo.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"generate": {
|
||||||
|
"dependsOn": ["^generate"],
|
||||||
|
"outputs": ["docs/api/**", "docs/self-hosting/manage/configure/*.yaml"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"generate:grpc": {
|
||||||
|
"dependsOn": ["^generate"],
|
||||||
|
"outputs": ["docs/api/**"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"generate:apidocs": {
|
||||||
|
"dependsOn": ["generate:grpc"],
|
||||||
|
"outputs": ["docs/api/**"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"generate:configdocs": {
|
||||||
|
"outputs": ["docs/self-hosting/manage/configure/*.yaml"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["generate"],
|
||||||
|
"outputs": ["build/**"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"dependsOn": ["generate"],
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"dependsOn": ["generate"],
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
},
|
||||||
|
"start:api": {
|
||||||
|
"dependsOn": ["generate"],
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
283
docs/vercel.json
283
docs/vercel.json
@@ -1,64 +1,223 @@
|
|||||||
{
|
{
|
||||||
"github": {
|
"github": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
"cleanUrls": true,
|
"cleanUrls": true,
|
||||||
"rewrites": [
|
"rewrites": [
|
||||||
{
|
{
|
||||||
"source": "/docs/proxy/js/script.js",
|
"source": "/docs/proxy/js/script.js",
|
||||||
"destination": "https://plausible.io/js/script.tagged-events.pageview-props.outbound-links.js"
|
"destination": "https://plausible.io/js/script.tagged-events.pageview-props.outbound-links.js"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "/docs/proxy/api/event",
|
"source": "/docs/proxy/api/event",
|
||||||
"destination": "https://plausible.io/api/event"
|
"destination": "https://plausible.io/api/event"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "/docs/:match*",
|
"source": "/docs/:match*",
|
||||||
"destination": "/:match*"
|
"destination": "/:match*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"redirects": [
|
"redirects": [
|
||||||
{ "source": "/", "destination": "/docs" },
|
{ "source": "/", "destination": "/docs" },
|
||||||
{ "source": "/docs/category/apis/:slug*", "destination": "/docs/apis/:slug*", "permanent": true },
|
{
|
||||||
{ "source": "/docs/apis/mgmt/:slug*", "destination": "/docs/apis/resources/mgmt/:slug*", "permanent": true },
|
"source": "/docs/category/apis/:slug*",
|
||||||
{ "source": "/docs/apis/auth/:slug*", "destination": "/docs/apis/resources/auth/:slug*", "permanent": true },
|
"destination": "/docs/apis/:slug*",
|
||||||
{ "source": "/docs/apis/system/:slug*", "destination": "/docs/apis/resources/system/:slug*", "permanent": true },
|
"permanent": true
|
||||||
{ "source": "/docs/apis/admin/:slug*", "destination": "/docs/apis/resources/admin/:slug*", "permanent": true },
|
},
|
||||||
{ "source": "/docs/apis/actionsv2/introduction", "destination": "/docs/apis/actions/v2/usage", "permanent": true },
|
{
|
||||||
{ "source": "/docs/apis/actionsv2/execution-local", "destination": "/docs/apis/actions/v2/testing-locally", "permanent": true },
|
"source": "/docs/apis/mgmt/:slug*",
|
||||||
{ "source": "/docs/guides/integrate/human-users", "destination": "/docs/guides/integrate/login", "permanent": true },
|
"destination": "/docs/apis/resources/mgmt/:slug*",
|
||||||
{ "source": "/docs/guides/solution-scenarios/device-authorization", "destination": "/docs/guides/integrate/login/oidc/device-authorization", "permanent": true },
|
"permanent": true
|
||||||
{ "source": "/docs/guides/integrate/oauth-recommended-flows", "destination": "/docs/guides/integrate/login/oidc/oauth-recommended-flows", "permanent": true },
|
},
|
||||||
{ "source": "/docs/guides/integrate/login-users", "destination": "/docs/guides/integrate/login/oidc/login-users", "permanent": true },
|
{
|
||||||
{ "source": "/docs/guides/integrate/logout", "destination": "/docs/guides/integrate/login/oidc/logout", "permanent": true },
|
"source": "/docs/apis/auth/:slug*",
|
||||||
{ "source": "/docs/guides/solution-scenarios/onboarding", "destination": "/docs/guides/integrate/onboarding", "permanent": true },
|
"destination": "/docs/apis/resources/auth/:slug*",
|
||||||
{ "source": "/docs/guides/solution-scenarios/onboarding/b2b", "destination": "/docs/guides/integrate/onboarding/b2b", "permanent": true },
|
"permanent": true
|
||||||
{ "source": "/docs/guides/solution-scenarios/onboarding/end-users", "destination": "/docs/guides/integrate/onboarding/end-users", "permanent": true },
|
},
|
||||||
{ "source": "/docs/concepts/structure/jwt_idp", "destination": "/docs/guides/integrate/identity-providers/jwt-idp", "permanent": true },
|
{
|
||||||
{ "source": "/docs/guides/solution-scenarios/onboarding/end-users", "destination": "/docs/guides/integrate/onboarding/end-users", "permanent": true },
|
"source": "/docs/apis/system/:slug*",
|
||||||
{ "source": "/docs/guides/integrate/serviceusers", "destination": "/docs/guides/integrate/service-users/authenticate-service-users", "permanent": true },
|
"destination": "/docs/apis/resources/system/:slug*",
|
||||||
{ "source": "/docs/guides/integrate/private-key-jwt", "destination": "/docs/guides/integrate/service-users/private-key-jwt", "permanent": true },
|
"permanent": true
|
||||||
{ "source": "/docs/guides/integrate/client-credentials", "destination": "/docs/guides/integrate/service-users/client-credentials", "permanent": true },
|
},
|
||||||
{ "source": "/docs/guides/integrate/pat", "destination": "/docs/guides/integrate/service-users/private-access-token", "permanent": true },
|
{
|
||||||
{ "source": "/docs/guides/integrate/access-zitadel-apis", "destination": "/docs/guides/integrate/zitadel-apis/access-zitadel-apis", "permanent": true },
|
"source": "/docs/apis/admin/:slug*",
|
||||||
{ "source": "/docs/guides/integrate/access-zitadel-system-api", "destination": "/docs/guides/integrate/zitadel-apis/access-zitadel-system-api", "permanent": true },
|
"destination": "/docs/apis/resources/admin/:slug*",
|
||||||
{ "source": "/docs/guides/integrate/event-api", "destination": "/docs/guides/integrate/zitadel-apis/event-api", "permanent": true },
|
"permanent": true
|
||||||
{ "source": "/docs/examples/call-zitadel-api/go", "destination": "/docs/guides/integrate/zitadel-apis/example-zitadel-api-with-go", "permanent": true },
|
},
|
||||||
{ "source": "/docs/examples/call-zitadel-api/dot-net", "destination": "/docs/guides/integrate/zitadel-apis/example-zitadel-api-with-dot-net", "permanent": true },
|
{
|
||||||
{ "source": "/docs/guides/manage/terraform/basics", "destination": "/docs/guides/manage/terraform-provider", "permanent": true },
|
"source": "/docs/apis/actionsv2/introduction",
|
||||||
{ "source": "/docs/guides/integrate/identity-providers", "destination": "/docs/guides/integrate/identity-providers/introduction", "permanent": true },
|
"destination": "/docs/apis/actions/v2/usage",
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#centralized-authentication-endpoint", "destination": "/docs/guides/integrate/login/hosted-login#centralized-authentication-endpoint", "permanent": true },
|
"permanent": true
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#security-and-compliance", "destination": "/docs/guides/integrate/login/hosted-login#security-and-compliance", "permanent": true },
|
},
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#developer-friendly-integration", "destination": "/docs/guides/integrate/login/hosted-login#developer-friendly-integration", "permanent": true },
|
{
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#key-features-of-the-hosted-login", "destination": "/docs/guides/integrate/login/hosted-login#key-features-of-the-hosted-login", "permanent": true },
|
"source": "/docs/apis/actionsv2/execution-local",
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#flexible-usernames", "destination": "/docs/guides/integrate/login/hosted-login#flexible-usernames", "permanent": true },
|
"destination": "/docs/apis/actions/v2/testing-locally",
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#support-for-multiple-authentication-methods", "destination": "/docs/guides/integrate/login/hosted-login#support-for-multiple-authentication-methods", "permanent": true },
|
"permanent": true
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#enterprise-single-sign-on", "destination": "/docs/guides/integrate/login/hosted-login#enterprise-single-sign-on", "permanent": true },
|
},
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#multi-tenancy-authentication", "destination": "/docs/guides/integrate/login/hosted-login#multi-tenancy-authentication", "permanent": true },
|
{
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#customization-options", "destination": "/docs/guides/integrate/login/hosted-login#customization-options", "permanent": true },
|
"source": "/docs/guides/integrate/human-users",
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#fast-account-switching", "destination": "/docs/guides/integrate/login/hosted-login#fast-account-switching", "permanent": true },
|
"destination": "/docs/guides/integrate/login",
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#self-service-for-users", "destination": "/docs/guides/integrate/login/hosted-login#self-service-for-users", "permanent": true },
|
"permanent": true
|
||||||
{ "source": "/docs/guides/integrate/login/login-users#password-reset", "destination": "/docs/guides/integrate/login/hosted-login#password-reset", "permanent": true }
|
},
|
||||||
]
|
{
|
||||||
|
"source": "/docs/guides/solution-scenarios/device-authorization",
|
||||||
|
"destination": "/docs/guides/integrate/login/oidc/device-authorization",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/oauth-recommended-flows",
|
||||||
|
"destination": "/docs/guides/integrate/login/oidc/oauth-recommended-flows",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login-users",
|
||||||
|
"destination": "/docs/guides/integrate/login/oidc/login-users",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/logout",
|
||||||
|
"destination": "/docs/guides/integrate/login/oidc/logout",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/solution-scenarios/onboarding",
|
||||||
|
"destination": "/docs/guides/integrate/onboarding",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/solution-scenarios/onboarding/b2b",
|
||||||
|
"destination": "/docs/guides/integrate/onboarding/b2b",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/solution-scenarios/onboarding/end-users",
|
||||||
|
"destination": "/docs/guides/integrate/onboarding/end-users",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/concepts/structure/jwt_idp",
|
||||||
|
"destination": "/docs/guides/integrate/identity-providers/jwt-idp",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/solution-scenarios/onboarding/end-users",
|
||||||
|
"destination": "/docs/guides/integrate/onboarding/end-users",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/serviceusers",
|
||||||
|
"destination": "/docs/guides/integrate/service-users/authenticate-service-users",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/private-key-jwt",
|
||||||
|
"destination": "/docs/guides/integrate/service-users/private-key-jwt",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/client-credentials",
|
||||||
|
"destination": "/docs/guides/integrate/service-users/client-credentials",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/pat",
|
||||||
|
"destination": "/docs/guides/integrate/service-users/private-access-token",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/access-zitadel-apis",
|
||||||
|
"destination": "/docs/guides/integrate/zitadel-apis/access-zitadel-apis",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/access-zitadel-system-api",
|
||||||
|
"destination": "/docs/guides/integrate/zitadel-apis/access-zitadel-system-api",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/event-api",
|
||||||
|
"destination": "/docs/guides/integrate/zitadel-apis/event-api",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/examples/call-zitadel-api/go",
|
||||||
|
"destination": "/docs/guides/integrate/zitadel-apis/example-zitadel-api-with-go",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/examples/call-zitadel-api/dot-net",
|
||||||
|
"destination": "/docs/guides/integrate/zitadel-apis/example-zitadel-api-with-dot-net",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/manage/terraform/basics",
|
||||||
|
"destination": "/docs/guides/manage/terraform-provider",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/identity-providers",
|
||||||
|
"destination": "/docs/guides/integrate/identity-providers/introduction",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#centralized-authentication-endpoint",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#centralized-authentication-endpoint",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#security-and-compliance",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#security-and-compliance",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#developer-friendly-integration",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#developer-friendly-integration",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#key-features-of-the-hosted-login",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#key-features-of-the-hosted-login",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#flexible-usernames",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#flexible-usernames",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#support-for-multiple-authentication-methods",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#support-for-multiple-authentication-methods",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#enterprise-single-sign-on",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#enterprise-single-sign-on",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#multi-tenancy-authentication",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#multi-tenancy-authentication",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#customization-options",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#customization-options",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#fast-account-switching",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#fast-account-switching",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#self-service-for-users",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#self-service-for-users",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/docs/guides/integrate/login/login-users#password-reset",
|
||||||
|
"destination": "/docs/guides/integrate/login/hosted-login#password-reset",
|
||||||
|
"permanent": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15395
docs/yarn.lock
15395
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -2,16 +2,16 @@
|
|||||||
"name": "zitadel-e2e",
|
"name": "zitadel-e2e",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"open": "npx cypress open",
|
"open": "pnpm exec cypress open",
|
||||||
"e2e": "npx cypress run",
|
"e2e": "pnpm exec cypress run",
|
||||||
"open:golang": "npm run open --",
|
"open:golang": "pnpm run open --",
|
||||||
"e2e:golang": "npm run e2e --",
|
"e2e:golang": "pnpm run e2e --",
|
||||||
"open:golangangular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 npm run open --",
|
"open:golangangular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 pnpm run open --",
|
||||||
"e2e:golangangular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 npm run e2e --",
|
"e2e:golangangular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 pnpm run e2e --",
|
||||||
"open:angulargolang": "npm run open:golangangular --",
|
"open:angulargolang": "pnpm run open:golangangular --",
|
||||||
"e2e:angulargolang": "npm run e2e:golangangular --",
|
"e2e:angulargolang": "pnpm run e2e:golangangular --",
|
||||||
"open:angular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 CYPRESS_WEBHOOK_HANDLER_HOST=host.docker.internal npm run open --",
|
"open:angular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 CYPRESS_WEBHOOK_HANDLER_HOST=host.docker.internal pnpm run open --",
|
||||||
"e2e:angular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 CYPRESS_WEBHOOK_HANDLER_HOST=host.docker.internal npm run e2e --",
|
"e2e:angular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 CYPRESS_WEBHOOK_HANDLER_HOST=host.docker.internal pnpm run e2e --",
|
||||||
"lint": "prettier --check cypress",
|
"lint": "prettier --check cypress",
|
||||||
"lint:fix": "prettier --write cypress"
|
"lint:fix": "prettier --write cypress"
|
||||||
},
|
},
|
||||||
|
25
e2e/turbo.json
Normal file
25
e2e/turbo.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"e2e": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": false
|
||||||
|
},
|
||||||
|
"e2e:golang": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": false
|
||||||
|
},
|
||||||
|
"e2e:golangangular": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": false
|
||||||
|
},
|
||||||
|
"e2e:angular": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": false
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"outputs": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1683
e2e/yarn.lock
1683
e2e/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
// This tells ESLint to load the config from the package `@zitadel/eslint-config`
|
// Use basic ESLint config since the login app has its own detailed config
|
||||||
extends: ["@zitadel/eslint-config"],
|
extends: ["eslint:recommended"],
|
||||||
settings: {
|
settings: {
|
||||||
next: {
|
next: {
|
||||||
rootDir: ["apps/*/"],
|
rootDir: ["apps/*/"],
|
||||||
|
@@ -36,8 +36,6 @@ We think the easiest path of getting up and running, is the following:
|
|||||||
- `login`: The login UI used by ZITADEL Cloud, powered by Next.js
|
- `login`: The login UI used by ZITADEL Cloud, powered by Next.js
|
||||||
- `@zitadel/client`: shared client utilities for node and browser environments
|
- `@zitadel/client`: shared client utilities for node and browser environments
|
||||||
- `@zitadel/proto`: Protocol Buffers (proto) definitions used by ZITADEL projects
|
- `@zitadel/proto`: Protocol Buffers (proto) definitions used by ZITADEL projects
|
||||||
- `@zitadel/tsconfig`: shared `tsconfig.json`s used throughout the monorepo
|
|
||||||
- `@zitadel/eslint-config`: ESLint preset
|
|
||||||
|
|
||||||
Each package and app is 100% [TypeScript](https://www.typescriptlang.org/).
|
Each package and app is 100% [TypeScript](https://www.typescriptlang.org/).
|
||||||
|
|
||||||
|
19
login/apps/login/.eslintrc.cjs
Executable file → Normal file
19
login/apps/login/.eslintrc.cjs
Executable file → Normal file
@@ -1,12 +1,21 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ["next/core-web-vitals"],
|
parser: "@typescript-eslint/parser",
|
||||||
ignorePatterns: ["external/**/*.ts"],
|
extends: ["next", "prettier"],
|
||||||
|
plugins: ["@typescript-eslint"],
|
||||||
rules: {
|
rules: {
|
||||||
"@next/next/no-html-link-for-pages": "off",
|
"@next/next/no-html-link-for-pages": "off",
|
||||||
|
"@next/next/no-img-element": "off",
|
||||||
|
"react/no-unescaped-entities": "off",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
||||||
|
"no-undef": "off",
|
||||||
},
|
},
|
||||||
settings: {
|
parserOptions: {
|
||||||
react: {
|
ecmaVersion: "latest",
|
||||||
version: "detect",
|
sourceType: "module",
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
},
|
},
|
||||||
|
project: "./tsconfig.json",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
7
login/apps/login/.gitignore
vendored
7
login/apps/login/.gitignore
vendored
@@ -1,3 +1,10 @@
|
|||||||
custom-config.js
|
custom-config.js
|
||||||
.env*.local
|
.env*.local
|
||||||
standalone
|
standalone
|
||||||
|
|
||||||
|
# Generated standalone files (temporary)
|
||||||
|
*.generated.*
|
||||||
|
package.monorepo.backup.json
|
||||||
|
|
||||||
|
# TypeScript build info
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
54
login/apps/login/Dockerfile
Normal file
54
login/apps/login/Dockerfile
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Dockerfile for standalone ZITADEL Login UI
|
||||||
|
FROM node:18-alpine AS base
|
||||||
|
|
||||||
|
# Install dependencies only when needed
|
||||||
|
FROM base AS deps
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Prepare standalone and install dependencies
|
||||||
|
COPY prepare-standalone.sh package*.json ./
|
||||||
|
COPY *.standalone.* ./
|
||||||
|
RUN ./prepare-standalone.sh
|
||||||
|
|
||||||
|
# Rebuild the source code only when needed
|
||||||
|
FROM base AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Prepare standalone configs
|
||||||
|
RUN ./prepare-standalone.sh --no-install
|
||||||
|
|
||||||
|
# Build application
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
RUN npm run build:standalone
|
||||||
|
|
||||||
|
# Production image, copy all the files and run next
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
|
# Set the correct permission for prerender cache
|
||||||
|
RUN mkdir .next
|
||||||
|
RUN chown nextjs:nodejs .next
|
||||||
|
|
||||||
|
# Automatically leverage output traces to reduce image size
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENV PORT 3000
|
||||||
|
ENV HOSTNAME "0.0.0.0"
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
@@ -3,10 +3,10 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm next dev --turbopack",
|
"dev": "next dev",
|
||||||
"test:unit": "pnpm vitest",
|
"dev:turbo": "next dev --turbopack",
|
||||||
|
"test:unit": "pnpm vitest --run",
|
||||||
"test:unit:standalone": "pnpm test:unit",
|
"test:unit:standalone": "pnpm test:unit",
|
||||||
"test:unit:watch": "pnpm test:unit --watch",
|
|
||||||
"lint": "pnpm exec next lint && pnpm exec prettier --check .",
|
"lint": "pnpm exec next lint && pnpm exec prettier --check .",
|
||||||
"lint:fix": "pnpm exec prettier --write .",
|
"lint:fix": "pnpm exec prettier --write .",
|
||||||
"lint-staged": "lint-staged",
|
"lint-staged": "lint-staged",
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"build:login:standalone": "NEXT_PUBLIC_BASE_PATH=/ui/v2/login NEXT_OUTPUT_MODE=standalone pnpm build",
|
"build:login:standalone": "NEXT_PUBLIC_BASE_PATH=/ui/v2/login NEXT_OUTPUT_MODE=standalone pnpm build",
|
||||||
"start": "pnpm build && pnpm exec next start",
|
"start": "pnpm build && pnpm exec next start",
|
||||||
"start:built": "pnpm exec next start",
|
"start:built": "pnpm exec next start",
|
||||||
"clean": "pnpm mock:stop && rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
"pre-commit": "lint-staged"
|
"pre-commit": "lint-staged"
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
"@heroicons/react": "2.1.3",
|
"@heroicons/react": "2.1.3",
|
||||||
"@tailwindcss/forms": "0.5.7",
|
"@tailwindcss/forms": "0.5.7",
|
||||||
"@vercel/analytics": "^1.2.2",
|
"@vercel/analytics": "^1.2.2",
|
||||||
"@zitadel/client": "workspace:*",
|
"@zitadel/client": "latest",
|
||||||
"@zitadel/proto": "workspace:*",
|
"@zitadel/proto": "latest",
|
||||||
"clsx": "1.2.1",
|
"clsx": "1.2.1",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
"uuid": "^11.1.0"
|
"uuid": "^11.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/eslint-parser": "^7.23.0",
|
||||||
"@bufbuild/buf": "^1.53.0",
|
"@bufbuild/buf": "^1.53.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
@@ -55,21 +56,25 @@
|
|||||||
"@types/react-dom": "19.1.2",
|
"@types/react-dom": "19.1.2",
|
||||||
"@types/tinycolor2": "1.4.3",
|
"@types/tinycolor2": "1.4.3",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
"@vercel/git-hooks": "1.0.0",
|
"@vercel/git-hooks": "1.0.0",
|
||||||
"@zitadel/eslint-config": "workspace:*",
|
|
||||||
"@zitadel/prettier-config": "workspace:*",
|
|
||||||
"@zitadel/tailwind-config": "workspace:*",
|
|
||||||
"@zitadel/tsconfig": "workspace:*",
|
|
||||||
"autoprefixer": "10.4.21",
|
"autoprefixer": "10.4.21",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-next": "15.4.0-canary.86",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"grpc-tools": "1.13.0",
|
"grpc-tools": "1.13.0",
|
||||||
"jsdom": "^26.1.0",
|
"jsdom": "^26.1.0",
|
||||||
"lint-staged": "15.5.1",
|
"lint-staged": "15.5.1",
|
||||||
"make-dir-cli": "4.0.0",
|
"make-dir-cli": "4.0.0",
|
||||||
"postcss": "8.5.3",
|
"postcss": "8.5.3",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"prettier-plugin-organize-imports": "^3.2.0",
|
||||||
"prettier-plugin-tailwindcss": "0.6.11",
|
"prettier-plugin-tailwindcss": "0.6.11",
|
||||||
"sass": "^1.87.0",
|
"sass": "^1.87.0",
|
||||||
"tailwindcss": "3.4.14",
|
"tailwindcss": "3.4.14",
|
||||||
"ts-proto": "^2.7.0",
|
"ts-proto": "^2.7.0",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3",
|
||||||
|
"vitest": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1 +1,11 @@
|
|||||||
export { default } from "@zitadel/prettier-config";
|
export default {
|
||||||
|
printWidth: 80,
|
||||||
|
tabWidth: 2,
|
||||||
|
useTabs: false,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: false,
|
||||||
|
trailingComma: "all",
|
||||||
|
bracketSpacing: true,
|
||||||
|
arrowParens: "always",
|
||||||
|
plugins: ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"],
|
||||||
|
};
|
||||||
|
@@ -10,7 +10,7 @@ import {
|
|||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { UserPlusIcon } from "@heroicons/react/24/outline";
|
import { UserPlusIcon } from "@heroicons/react/24/outline";
|
||||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||||
import { getLocale } from "next-intl/server";
|
// import { getLocale } from "next-intl/server";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
@@ -33,7 +33,6 @@ export default async function Page(props: {
|
|||||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||||
}) {
|
}) {
|
||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const locale = getLocale();
|
|
||||||
|
|
||||||
const requestId = searchParams?.requestId;
|
const requestId = searchParams?.requestId;
|
||||||
const organization = searchParams?.organization;
|
const organization = searchParams?.organization;
|
||||||
@@ -78,11 +77,11 @@ export default async function Page(props: {
|
|||||||
<Translated i18nKey="description" namespace="accounts" />
|
<Translated i18nKey="description" namespace="accounts" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col w-full space-y-2">
|
<div className="flex w-full flex-col space-y-2">
|
||||||
<SessionsList sessions={sessions} requestId={requestId} />
|
<SessionsList sessions={sessions} requestId={requestId} />
|
||||||
<Link href={`/loginname?` + params}>
|
<Link href={`/loginname?` + params}>
|
||||||
<div className="flex flex-row items-center py-3 px-4 hover:bg-black/10 dark:hover:bg-white/10 rounded-md transition-all">
|
<div className="flex flex-row items-center rounded-md px-4 py-3 transition-all hover:bg-black/10 dark:hover:bg-white/10">
|
||||||
<div className="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
|
<div className="mr-4 flex h-8 w-8 flex-row items-center justify-center rounded-full bg-black/5 dark:bg-white/5">
|
||||||
<UserPlusIcon className="h-5 w-5" />
|
<UserPlusIcon className="h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
|
@@ -18,7 +18,7 @@ import {
|
|||||||
listAuthenticationMethodTypes,
|
listAuthenticationMethodTypes,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
import { getLocale } from "next-intl/server";
|
// import { getLocale } from "next-intl/server";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
@@ -26,7 +26,6 @@ export default async function Page(props: {
|
|||||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||||
}) {
|
}) {
|
||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const locale = getLocale();
|
|
||||||
|
|
||||||
const { loginName, requestId, organization, sessionId } = searchParams;
|
const { loginName, requestId, organization, sessionId } = searchParams;
|
||||||
|
|
||||||
@@ -193,7 +192,7 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
{loginSettings?.allowExternalIdp && !!identityProviders.length && (
|
{loginSettings?.allowExternalIdp && !!identityProviders.length && (
|
||||||
<>
|
<>
|
||||||
<div className="py-3 flex flex-col">
|
<div className="flex flex-col py-3">
|
||||||
<p className="ztdl-p text-center">
|
<p className="ztdl-p text-center">
|
||||||
<Translated i18nKey="linkWithIDP" namespace="authenticator" />
|
<Translated i18nKey="linkWithIDP" namespace="authenticator" />
|
||||||
</p>
|
</p>
|
||||||
|
@@ -27,13 +27,13 @@ export default async function RootLayout({
|
|||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<div
|
<div
|
||||||
className={`relative min-h-screen bg-background-light-600 dark:bg-background-dark-600 flex flex-col justify-center`}
|
className={`relative flex min-h-screen flex-col justify-center bg-background-light-600 dark:bg-background-dark-600`}
|
||||||
>
|
>
|
||||||
<div className="relative mx-auto max-w-[440px] py-8 w-full">
|
<div className="relative mx-auto w-full max-w-[440px] py-8">
|
||||||
<Skeleton>
|
<Skeleton>
|
||||||
<div className="h-40"></div>
|
<div className="h-40"></div>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<div className="flex flex-row justify-end py-4 items-center space-x-4">
|
<div className="flex flex-row items-center justify-end space-x-4 py-4">
|
||||||
<Theme />
|
<Theme />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,11 +42,11 @@ export default async function RootLayout({
|
|||||||
>
|
>
|
||||||
<LanguageProvider>
|
<LanguageProvider>
|
||||||
<div
|
<div
|
||||||
className={`relative min-h-screen bg-background-light-600 dark:bg-background-dark-600 flex flex-col justify-center`}
|
className={`relative flex min-h-screen flex-col justify-center bg-background-light-600 dark:bg-background-dark-600`}
|
||||||
>
|
>
|
||||||
<div className="relative mx-auto max-w-[440px] py-8 w-full ">
|
<div className="relative mx-auto w-full max-w-[440px] py-8">
|
||||||
{children}
|
{children}
|
||||||
<div className="flex flex-row justify-end py-4 items-center space-x-4">
|
<div className="flex flex-row items-center justify-end space-x-4 py-4">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
<Theme />
|
<Theme />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -79,7 +79,7 @@ export default async function Page(props: {
|
|||||||
></UsernameForm>
|
></UsernameForm>
|
||||||
|
|
||||||
{identityProviders && loginSettings?.allowExternalIdp && (
|
{identityProviders && loginSettings?.allowExternalIdp && (
|
||||||
<div className="w-full pt-6 pb-4">
|
<div className="w-full pb-4 pt-6">
|
||||||
<SignInWithIdp
|
<SignInWithIdp
|
||||||
identityProviders={identityProviders}
|
identityProviders={identityProviders}
|
||||||
requestId={requestId}
|
requestId={requestId}
|
||||||
|
@@ -72,7 +72,7 @@ export default async function Page(props: {
|
|||||||
<Translated i18nKey="description" namespace="logout" />
|
<Translated i18nKey="description" namespace="logout" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col w-full space-y-2">
|
<div className="flex w-full flex-col space-y-2">
|
||||||
<SessionsClearList
|
<SessionsClearList
|
||||||
sessions={sessions}
|
sessions={sessions}
|
||||||
logoutHint={logoutHint}
|
logoutHint={logoutHint}
|
||||||
|
@@ -11,7 +11,6 @@ import {
|
|||||||
getLoginSettings,
|
getLoginSettings,
|
||||||
getSession,
|
getSession,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { getLocale } from "next-intl/server";
|
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
@@ -20,7 +19,6 @@ export default async function Page(props: {
|
|||||||
}) {
|
}) {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const locale = getLocale();
|
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
@@ -13,7 +13,7 @@ export default async function Page(props: {
|
|||||||
}) {
|
}) {
|
||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
|
|
||||||
const { loginName, prompt, organization, requestId, userId } = searchParams;
|
const { loginName, prompt, organization, requestId } = searchParams;
|
||||||
|
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
@@ -54,7 +54,7 @@ export default async function Page(props: {
|
|||||||
<span>
|
<span>
|
||||||
<Translated i18nKey="set.info.description" namespace="passkey" />
|
<Translated i18nKey="set.info.description" namespace="passkey" />
|
||||||
<a
|
<a
|
||||||
className="text-primary-light-500 dark:text-primary-dark-500 hover:text-primary-light-300 hover:dark:text-primary-dark-300"
|
className="text-primary-light-500 hover:text-primary-light-300 dark:text-primary-dark-500 hover:dark:text-primary-dark-300"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://zitadel.com/docs/guides/manage/user/reg-create-user#with-passwordless"
|
href="https://zitadel.com/docs/guides/manage/user/reg-create-user#with-passwordless"
|
||||||
>
|
>
|
||||||
|
@@ -13,14 +13,12 @@ import {
|
|||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||||
import { getLocale } from "next-intl/server";
|
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||||
}) {
|
}) {
|
||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const locale = getLocale();
|
|
||||||
|
|
||||||
const { userId, loginName, organization, requestId, code, initial } =
|
const { userId, loginName, organization, requestId, code, initial } =
|
||||||
searchParams;
|
searchParams;
|
||||||
|
@@ -14,14 +14,12 @@ import {
|
|||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||||
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||||
import { getLocale } from "next-intl/server";
|
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||||
}) {
|
}) {
|
||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const locale = getLocale();
|
|
||||||
|
|
||||||
let { firstname, lastname, email, organization, requestId } = searchParams;
|
let { firstname, lastname, email, organization, requestId } = searchParams;
|
||||||
|
|
||||||
@@ -117,7 +115,7 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
{loginSettings?.allowExternalIdp && !!identityProviders.length && (
|
{loginSettings?.allowExternalIdp && !!identityProviders.length && (
|
||||||
<>
|
<>
|
||||||
<div className="py-3 flex flex-col items-center">
|
<div className="flex flex-col items-center py-3">
|
||||||
<p className="ztdl-p text-center">
|
<p className="ztdl-p text-center">
|
||||||
<Translated i18nKey="orUseIDP" namespace="register" />
|
<Translated i18nKey="orUseIDP" namespace="register" />
|
||||||
</p>
|
</p>
|
||||||
|
@@ -7,14 +7,12 @@ import { getSessionCookieById } from "@/lib/cookies";
|
|||||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import { getBrandingSettings, getSession } from "@/lib/zitadel";
|
import { getBrandingSettings, getSession } from "@/lib/zitadel";
|
||||||
import { getLocale } from "next-intl/server";
|
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||||
}) {
|
}) {
|
||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const locale = getLocale();
|
|
||||||
|
|
||||||
const { loginName, requestId, sessionId, organization } = searchParams;
|
const { loginName, requestId, sessionId, organization } = searchParams;
|
||||||
|
|
||||||
|
@@ -6,14 +6,12 @@ import { UserAvatar } from "@/components/user-avatar";
|
|||||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import { getBrandingSettings } from "@/lib/zitadel";
|
import { getBrandingSettings } from "@/lib/zitadel";
|
||||||
import { getLocale } from "next-intl/server";
|
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||||
}) {
|
}) {
|
||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const locale = getLocale();
|
|
||||||
|
|
||||||
const { loginName, organization, requestId, checkAfter } = searchParams;
|
const { loginName, organization, requestId, checkAfter } = searchParams;
|
||||||
|
|
||||||
|
@@ -8,12 +8,10 @@ import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
|||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import { getBrandingSettings, getUserByID } from "@/lib/zitadel";
|
import { getBrandingSettings, getUserByID } from "@/lib/zitadel";
|
||||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||||
import { getLocale } from "next-intl/server";
|
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
export default async function Page(props: { searchParams: Promise<any> }) {
|
export default async function Page(props: { searchParams: Promise<any> }) {
|
||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const locale = getLocale();
|
|
||||||
|
|
||||||
const { userId, loginName, code, organization, requestId, invite, send } =
|
const { userId, loginName, code, organization, requestId, invite, send } =
|
||||||
searchParams;
|
searchParams;
|
||||||
@@ -136,7 +134,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{id && send && (
|
{id && send && (
|
||||||
<div className="py-4 w-full">
|
<div className="w-full py-4">
|
||||||
<Alert type={AlertType.INFO}>
|
<Alert type={AlertType.INFO}>
|
||||||
<Translated i18nKey="verify.codeSent" namespace="verify" />
|
<Translated i18nKey="verify.codeSent" namespace="verify" />
|
||||||
</Alert>
|
</Alert>
|
||||||
|
@@ -3,11 +3,7 @@ import { Translated } from "@/components/translated";
|
|||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import { getBrandingSettings, getUserByID } from "@/lib/zitadel";
|
||||||
getBrandingSettings,
|
|
||||||
getLoginSettings,
|
|
||||||
getUserByID,
|
|
||||||
} from "@/lib/zitadel";
|
|
||||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
@@ -31,14 +27,6 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
|||||||
console.warn("Error loading session:", error);
|
console.warn("Error loading session:", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
let loginSettings;
|
|
||||||
if (!requestId) {
|
|
||||||
loginSettings = await getLoginSettings({
|
|
||||||
serviceUrl,
|
|
||||||
organization,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = userId ?? sessionFactors?.factors?.user?.id;
|
const id = userId ?? sessionFactors?.factors?.user?.id;
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
@@ -11,7 +11,7 @@ export function AddressBar({ domain }: Props) {
|
|||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center space-x-2 p-3.5 lg:px-5 lg:py-3 overflow-hidden">
|
<div className="flex items-center space-x-2 overflow-hidden p-3.5 lg:px-5 lg:py-3">
|
||||||
<div className="text-gray-600">
|
<div className="text-gray-600">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -27,7 +27,7 @@ export function AddressBar({ domain }: Props) {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-1 text-sm font-medium">
|
<div className="flex space-x-1 text-sm font-medium">
|
||||||
<div className="max-w-[150px] px-2 overflow-hidden text-gray-500 text-ellipsis">
|
<div className="max-w-[150px] overflow-hidden text-ellipsis px-2 text-gray-500">
|
||||||
<span className="whitespace-nowrap">{domain}</span>
|
<span className="whitespace-nowrap">{domain}</span>
|
||||||
</div>
|
</div>
|
||||||
{pathname ? (
|
{pathname ? (
|
||||||
|
@@ -26,7 +26,7 @@ export function Alert({ children, type = AlertType.ALERT }: Props) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"flex flex-row items-center justify-center border rounded-md py-2 pr-2 scroll-px-40",
|
"flex scroll-px-40 flex-row items-center justify-center rounded-md border py-2 pr-2",
|
||||||
{
|
{
|
||||||
[yellow]: type === AlertType.ALERT,
|
[yellow]: type === AlertType.ALERT,
|
||||||
[neutral]: type === AlertType.INFO,
|
[neutral]: type === AlertType.INFO,
|
||||||
@@ -34,12 +34,12 @@ export function Alert({ children, type = AlertType.ALERT }: Props) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{type === AlertType.ALERT && (
|
{type === AlertType.ALERT && (
|
||||||
<ExclamationTriangleIcon className="flex-shrink-0 h-5 w-5 mr-2 ml-2" />
|
<ExclamationTriangleIcon className="ml-2 mr-2 h-5 w-5 flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
{type === AlertType.INFO && (
|
{type === AlertType.INFO && (
|
||||||
<InformationCircleIcon className="flex-shrink-0 h-5 w-5 mr-2 ml-2" />
|
<InformationCircleIcon className="ml-2 mr-2 h-5 w-5 flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
<span className="text-sm w-full ">{children}</span>
|
<span className="w-full text-sm">{children}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,7 @@ export function AppAvatar({ appName, imageUrl, shadow }: AvatarProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-[100px] h-[100px] flex justify-center items-center cursor-default pointer-events-none group-focus:outline-none group-focus:ring-2 transition-colors duration-200 dark:group-focus:ring-offset-blue bg-primary-light-500 text-primary-light-contrast-500 hover:bg-primary-light-400 hover:dark:bg-primary-dark-500 group-focus:ring-primary-light-200 dark:group-focus:ring-primary-dark-400 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 dark:text-blue rounded-full ${
|
className={`dark:group-focus:ring-offset-blue dark:text-blue pointer-events-none flex h-[100px] w-[100px] cursor-default items-center justify-center rounded-full bg-primary-light-500 text-primary-light-contrast-500 transition-colors duration-200 hover:bg-primary-light-400 group-focus:outline-none group-focus:ring-2 group-focus:ring-primary-light-200 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 hover:dark:bg-primary-dark-500 dark:group-focus:ring-primary-dark-400 ${
|
||||||
shadow ? "shadow" : ""
|
shadow ? "shadow" : ""
|
||||||
}`}
|
}`}
|
||||||
style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark}
|
style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark}
|
||||||
@@ -37,11 +37,11 @@ export function AppAvatar({ appName, imageUrl, shadow }: AvatarProps) {
|
|||||||
height={48}
|
height={48}
|
||||||
width={48}
|
width={48}
|
||||||
alt="avatar"
|
alt="avatar"
|
||||||
className="w-full h-full border border-divider-light dark:border-divider-dark rounded-full"
|
className="h-full w-full rounded-full border border-divider-light dark:border-divider-dark"
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className={`uppercase text-3xl`}>{credentials}</span>
|
<span className={`text-3xl uppercase`}>{credentials}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -35,12 +35,12 @@ export const TOTP = (alreadyAdded: boolean, link: string) => {
|
|||||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"flex items-center font-medium",
|
||||||
alreadyAdded ? "opacity-50" : "",
|
alreadyAdded ? "opacity-50" : "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="h-8 w-8 transform -translate-x-[2px] mr-4 fill-current text-black dark:text-white"
|
className="mr-4 h-8 w-8 -translate-x-[2px] transform fill-current text-black dark:text-white"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
@@ -63,7 +63,7 @@ export const U2F = (alreadyAdded: boolean, link: string) => {
|
|||||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"flex items-center font-medium",
|
||||||
alreadyAdded ? "" : "",
|
alreadyAdded ? "" : "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -73,7 +73,7 @@ export const U2F = (alreadyAdded: boolean, link: string) => {
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="w-8 h-8 mr-4"
|
className="mr-4 h-8 w-8"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
@@ -97,12 +97,12 @@ export const EMAIL = (alreadyAdded: boolean, link: string) => {
|
|||||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"flex items-center font-medium",
|
||||||
alreadyAdded ? "" : "",
|
alreadyAdded ? "" : "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-8 h-8 mr-4"
|
className="mr-4 h-8 w-8"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -132,12 +132,12 @@ export const SMS = (alreadyAdded: boolean, link: string) => {
|
|||||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"flex items-center font-medium",
|
||||||
alreadyAdded ? "" : "",
|
alreadyAdded ? "" : "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-8 h-8 mr-4"
|
className="mr-4 h-8 w-8"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -166,7 +166,7 @@ export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
|
|||||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"flex items-center font-medium",
|
||||||
alreadyAdded ? "" : "",
|
alreadyAdded ? "" : "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -176,7 +176,7 @@ export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="w-8 h-8 mr-4"
|
className="mr-4 h-8 w-8"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
@@ -200,12 +200,12 @@ export const PASSWORD = (alreadyAdded: boolean, link: string) => {
|
|||||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-medium flex items-center",
|
"flex items-center font-medium",
|
||||||
alreadyAdded ? "" : "",
|
alreadyAdded ? "" : "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-8 h-7 mr-4 fill-current"
|
className="mr-4 h-7 w-8 fill-current"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
@@ -225,9 +225,9 @@ export const PASSWORD = (alreadyAdded: boolean, link: string) => {
|
|||||||
|
|
||||||
function Setup() {
|
function Setup() {
|
||||||
return (
|
return (
|
||||||
<div className="transform absolute right-2 top-0">
|
<div className="absolute right-2 top-0 transform">
|
||||||
<StateBadge evenPadding={true} state={BadgeState.Success}>
|
<StateBadge evenPadding={true} state={BadgeState.Success}>
|
||||||
<CheckIcon className="w-4 h-4" />
|
<CheckIcon className="h-4 w-4" />
|
||||||
</StateBadge>
|
</StateBadge>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -34,20 +34,18 @@ export function AuthenticationMethodRadio({
|
|||||||
className={({ active, checked }) =>
|
className={({ active, checked }) =>
|
||||||
`${
|
`${
|
||||||
active
|
active
|
||||||
? "ring-2 ring-opacity-60 ring-primary-light-500 dark:ring-white/20"
|
? "ring-2 ring-primary-light-500 ring-opacity-60 dark:ring-white/20"
|
||||||
: ""
|
: ""
|
||||||
}
|
} ${
|
||||||
${
|
checked
|
||||||
checked
|
? "bg-background-light-400 ring-2 ring-primary-light-500 dark:bg-background-dark-400 dark:ring-primary-dark-500"
|
||||||
? "bg-background-light-400 dark:bg-background-dark-400 ring-2 ring-primary-light-500 dark:ring-primary-dark-500"
|
: "bg-background-light-400 dark:bg-background-dark-400"
|
||||||
: "bg-background-light-400 dark:bg-background-dark-400"
|
} boder-divider-light relative flex h-full flex-1 cursor-pointer rounded-lg border px-5 py-4 hover:shadow-lg focus:outline-none dark:border-divider-dark dark:hover:bg-white/10`
|
||||||
}
|
|
||||||
h-full flex-1 relative border boder-divider-light dark:border-divider-dark flex cursor-pointer rounded-lg px-5 py-4 focus:outline-none hover:shadow-lg dark:hover:bg-white/10`
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ active, checked }) => (
|
{({ active, checked }) => (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col items-center w-full text-sm">
|
<div className="flex w-full flex-col items-center text-sm">
|
||||||
{method === "passkey" && (
|
{method === "passkey" && (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -55,7 +53,7 @@ export function AuthenticationMethodRadio({
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="w-8 h-8 mb-3"
|
className="mb-3 h-8 w-8"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
@@ -66,7 +64,7 @@ export function AuthenticationMethodRadio({
|
|||||||
)}
|
)}
|
||||||
{method === "password" && (
|
{method === "password" && (
|
||||||
<svg
|
<svg
|
||||||
className="w-8 h-8 mb-3 fill-current"
|
className="mb-3 h-8 w-8 fill-current"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
@@ -76,7 +74,7 @@ export function AuthenticationMethodRadio({
|
|||||||
)}
|
)}
|
||||||
<RadioGroup.Label
|
<RadioGroup.Label
|
||||||
as="p"
|
as="p"
|
||||||
className={`font-medium ${checked ? "" : ""}`}
|
className={`font-medium ${checked ? "" : ""}`}
|
||||||
>
|
>
|
||||||
{method === AuthenticationMethod.Passkey && (
|
{method === AuthenticationMethod.Passkey && (
|
||||||
<Translated
|
<Translated
|
||||||
|
@@ -64,16 +64,16 @@ export function Avatar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-full h-full flex-shrink-0 flex justify-center items-center cursor-default pointer-events-none group-focus:outline-none group-focus:ring-2 transition-colors duration-200 dark:group-focus:ring-offset-blue bg-primary-light-500 text-primary-light-contrast-500 hover:bg-primary-light-400 hover:dark:bg-primary-dark-500 group-focus:ring-primary-light-200 dark:group-focus:ring-primary-dark-400 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 dark:text-blue rounded-full ${
|
className={`dark:group-focus:ring-offset-blue dark:text-blue pointer-events-none flex h-full w-full flex-shrink-0 cursor-default items-center justify-center rounded-full bg-primary-light-500 text-primary-light-contrast-500 transition-colors duration-200 hover:bg-primary-light-400 group-focus:outline-none group-focus:ring-2 group-focus:ring-primary-light-200 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 hover:dark:bg-primary-dark-500 dark:group-focus:ring-primary-dark-400 ${
|
||||||
shadow ? "shadow" : ""
|
shadow ? "shadow" : ""
|
||||||
} ${
|
} ${
|
||||||
size === "large"
|
size === "large"
|
||||||
? "h-20 w-20 font-normal"
|
? "h-20 w-20 font-normal"
|
||||||
: size === "base"
|
: size === "base"
|
||||||
? "w-[38px] h-[38px] font-bold"
|
? "h-[38px] w-[38px] font-bold"
|
||||||
: size === "small"
|
: size === "small"
|
||||||
? "!w-[32px] !h-[32px] font-bold text-[13px]"
|
? "!h-[32px] !w-[32px] text-[13px] font-bold"
|
||||||
: "w-12 h-12"
|
: "h-12 w-12"
|
||||||
}`}
|
}`}
|
||||||
style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark}
|
style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark}
|
||||||
>
|
>
|
||||||
@@ -82,7 +82,7 @@ export function Avatar({
|
|||||||
height={48}
|
height={48}
|
||||||
width={48}
|
width={48}
|
||||||
alt="avatar"
|
alt="avatar"
|
||||||
className="w-full h-full border border-divider-light dark:border-divider-dark rounded-full"
|
className="h-full w-full rounded-full border border-divider-light dark:border-divider-dark"
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@@ -33,8 +33,7 @@ export const getButtonClasses = (
|
|||||||
color: ButtonColors,
|
color: ButtonColors,
|
||||||
) =>
|
) =>
|
||||||
clsx({
|
clsx({
|
||||||
"box-border font-normal leading-36px text-14px inline-flex items-center rounded-md focus:outline-none transition-colors transition-shadow duration-300":
|
"box-border font-normal leading-36px text-14px inline-flex items-center rounded-md focus:outline-none transition-colors transition-shadow duration-300": true,
|
||||||
true,
|
|
||||||
"shadow hover:shadow-xl active:shadow-xl disabled:border-none disabled:bg-gray-300 disabled:text-gray-600 disabled:shadow-none disabled:cursor-not-allowed disabled:dark:bg-gray-800 disabled:dark:text-gray-900":
|
"shadow hover:shadow-xl active:shadow-xl disabled:border-none disabled:bg-gray-300 disabled:text-gray-600 disabled:shadow-none disabled:cursor-not-allowed disabled:dark:bg-gray-800 disabled:dark:text-gray-900":
|
||||||
variant === ButtonVariants.Primary,
|
variant === ButtonVariants.Primary,
|
||||||
"bg-primary-light-500 dark:bg-primary-dark-500 hover:bg-primary-light-400 hover:dark:bg-primary-dark-400 text-primary-light-contrast-500 dark:text-primary-dark-contrast-500":
|
"bg-primary-light-500 dark:bg-primary-dark-500 hover:bg-primary-light-400 hover:dark:bg-primary-dark-400 text-primary-light-contrast-500 dark:text-primary-dark-contrast-500":
|
||||||
|
@@ -149,7 +149,7 @@ export function ChangePasswordForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="w-full">
|
<form className="w-full">
|
||||||
<div className="pt-4 grid grid-cols-1 gap-4 mb-4">
|
<div className="mb-4 grid grid-cols-1 gap-4 pt-4">
|
||||||
<div className="">
|
<div className="">
|
||||||
<TextInput
|
<TextInput
|
||||||
type="password"
|
type="password"
|
||||||
@@ -202,7 +202,7 @@ export function ChangePasswordForm({
|
|||||||
onClick={handleSubmit(submitChange)}
|
onClick={handleSubmit(submitChange)}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
|
||||||
<Translated i18nKey="change.submit" namespace="password" />
|
<Translated i18nKey="change.submit" namespace="password" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -36,7 +36,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex items-start">
|
<div className="relative flex items-start">
|
||||||
<div className="flex items-center h-5">
|
<div className="flex h-5 items-center">
|
||||||
<div className="box-sizing block">
|
<div className="box-sizing block">
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -48,7 +48,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"form-checkbox rounded border-gray-300 text-primary-light-500 dark:text-primary-dark-500 shadow-sm focus:border-indigo-300 focus:ring focus:ring-offset-0 focus:ring-indigo-200 focus:ring-opacity-50",
|
"form-checkbox rounded border-gray-300 text-primary-light-500 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 focus:ring-offset-0 dark:text-primary-dark-500",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
@@ -25,7 +25,7 @@ export function ChooseAuthenticatorToLogin({
|
|||||||
<Translated i18nKey="chooseAlternativeMethod" namespace="idp" />
|
<Translated i18nKey="chooseAlternativeMethod" namespace="idp" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="grid grid-cols-1 gap-5 w-full pt-4">
|
<div className="grid w-full grid-cols-1 gap-5 pt-4">
|
||||||
{authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
{authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
||||||
loginSettings?.allowUsernamePassword &&
|
loginSettings?.allowUsernamePassword &&
|
||||||
PASSWORD(false, "/password?" + params)}
|
PASSWORD(false, "/password?" + params)}
|
||||||
|
@@ -37,7 +37,7 @@ export function ChooseAuthenticatorToSetup({
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-5 w-full pt-4">
|
<div className="grid w-full grid-cols-1 gap-5 pt-4">
|
||||||
{!authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
{!authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
||||||
loginSettings.allowUsernamePassword &&
|
loginSettings.allowUsernamePassword &&
|
||||||
PASSWORD(false, "/password/set?" + params)}
|
PASSWORD(false, "/password/set?" + params)}
|
||||||
|
@@ -58,7 +58,7 @@ export function ChooseSecondFactorToSetup({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-1 gap-5 w-full pt-4">
|
<div className="grid w-full grid-cols-1 gap-5 pt-4">
|
||||||
{loginSettings.secondFactors.map((factor) => {
|
{loginSettings.secondFactors.map((factor) => {
|
||||||
switch (factor) {
|
switch (factor) {
|
||||||
case SecondFactorType.OTP:
|
case SecondFactorType.OTP:
|
||||||
@@ -94,7 +94,7 @@ export function ChooseSecondFactorToSetup({
|
|||||||
</div>
|
</div>
|
||||||
{!force && (
|
{!force && (
|
||||||
<button
|
<button
|
||||||
className="transition-all text-sm hover:text-primary-light-500 dark:hover:text-primary-dark-500"
|
className="text-sm transition-all hover:text-primary-light-500 dark:hover:text-primary-dark-500"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const resp = await skipMFAAndContinueWithNextUrl({
|
const resp = await skipMFAAndContinueWithNextUrl({
|
||||||
userId,
|
userId,
|
||||||
|
@@ -34,7 +34,7 @@ export function ChooseSecondFactor({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-5 w-full pt-4">
|
<div className="grid w-full grid-cols-1 gap-5 pt-4">
|
||||||
{userMethods.map((method, i) => {
|
{userMethods.map((method, i) => {
|
||||||
return (
|
return (
|
||||||
<div key={"method-" + i}>
|
<div key={"method-" + i}>
|
||||||
|
@@ -47,16 +47,16 @@ export function ConsentScreen({
|
|||||||
const scopes = scope?.filter((s) => !!s);
|
const scopes = scope?.filter((s) => !!s);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-4 w-full flex flex-col items-center space-y-4">
|
<div className="flex w-full flex-col items-center space-y-4 pt-4">
|
||||||
<ul className="list-disc space-y-2 w-full">
|
<ul className="w-full list-disc space-y-2">
|
||||||
{scopes?.length === 0 && (
|
{scopes?.length === 0 && (
|
||||||
<span className="w-full text-sm flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light py-2 px-4 rounded-md transition-all">
|
<span className="flex w-full flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 text-sm transition-all dark:bg-background-dark-400">
|
||||||
<Translated i18nKey="device.scope.openid" namespace="device" />
|
<Translated i18nKey="device.scope.openid" namespace="device" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{scopes?.map((s) => {
|
{scopes?.map((s) => {
|
||||||
const translationKey = `device.scope.${s}`;
|
const translationKey = `device.scope.${s}`;
|
||||||
const description = t(translationKey, null);
|
const description = t(translationKey);
|
||||||
|
|
||||||
// Check if the key itself is returned and provide a fallback
|
// Check if the key itself is returned and provide a fallback
|
||||||
const resolvedDescription =
|
const resolvedDescription =
|
||||||
@@ -65,7 +65,7 @@ export function ConsentScreen({
|
|||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={s}
|
key={s}
|
||||||
className="w-full text-sm flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light py-2 px-4 rounded-md transition-all"
|
className="flex w-full flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 text-sm transition-all dark:bg-background-dark-400"
|
||||||
>
|
>
|
||||||
<span>{resolvedDescription}</span>
|
<span>{resolvedDescription}</span>
|
||||||
</li>
|
</li>
|
||||||
@@ -73,7 +73,7 @@ export function ConsentScreen({
|
|||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p className="ztdl-p text-xs text-left">
|
<p className="ztdl-p text-left text-xs">
|
||||||
<Translated
|
<Translated
|
||||||
i18nKey="request.disclaimer"
|
i18nKey="request.disclaimer"
|
||||||
namespace="device"
|
namespace="device"
|
||||||
@@ -95,7 +95,7 @@ export function ConsentScreen({
|
|||||||
variant={ButtonVariants.Secondary}
|
variant={ButtonVariants.Secondary}
|
||||||
data-testid="deny-button"
|
data-testid="deny-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}
|
||||||
<Translated i18nKey="device.request.deny" namespace="device" />
|
<Translated i18nKey="device.request.deny" namespace="device" />
|
||||||
</Button>
|
</Button>
|
||||||
<span className="flex-grow"></span>
|
<span className="flex-grow"></span>
|
||||||
|
@@ -27,7 +27,7 @@ export function CopyToClipboard({ value }: Props) {
|
|||||||
<button
|
<button
|
||||||
id="tooltip-ctc"
|
id="tooltip-ctc"
|
||||||
type="button"
|
type="button"
|
||||||
className=" text-primary-light-500 dark:text-primary-dark-500"
|
className="text-primary-light-500 dark:text-primary-dark-500"
|
||||||
onClick={() => setCopied(true)}
|
onClick={() => setCopied(true)}
|
||||||
>
|
>
|
||||||
{!copied ? (
|
{!copied ? (
|
||||||
|
@@ -85,7 +85,7 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) {
|
|||||||
onClick={handleSubmit(submitCodeAndContinue)}
|
onClick={handleSubmit(submitCodeAndContinue)}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
|
||||||
<Translated i18nKey="verify.submit" namespace="verify" />
|
<Translated i18nKey="verify.submit" namespace="verify" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -17,7 +17,7 @@ export function DynamicTheme({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ThemeWrapper branding={branding}>
|
<ThemeWrapper branding={branding}>
|
||||||
<div className="rounded-lg bg-background-light-400 dark:bg-background-dark-500 px-8 py-12">
|
<div className="rounded-lg bg-background-light-400 px-8 py-12 dark:bg-background-dark-500">
|
||||||
<div className="mx-auto flex flex-col items-center space-y-4">
|
<div className="mx-auto flex flex-col items-center space-y-4">
|
||||||
<div className="relative flex flex-row items-center justify-center gap-8">
|
<div className="relative flex flex-row items-center justify-center gap-8">
|
||||||
{branding && (
|
{branding && (
|
||||||
|
@@ -26,15 +26,15 @@ export const BaseButton = forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
disabled={formStatus.pending}
|
disabled={formStatus.pending}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"flex-1 transition-all cursor-pointer flex flex-row items-center bg-background-light-400 text-text-light-500 dark:bg-background-dark-500 dark:text-text-dark-500 border border-divider-light hover:border-black dark:border-divider-dark hover:dark:border-white focus:border-primary-light-500 focus:dark:border-primary-dark-500 outline-none rounded-md px-4 text-sm",
|
"flex flex-1 cursor-pointer flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 text-sm text-text-light-500 outline-none transition-all hover:border-black focus:border-primary-light-500 dark:border-divider-dark dark:bg-background-dark-500 dark:text-text-dark-500 hover:dark:border-white focus:dark:border-primary-dark-500",
|
||||||
props.className,
|
props.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex-1 justify-between flex items-center gap-4">
|
<div className="flex flex-1 items-center justify-between gap-4">
|
||||||
<div className="flex-1 flex flex-row items-center">
|
<div className="flex flex-1 flex-row items-center">
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
{formStatus.pending && <Loader2Icon className="w-4 h-4 animate-spin" />}
|
{formStatus.pending && <Loader2Icon className="h-4 w-4 animate-spin" />}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@@ -12,7 +12,7 @@ export const SignInWithApple = forwardRef<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseButton {...restProps} ref={ref}>
|
<BaseButton {...restProps} ref={ref}>
|
||||||
<div className="h-12 w-12 flex items-center justify-center">
|
<div className="flex h-12 w-12 items-center justify-center">
|
||||||
<div className="h-6 w-6">
|
<div className="h-6 w-6">
|
||||||
<svg viewBox="0 0 170 170" fill="currentColor">
|
<svg viewBox="0 0 170 170" fill="currentColor">
|
||||||
<title>Apple Logo</title>
|
<title>Apple Logo</title>
|
||||||
|
@@ -12,13 +12,13 @@ export const SignInWithAzureAd = forwardRef<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseButton {...restProps} ref={ref}>
|
<BaseButton {...restProps} ref={ref}>
|
||||||
<div className="h-12 p-[10px] w-12 flex items-center justify-center">
|
<div className="flex h-12 w-12 items-center justify-center p-[10px]">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="21"
|
width="21"
|
||||||
height="21"
|
height="21"
|
||||||
viewBox="0 0 21 21"
|
viewBox="0 0 21 21"
|
||||||
className="w-full h-full"
|
className="h-full w-full"
|
||||||
>
|
>
|
||||||
<path fill="#f25022" d="M1 1H10V10H1z"></path>
|
<path fill="#f25022" d="M1 1H10V10H1z"></path>
|
||||||
<path fill="#00a4ef" d="M1 11H10V20H1z"></path>
|
<path fill="#00a4ef" d="M1 11H10V20H1z"></path>
|
||||||
|
@@ -11,7 +11,7 @@ function GitHubLogo() {
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 1024 1024"
|
viewBox="0 0 1024 1024"
|
||||||
className="h-8 w-8 hidden dark:block"
|
className="hidden h-8 w-8 dark:block"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="#fafafa"
|
fill="#fafafa"
|
||||||
@@ -24,7 +24,7 @@ function GitHubLogo() {
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 1024 1024"
|
viewBox="0 0 1024 1024"
|
||||||
className="h-8 w-8 block dark:hidden"
|
className="block h-8 w-8 dark:hidden"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="#1B1F23"
|
fill="#1B1F23"
|
||||||
|
@@ -12,7 +12,7 @@ export const SignInWithGitlab = forwardRef<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseButton {...restProps} ref={ref}>
|
<BaseButton {...restProps} ref={ref}>
|
||||||
<div className="h-12 w-12 flex items-center justify-center">
|
<div className="flex h-12 w-12 items-center justify-center">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width={25}
|
width={25}
|
||||||
|
@@ -12,7 +12,7 @@ export const SignInWithGoogle = forwardRef<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseButton {...restProps} ref={ref}>
|
<BaseButton {...restProps} ref={ref}>
|
||||||
<div className="h-12 w-12 flex items-center justify-center">
|
<div className="flex h-12 w-12 items-center justify-center">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlSpace="preserve"
|
xmlSpace="preserve"
|
||||||
|
@@ -27,12 +27,9 @@ export type TextInputProps = DetailedHTMLProps<
|
|||||||
|
|
||||||
const styles = (error: boolean, disabled: boolean) =>
|
const styles = (error: boolean, disabled: boolean) =>
|
||||||
clsx({
|
clsx({
|
||||||
"h-[40px] mb-[2px] rounded p-[7px] bg-input-light-background dark:bg-input-dark-background transition-colors duration-300 grow":
|
"h-[40px] mb-[2px] rounded p-[7px] bg-input-light-background dark:bg-input-dark-background transition-colors duration-300 grow": true,
|
||||||
true,
|
"border border-input-light-border dark:border-input-dark-border hover:border-black hover:dark:border-white focus:border-primary-light-500 focus:dark:border-primary-dark-500": true,
|
||||||
"border border-input-light-border dark:border-input-dark-border hover:border-black hover:dark:border-white focus:border-primary-light-500 focus:dark:border-primary-dark-500":
|
"focus:outline-none focus:ring-0 text-base text-black dark:text-white placeholder:italic placeholder-gray-700 dark:placeholder-gray-700": true,
|
||||||
true,
|
|
||||||
"focus:outline-none focus:ring-0 text-base text-black dark:text-white placeholder:italic placeholder-gray-700 dark:placeholder-gray-700":
|
|
||||||
true,
|
|
||||||
"border border-warn-light-500 dark:border-warn-dark-500 hover:border-warn-light-500 hover:dark:border-warn-dark-500 focus:border-warn-light-500 focus:dark:border-warn-dark-500":
|
"border border-warn-light-500 dark:border-warn-dark-500 hover:border-warn-light-500 hover:dark:border-warn-dark-500 focus:border-warn-light-500 focus:dark:border-warn-dark-500":
|
||||||
error,
|
error,
|
||||||
"pointer-events-none text-gray-500 dark:text-gray-800 border border-input-light-border dark:border-input-dark-border hover:border-light-hoverborder hover:dark:border-hoverborder cursor-default":
|
"pointer-events-none text-gray-500 dark:text-gray-800 border border-input-light-border dark:border-input-dark-border hover:border-light-hoverborder hover:dark:border-hoverborder cursor-default":
|
||||||
@@ -60,7 +57,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
|
|||||||
return (
|
return (
|
||||||
<label className="relative flex flex-col text-12px text-input-light-label dark:text-input-dark-label">
|
<label className="relative flex flex-col text-12px text-input-light-label dark:text-input-dark-label">
|
||||||
<span
|
<span
|
||||||
className={`leading-3 mb-1 ${
|
className={`mb-1 leading-3 ${
|
||||||
error ? "text-warn-light-500 dark:text-warn-dark-500" : ""
|
error ? "text-warn-light-500 dark:text-warn-dark-500" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -81,12 +78,12 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{suffix && (
|
{suffix && (
|
||||||
<span className="z-30 absolute right-[3px] bottom-[22px] transform translate-y-1/2 bg-background-light-500 dark:bg-background-dark-500 p-2 rounded-sm">
|
<span className="absolute bottom-[22px] right-[3px] z-30 translate-y-1/2 transform rounded-sm bg-background-light-500 p-2 dark:bg-background-dark-500">
|
||||||
@{suffix}
|
@{suffix}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="leading-14.5px h-14.5px text-warn-light-500 dark:text-warn-dark-500 flex flex-row items-center text-12px">
|
<div className="leading-14.5px h-14.5px flex flex-row items-center text-12px text-warn-light-500 dark:text-warn-dark-500">
|
||||||
<span>{error ? error : " "}</span>
|
<span>{error ? error : " "}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -37,13 +37,13 @@ export function LanguageSwitcher() {
|
|||||||
<Listbox value={selected} onChange={handleChange}>
|
<Listbox value={selected} onChange={handleChange}>
|
||||||
<ListboxButton
|
<ListboxButton
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"relative block w-full rounded-lg bg-black/5 dark:bg-white/5 py-1.5 pr-8 pl-3 text-left text-sm/6 text-black dark:text-white",
|
"relative block w-full rounded-lg bg-black/5 py-1.5 pl-3 pr-8 text-left text-sm/6 text-black dark:bg-white/5 dark:text-white",
|
||||||
"focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-white/25",
|
"focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-white/25",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{selected.name}
|
{selected.name}
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className="group pointer-events-none absolute top-2.5 right-2.5 size-4"
|
className="group pointer-events-none absolute right-2.5 top-2.5 size-4"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</ListboxButton>
|
</ListboxButton>
|
||||||
@@ -51,7 +51,7 @@ export function LanguageSwitcher() {
|
|||||||
anchor="bottom"
|
anchor="bottom"
|
||||||
transition
|
transition
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"w-[var(--button-width)] rounded-xl border border-black/5 dark:border-white/5 bg-background-light-500 dark:bg-background-dark-500 p-1 [--anchor-gap:var(--spacing-1)] focus:outline-none",
|
"w-[var(--button-width)] rounded-xl border border-black/5 bg-background-light-500 p-1 [--anchor-gap:var(--spacing-1)] focus:outline-none dark:border-white/5 dark:bg-background-dark-500",
|
||||||
"transition duration-100 ease-in data-[leave]:data-[closed]:opacity-0",
|
"transition duration-100 ease-in data-[leave]:data-[closed]:opacity-0",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -59,7 +59,7 @@ export function LanguageSwitcher() {
|
|||||||
<ListboxOption
|
<ListboxOption
|
||||||
key={lang.code}
|
key={lang.code}
|
||||||
value={lang}
|
value={lang}
|
||||||
className="group flex cursor-default items-center gap-2 rounded-lg py-1.5 px-3 select-none data-[focus]:bg-black/10 dark:data-[focus]:bg-white/10"
|
className="group flex cursor-default select-none items-center gap-2 rounded-lg px-3 py-1.5 data-[focus]:bg-black/10 dark:data-[focus]:bg-white/10"
|
||||||
>
|
>
|
||||||
<CheckIcon className="invisible size-4 group-data-[selected]:visible" />
|
<CheckIcon className="invisible size-4 group-data-[selected]:visible" />
|
||||||
<div className="text-sm/6 text-black dark:text-white">
|
<div className="text-sm/6 text-black dark:text-white">
|
||||||
|
@@ -100,7 +100,7 @@ export function LDAPUsernamePasswordForm({ idpId, link }: Props) {
|
|||||||
onClick={handleSubmit(submitUsernamePassword)}
|
onClick={handleSubmit(submitUsernamePassword)}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}
|
||||||
<Translated i18nKey="submit" namespace="ldap" />
|
<Translated i18nKey="submit" namespace="ldap" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -220,14 +220,14 @@ export function LoginOTP({
|
|||||||
{["email", "sms"].includes(method) && (
|
{["email", "sms"].includes(method) && (
|
||||||
<Alert type={AlertType.INFO}>
|
<Alert type={AlertType.INFO}>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<span className="flex-1 mr-auto text-left">
|
<span className="mr-auto flex-1 text-left">
|
||||||
<Translated i18nKey="verify.noCodeReceived" namespace="otp" />
|
<Translated i18nKey="verify.noCodeReceived" namespace="otp" />
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
aria-label="Resend OTP Code"
|
aria-label="Resend OTP Code"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
type="button"
|
type="button"
|
||||||
className="ml-4 text-primary-light-500 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 hover:text-primary-light-400 cursor-pointer disabled:cursor-default disabled:text-gray-400 dark:disabled:text-gray-700"
|
className="ml-4 cursor-pointer text-primary-light-500 hover:text-primary-light-400 disabled:cursor-default disabled:text-gray-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 dark:disabled:text-gray-700"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
updateSessionForOTPChallenge()
|
updateSessionForOTPChallenge()
|
||||||
@@ -275,7 +275,7 @@ export function LoginOTP({
|
|||||||
})}
|
})}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
|
||||||
<Translated i18nKey="verify.submit" namespace="otp" />
|
<Translated i18nKey="verify.submit" namespace="otp" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -271,7 +271,7 @@ export function LoginPasskey({
|
|||||||
}}
|
}}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
|
||||||
<Translated i18nKey="verify.submit" namespace="passkey" />
|
<Translated i18nKey="verify.submit" namespace="passkey" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -19,7 +19,7 @@ const check = (
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="w-6 h-6 las la-check text-green-500 dark:text-green-500 mr-2 text-lg"
|
className="las la-check mr-2 h-6 w-6 text-lg text-green-500 dark:text-green-500"
|
||||||
role="img"
|
role="img"
|
||||||
>
|
>
|
||||||
<title>Matches</title>
|
<title>Matches</title>
|
||||||
@@ -32,7 +32,7 @@ const check = (
|
|||||||
);
|
);
|
||||||
const cross = (
|
const cross = (
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6 las la-times text-warn-light-500 dark:text-warn-dark-500 mr-2 text-lg"
|
className="las la-times mr-2 h-6 w-6 text-lg text-warn-light-500 dark:text-warn-dark-500"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
@@ -124,7 +124,7 @@ export function PasswordForm({
|
|||||||
/>
|
/>
|
||||||
{!loginSettings?.hidePasswordReset && (
|
{!loginSettings?.hidePasswordReset && (
|
||||||
<button
|
<button
|
||||||
className="transition-all text-sm hover:text-primary-light-500 dark:hover:text-primary-dark-500"
|
className="text-sm transition-all hover:text-primary-light-500 dark:hover:text-primary-dark-500"
|
||||||
onClick={() => resetPasswordAndContinue()}
|
onClick={() => resetPasswordAndContinue()}
|
||||||
type="button"
|
type="button"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@@ -167,7 +167,7 @@ export function PasswordForm({
|
|||||||
onClick={handleSubmit(submitPassword)}
|
onClick={handleSubmit(submitPassword)}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
|
||||||
<Translated i18nKey="verify.submit" namespace="password" />
|
<Translated i18nKey="verify.submit" namespace="password" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -23,7 +23,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className="flex flex-row items-center text-text-light-secondary-500 dark:text-text-dark-secondary-500 mt-4 text-sm">
|
<p className="mt-4 flex flex-row items-center text-sm text-text-light-secondary-500 dark:text-text-dark-secondary-500">
|
||||||
<Translated i18nKey="agreeTo" namespace="register" />
|
<Translated i18nKey="agreeTo" namespace="register" />
|
||||||
{legal?.helpLink && (
|
{legal?.helpLink && (
|
||||||
<span>
|
<span>
|
||||||
@@ -34,7 +34,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="ml-1 w-5 h-5"
|
className="ml-1 h-5 w-5"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
|
@@ -96,7 +96,7 @@ export function RegisterFormIDPIncomplete({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="w-full">
|
<form className="w-full">
|
||||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
<div className="mb-4 grid grid-cols-2 gap-4">
|
||||||
<div className="">
|
<div className="">
|
||||||
<TextInput
|
<TextInput
|
||||||
type="firstname"
|
type="firstname"
|
||||||
@@ -147,7 +147,7 @@ export function RegisterFormIDPIncomplete({
|
|||||||
onClick={handleSubmit(submitAndRegister)}
|
onClick={handleSubmit(submitAndRegister)}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
|
||||||
<Translated i18nKey="submit" namespace="register" />
|
<Translated i18nKey="submit" namespace="register" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -124,7 +124,7 @@ export function RegisterForm({
|
|||||||
const [tosAndPolicyAccepted, setTosAndPolicyAccepted] = useState(false);
|
const [tosAndPolicyAccepted, setTosAndPolicyAccepted] = useState(false);
|
||||||
return (
|
return (
|
||||||
<form className="w-full">
|
<form className="w-full">
|
||||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
<div className="mb-4 grid grid-cols-2 gap-4">
|
||||||
<div className="">
|
<div className="">
|
||||||
<TextInput
|
<TextInput
|
||||||
type="firstname"
|
type="firstname"
|
||||||
@@ -170,7 +170,7 @@ export function RegisterForm({
|
|||||||
loginSettings.allowUsernamePassword &&
|
loginSettings.allowUsernamePassword &&
|
||||||
loginSettings.passkeysType == PasskeysType.ALLOWED && (
|
loginSettings.passkeysType == PasskeysType.ALLOWED && (
|
||||||
<>
|
<>
|
||||||
<p className="mt-4 ztdl-p mb-6 block text-left">
|
<p className="ztdl-p mb-6 mt-4 block text-left">
|
||||||
<Translated i18nKey="selectMethod" namespace="register" />
|
<Translated i18nKey="selectMethod" namespace="register" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -211,14 +211,14 @@ export function RegisterForm({
|
|||||||
const usePasswordToContinue: boolean =
|
const usePasswordToContinue: boolean =
|
||||||
loginSettings?.allowUsernamePassword &&
|
loginSettings?.allowUsernamePassword &&
|
||||||
loginSettings?.passkeysType == PasskeysType.ALLOWED
|
loginSettings?.passkeysType == PasskeysType.ALLOWED
|
||||||
? !!!(selected === methods[0]) // choose selection if both available
|
? !(selected === methods[0]) // choose selection if both available
|
||||||
: !!loginSettings?.allowUsernamePassword; // if password is chosen
|
: !!loginSettings?.allowUsernamePassword; // if password is chosen
|
||||||
// set password as default if only password is allowed
|
// set password as default if only password is allowed
|
||||||
return submitAndContinue(values, usePasswordToContinue);
|
return submitAndContinue(values, usePasswordToContinue);
|
||||||
})}
|
})}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}
|
||||||
<Translated i18nKey="submit" namespace="register" />
|
<Translated i18nKey="submit" namespace="register" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -211,7 +211,7 @@ export function RegisterPasskey({
|
|||||||
onClick={handleSubmit(submitRegisterAndContinue)}
|
onClick={handleSubmit(submitRegisterAndContinue)}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
|
||||||
<Translated i18nKey="set.submit" namespace="passkey" />
|
<Translated i18nKey="set.submit" namespace="passkey" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -216,7 +216,7 @@ export function RegisterU2f({
|
|||||||
onClick={submitRegisterAndContinue}
|
onClick={submitRegisterAndContinue}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
|
||||||
<Translated i18nKey="set.submit" namespace="u2f" />
|
<Translated i18nKey="set.submit" namespace="u2f" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -15,7 +15,7 @@ export function SelfServiceMenu({ sessionId }: { sessionId: string }) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col space-y-2">
|
<div className="flex w-full flex-col space-y-2">
|
||||||
{list.map((menuitem, index) => {
|
{list.map((menuitem, index) => {
|
||||||
return (
|
return (
|
||||||
<SelfServiceItem
|
<SelfServiceItem
|
||||||
@@ -34,7 +34,7 @@ const SelfServiceItem = ({ name, link }: { name: string; link: string }) => {
|
|||||||
<Link
|
<Link
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
href={link}
|
href={link}
|
||||||
className="w-full group flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light hover:shadow-lg dark:hover:bg-white/10 py-2 px-4 rounded-md transition-all"
|
className="group flex w-full flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 transition-all hover:shadow-lg dark:bg-background-dark-400 dark:hover:bg-white/10"
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</Link>
|
</Link>
|
||||||
|
@@ -52,7 +52,7 @@ export function SessionClearItem({
|
|||||||
reload();
|
reload();
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="group flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light hover:shadow-lg dark:hover:bg-white/10 py-2 px-4 rounded-md transition-all"
|
className="group flex flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 transition-all hover:shadow-lg dark:bg-background-dark-400 dark:hover:bg-white/10"
|
||||||
>
|
>
|
||||||
<div className="pr-4">
|
<div className="pr-4">
|
||||||
<Avatar
|
<Avatar
|
||||||
@@ -64,11 +64,11 @@ export function SessionClearItem({
|
|||||||
|
|
||||||
<div className="flex flex-col items-start overflow-hidden">
|
<div className="flex flex-col items-start overflow-hidden">
|
||||||
<span className="">{session.factors?.user?.displayName}</span>
|
<span className="">{session.factors?.user?.displayName}</span>
|
||||||
<span className="text-xs opacity-80 text-ellipsis">
|
<span className="text-ellipsis text-xs opacity-80">
|
||||||
{session.factors?.user?.loginName}
|
{session.factors?.user?.loginName}
|
||||||
</span>
|
</span>
|
||||||
{valid ? (
|
{valid ? (
|
||||||
<span className="text-xs opacity-80 text-ellipsis">
|
<span className="text-ellipsis text-xs opacity-80">
|
||||||
{verifiedAt && (
|
{verifiedAt && (
|
||||||
<Translated
|
<Translated
|
||||||
i18nKey="verfiedAt"
|
i18nKey="verfiedAt"
|
||||||
@@ -79,7 +79,7 @@ export function SessionClearItem({
|
|||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
verifiedAt && (
|
verifiedAt && (
|
||||||
<span className="text-xs opacity-80 text-ellipsis">
|
<span className="text-ellipsis text-xs opacity-80">
|
||||||
expired{" "}
|
expired{" "}
|
||||||
{session.expirationDate &&
|
{session.expirationDate &&
|
||||||
moment(timestampDate(session.expirationDate)).fromNow()}
|
moment(timestampDate(session.expirationDate)).fromNow()}
|
||||||
@@ -90,14 +90,14 @@ export function SessionClearItem({
|
|||||||
|
|
||||||
<span className="flex-grow"></span>
|
<span className="flex-grow"></span>
|
||||||
<div className="relative flex flex-row items-center">
|
<div className="relative flex flex-row items-center">
|
||||||
<div className="mr-6 px-2 py-[2px] text-xs hidden group-hover:block transition-all text-warn-light-500 dark:text-warn-dark-500 bg-[#ff0000]/10 dark:bg-[#ff0000]/10 rounded-full flex items-center justify-center">
|
<div className="mr-6 flex hidden items-center justify-center rounded-full bg-[#ff0000]/10 px-2 py-[2px] text-xs text-warn-light-500 transition-all group-hover:block dark:bg-[#ff0000]/10 dark:text-warn-dark-500">
|
||||||
<Translated i18nKey="clear" namespace="logout" />
|
<Translated i18nKey="clear" namespace="logout" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{valid ? (
|
{valid ? (
|
||||||
<div className="absolute h-2 w-2 bg-green-500 rounded-full mx-2 transform right-0 transition-all"></div>
|
<div className="absolute right-0 mx-2 h-2 w-2 transform rounded-full bg-green-500 transition-all"></div>
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute h-2 w-2 bg-red-500 rounded-full mx-2 transform right-0 transition-all"></div>
|
<div className="absolute right-0 mx-2 h-2 w-2 transform rounded-full bg-red-500 transition-all"></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -102,7 +102,7 @@ export function SessionItem({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="group flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light hover:shadow-lg dark:hover:bg-white/10 py-2 px-4 rounded-md transition-all"
|
className="group flex flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 transition-all hover:shadow-lg dark:bg-background-dark-400 dark:hover:bg-white/10"
|
||||||
>
|
>
|
||||||
<div className="pr-4">
|
<div className="pr-4">
|
||||||
<Avatar
|
<Avatar
|
||||||
@@ -114,16 +114,16 @@ export function SessionItem({
|
|||||||
|
|
||||||
<div className="flex flex-col items-start overflow-hidden">
|
<div className="flex flex-col items-start overflow-hidden">
|
||||||
<span className="">{session.factors?.user?.displayName}</span>
|
<span className="">{session.factors?.user?.displayName}</span>
|
||||||
<span className="text-xs opacity-80 text-ellipsis">
|
<span className="text-ellipsis text-xs opacity-80">
|
||||||
{session.factors?.user?.loginName}
|
{session.factors?.user?.loginName}
|
||||||
</span>
|
</span>
|
||||||
{valid ? (
|
{valid ? (
|
||||||
<span className="text-xs opacity-80 text-ellipsis">
|
<span className="text-ellipsis text-xs opacity-80">
|
||||||
{verifiedAt && moment(timestampDate(verifiedAt)).fromNow()}
|
{verifiedAt && moment(timestampDate(verifiedAt)).fromNow()}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
verifiedAt && (
|
verifiedAt && (
|
||||||
<span className="text-xs opacity-80 text-ellipsis">
|
<span className="text-ellipsis text-xs opacity-80">
|
||||||
expired{" "}
|
expired{" "}
|
||||||
{session.expirationDate &&
|
{session.expirationDate &&
|
||||||
moment(timestampDate(session.expirationDate)).fromNow()}
|
moment(timestampDate(session.expirationDate)).fromNow()}
|
||||||
@@ -135,13 +135,13 @@ export function SessionItem({
|
|||||||
<span className="flex-grow"></span>
|
<span className="flex-grow"></span>
|
||||||
<div className="relative flex flex-row items-center">
|
<div className="relative flex flex-row items-center">
|
||||||
{valid ? (
|
{valid ? (
|
||||||
<div className="absolute h-2 w-2 bg-green-500 rounded-full mx-2 transform right-0 group-hover:right-6 transition-all"></div>
|
<div className="absolute right-0 mx-2 h-2 w-2 transform rounded-full bg-green-500 transition-all group-hover:right-6"></div>
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute h-2 w-2 bg-red-500 rounded-full mx-2 transform right-0 group-hover:right-6 transition-all"></div>
|
<div className="absolute right-0 mx-2 h-2 w-2 transform rounded-full bg-red-500 transition-all group-hover:right-6"></div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<XCircleIcon
|
<XCircleIcon
|
||||||
className="hidden group-hover:block h-5 w-5 transition-all opacity-50 hover:opacity-100"
|
className="hidden h-5 w-5 opacity-50 transition-all hover:opacity-100 group-hover:block"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@@ -188,18 +188,18 @@ export function SetPasswordForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="w-full">
|
<form className="w-full">
|
||||||
<div className="pt-4 grid grid-cols-1 gap-4 mb-4">
|
<div className="mb-4 grid grid-cols-1 gap-4 pt-4">
|
||||||
{codeRequired && (
|
{codeRequired && (
|
||||||
<Alert type={AlertType.INFO}>
|
<Alert type={AlertType.INFO}>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<span className="flex-1 mr-auto text-left">
|
<span className="mr-auto flex-1 text-left">
|
||||||
<Translated i18nKey="set.noCodeReceived" namespace="password" />
|
<Translated i18nKey="set.noCodeReceived" namespace="password" />
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
aria-label="Resend OTP Code"
|
aria-label="Resend OTP Code"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
type="button"
|
type="button"
|
||||||
className="ml-4 text-primary-light-500 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 hover:text-primary-light-400 cursor-pointer disabled:cursor-default disabled:text-gray-400 dark:disabled:text-gray-700"
|
className="ml-4 cursor-pointer text-primary-light-500 hover:text-primary-light-400 disabled:cursor-default disabled:text-gray-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 dark:disabled:text-gray-700"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
resendCode();
|
resendCode();
|
||||||
}}
|
}}
|
||||||
@@ -277,7 +277,7 @@ export function SetPasswordForm({
|
|||||||
onClick={handleSubmit(submitPassword)}
|
onClick={handleSubmit(submitPassword)}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
|
||||||
<Translated i18nKey="set.submit" namespace="password" />
|
<Translated i18nKey="set.submit" namespace="password" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -108,7 +108,7 @@ export function SetRegisterPasswordForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="w-full">
|
<form className="w-full">
|
||||||
<div className="pt-4 grid grid-cols-1 gap-4 mb-4">
|
<div className="mb-4 grid grid-cols-1 gap-4 pt-4">
|
||||||
<div className="">
|
<div className="">
|
||||||
<TextInput
|
<TextInput
|
||||||
type="password"
|
type="password"
|
||||||
@@ -161,7 +161,7 @@ export function SetRegisterPasswordForm({
|
|||||||
onClick={handleSubmit(submitRegister)}
|
onClick={handleSubmit(submitRegister)}
|
||||||
data-testid="submit-button"
|
data-testid="submit-button"
|
||||||
>
|
>
|
||||||
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
|
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
|
||||||
<Translated i18nKey="password.submit" namespace="register" />
|
<Translated i18nKey="password.submit" namespace="register" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -76,8 +76,8 @@ export function SignInWithIdp({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full space-y-2 text-sm">
|
<div className="flex w-full flex-col space-y-2 text-sm">
|
||||||
<p className="text-center ztdl-p">
|
<p className="ztdl-p text-center">
|
||||||
<Translated i18nKey="orSignInWith" namespace="idp" />
|
<Translated i18nKey="orSignInWith" namespace="idp" />
|
||||||
</p>
|
</p>
|
||||||
{!!identityProviders.length && identityProviders?.map(renderIDPButton)}
|
{!!identityProviders.length && identityProviders?.map(renderIDPButton)}
|
||||||
|
@@ -2,7 +2,7 @@ import { ReactNode } from "react";
|
|||||||
|
|
||||||
export function Skeleton({ children }: { children?: ReactNode }) {
|
export function Skeleton({ children }: { children?: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="skeleton py-12 px-8 rounded-lg bg-background-light-600 dark:bg-background-dark-600 flex flex-row items-center justify-center">
|
<div className="skeleton flex flex-row items-center justify-center rounded-lg bg-background-light-600 px-8 py-12 dark:bg-background-dark-600">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -4,7 +4,7 @@ export const Spinner: FC<{ className?: string }> = ({ className = "" }) => {
|
|||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
role="status"
|
role="status"
|
||||||
className={`${className} inline-block animate-spin fill-primary-light-500 dark:fill-primary-dark-500 text-black/10 dark:text-white/10`}
|
className={`${className} inline-block animate-spin fill-primary-light-500 text-black/10 dark:fill-primary-dark-500 dark:text-white/10`}
|
||||||
viewBox="0 0 100 101"
|
viewBox="0 0 100 101"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@@ -16,8 +16,7 @@ export type StateBadgeProps = {
|
|||||||
|
|
||||||
const getBadgeClasses = (state: BadgeState, evenPadding: boolean) =>
|
const getBadgeClasses = (state: BadgeState, evenPadding: boolean) =>
|
||||||
clsx({
|
clsx({
|
||||||
"w-fit border-box h-18.5px flex flex-row items-center whitespace-nowrap tracking-wider leading-4 items-center justify-center px-2 py-2px text-12px rounded-full shadow-sm":
|
"w-fit border-box h-18.5px flex flex-row items-center whitespace-nowrap tracking-wider leading-4 items-center justify-center px-2 py-2px text-12px rounded-full shadow-sm": true,
|
||||||
true,
|
|
||||||
"bg-state-success-light-background text-state-success-light-color dark:bg-state-success-dark-background dark:text-state-success-dark-color ":
|
"bg-state-success-light-background text-state-success-light-color dark:bg-state-success-dark-background dark:text-state-success-dark-color ":
|
||||||
state === BadgeState.Success,
|
state === BadgeState.Success,
|
||||||
"bg-state-neutral-light-background text-state-neutral-light-color dark:bg-state-neutral-dark-background dark:text-state-neutral-dark-color":
|
"bg-state-neutral-light-background text-state-neutral-light-color dark:bg-state-neutral-dark-background dark:text-state-neutral-dark-color":
|
||||||
|
@@ -23,7 +23,7 @@ export const Tab = ({
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
className={clsx("mt-2 mr-2 rounded-lg px-3 py-1 text-sm font-medium", {
|
className={clsx("mr-2 mt-2 rounded-lg px-3 py-1 text-sm font-medium", {
|
||||||
"bg-gray-700 text-gray-100 hover:bg-gray-500 hover:text-white":
|
"bg-gray-700 text-gray-100 hover:bg-gray-500 hover:text-white":
|
||||||
!isActive,
|
!isActive,
|
||||||
"bg-blue-500 text-white": isActive,
|
"bg-blue-500 text-white": isActive,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user