diff --git a/.env.example b/.env.example index 71a9074a63..0649839a45 100644 --- a/.env.example +++ b/.env.example @@ -31,3 +31,5 @@ USE_MINIO=1 # Nginx Configuration NGINX_PORT=80 + +MONGO_DB_URL="mongodb://plane-mongodb:27017/" \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..5990d9c64c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/build-aio-branch-ee.yml b/.github/workflows/build-aio-branch-ee.yml new file mode 100644 index 0000000000..293668a570 --- /dev/null +++ b/.github/workflows/build-aio-branch-ee.yml @@ -0,0 +1,204 @@ +name: Branch Build AIO + +on: + workflow_dispatch: + inputs: + full: + description: 'Run full build' + type: boolean + required: false + default: true + slim: + description: 'Run slim build' + type: boolean + required: false + default: true + base_tag_name: + description: 'Base Tag Name' + required: false + default: '' + release: + types: [released, prereleased] + +env: + TARGET_BRANCH: ${{ github.ref_name || github.event.release.target_commitish }} + IS_PRERELEASE: ${{ github.event.release.prerelease }} + FULL_BUILD_INPUT: ${{ github.event.inputs.full }} + SLIM_BUILD_INPUT: ${{ github.event.inputs.slim }} + +jobs: + branch_build_setup: + name: Build Setup + runs-on: ubuntu-latest + outputs: + gh_branch_name: ${{ steps.set_env_variables.outputs.TARGET_BRANCH }} + gh_buildx_driver: ${{ steps.set_env_variables.outputs.BUILDX_DRIVER }} + gh_buildx_version: ${{ steps.set_env_variables.outputs.BUILDX_VERSION }} + gh_buildx_platforms: ${{ steps.set_env_variables.outputs.BUILDX_PLATFORMS }} + gh_buildx_endpoint: ${{ steps.set_env_variables.outputs.BUILDX_ENDPOINT }} + aio_base_tag: ${{ steps.set_env_variables.outputs.AIO_BASE_TAG }} + do_full_build: ${{ steps.set_env_variables.outputs.DO_FULL_BUILD }} + do_slim_build: ${{ steps.set_env_variables.outputs.DO_SLIM_BUILD }} + + steps: + - id: set_env_variables + name: Set Environment Variables + run: | + if [ [ "${{ github.event_name }}" == "release" ] && [ "${{ env.IS_PRERELEASE }}" != "true" ] ; then + echo "BUILDX_DRIVER=cloud" >> $GITHUB_OUTPUT + echo "BUILDX_VERSION=lab:latest" >> $GITHUB_OUTPUT + echo "BUILDX_PLATFORMS=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT + echo "BUILDX_ENDPOINT=makeplane/plane-dev" >> $GITHUB_OUTPUT + + echo "AIO_BASE_TAG=latest" >> $GITHUB_OUTPUT + else + echo "BUILDX_DRIVER=docker-container" >> $GITHUB_OUTPUT + echo "BUILDX_VERSION=latest" >> $GITHUB_OUTPUT + echo "BUILDX_PLATFORMS=linux/amd64" >> $GITHUB_OUTPUT + echo "BUILDX_ENDPOINT=" >> $GITHUB_OUTPUT + + if [ "${{ github.event.inputs.base_tag_name }}" != "" ]; then + echo "AIO_BASE_TAG=${{ github.event.inputs.base_tag_name }}" >> $GITHUB_OUTPUT + elif [ "${{ env.TARGET_BRANCH }}" == "preview" ]; then + echo "AIO_BASE_TAG=preview" >> $GITHUB_OUTPUT + else + echo "AIO_BASE_TAG=develop" >> $GITHUB_OUTPUT + fi + fi + echo "TARGET_BRANCH=${{ env.TARGET_BRANCH }}" >> $GITHUB_OUTPUT + + if [ "${{ env.FULL_BUILD_INPUT }}" == "true" ] || [ "${{github.event_name}}" == "push" ] || [ "${{github.event_name}}" == "release" ]; then + echo "DO_FULL_BUILD=true" >> $GITHUB_OUTPUT + else + echo "DO_FULL_BUILD=false" >> $GITHUB_OUTPUT + fi + + if [ "${{ env.SLIM_BUILD_INPUT }}" == "true" ] || [ "${{github.event_name}}" == "push" ] || [ "${{github.event_name}}" == "release" ]; then + echo "DO_SLIM_BUILD=true" >> $GITHUB_OUTPUT + else + echo "DO_SLIM_BUILD=false" >> $GITHUB_OUTPUT + fi + + - id: checkout_files + name: Checkout Files + uses: actions/checkout@v4 + + full_build_push: + if: ${{ needs.branch_build_setup.outputs.do_full_build == 'true' }} + runs-on: ubuntu-20.04 + needs: [branch_build_setup] + env: + BUILD_TYPE: full + AIO_BASE_TAG: ${{ needs.branch_build_setup.outputs.aio_base_tag }} + AIO_IMAGE_TAGS: makeplane/plane-aio-enterprise:full-${{ needs.branch_build_setup.outputs.gh_branch_name }} + TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }} + BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }} + BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }} + BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }} + BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }} + steps: + - name: Set Docker Tag + run: | + if [ "${{ github.event_name }}" == "release" ]; then + TAG=makeplane/plane-aio-enterprise:${{env.BUILD_TYPE}}-stable,makeplane/plane-aio-enterprise:${{env.BUILD_TYPE}}-${{ github.event.release.tag_name }} + elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then + TAG=makeplane/plane-aio-enterprise:${{env.BUILD_TYPE}}-latest + else + TAG=${{ env.AIO_IMAGE_TAGS }} + fi + echo "AIO_IMAGE_TAGS=${TAG}" >> $GITHUB_ENV + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: ${{ env.BUILDX_DRIVER }} + version: ${{ env.BUILDX_VERSION }} + endpoint: ${{ env.BUILDX_ENDPOINT }} + + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Build and Push to Docker Hub + uses: docker/build-push-action@v5.1.0 + with: + context: . + file: ./aio/Dockerfile-app + platforms: ${{ env.BUILDX_PLATFORMS }} + tags: ${{ env.AIO_IMAGE_TAGS }} + push: true + build-args: | + BUILD_TAG=${{ env.AIO_BASE_TAG }} + BUILD_TYPE=${{env.BUILD_TYPE}} + cache-from: type=gha + cache-to: type=gha,mode=max + + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + + slim_build_push: + if: ${{ needs.branch_build_setup.outputs.do_slim_build == 'true' }} + runs-on: ubuntu-20.04 + needs: [branch_build_setup] + env: + BUILD_TYPE: slim + AIO_BASE_TAG: ${{ needs.branch_build_setup.outputs.aio_base_tag }} + AIO_IMAGE_TAGS: makeplane/plane-aio-enterprise:slim-${{ needs.branch_build_setup.outputs.gh_branch_name }} + TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }} + BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }} + BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }} + BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }} + BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }} + steps: + - name: Set Docker Tag + run: | + if [ "${{ github.event_name }}" == "release" ]; then + TAG=makeplane/plane-aio-enterprise:${{env.BUILD_TYPE}}-stable,makeplane/plane-aio-enterprise:${{env.BUILD_TYPE}}-${{ github.event.release.tag_name }} + elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then + TAG=makeplane/plane-aio-enterprise:${{env.BUILD_TYPE}}-latest + else + TAG=${{ env.AIO_IMAGE_TAGS }} + fi + echo "AIO_IMAGE_TAGS=${TAG}" >> $GITHUB_ENV + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: ${{ env.BUILDX_DRIVER }} + version: ${{ env.BUILDX_VERSION }} + endpoint: ${{ env.BUILDX_ENDPOINT }} + + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Build and Push to Docker Hub + uses: docker/build-push-action@v5.1.0 + with: + context: . + file: ./aio/Dockerfile-app + platforms: ${{ env.BUILDX_PLATFORMS }} + tags: ${{ env.AIO_IMAGE_TAGS }} + push: true + build-args: | + BUILD_TAG=${{ env.AIO_BASE_TAG }} + BUILD_TYPE=${{env.BUILD_TYPE}} + cache-from: type=gha + cache-to: type=gha,mode=max + + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/build-branch-ee.yml b/.github/workflows/build-branch-ee.yml new file mode 100644 index 0000000000..9afef51548 --- /dev/null +++ b/.github/workflows/build-branch-ee.yml @@ -0,0 +1,548 @@ +name: Branch Build Enterprise + +on: + workflow_dispatch: + inputs: + arm64: + description: "Build for ARM64 architecture" + required: false + default: false + type: boolean + push: + branches: + - master + - preview + release: + types: [released, prereleased] + +env: + TARGET_BRANCH: ${{ github.ref_name || github.event.release.target_commitish }} + ARM64_BUILD: ${{ github.event.inputs.arm64 }} + IS_PRERELEASE: ${{ github.event.release.prerelease }} + +jobs: + branch_build_setup: + name: Build Setup + runs-on: ${{vars.ACTION_RUNS_ON}} + outputs: + gh_branch_name: ${{ steps.set_env_variables.outputs.TARGET_BRANCH }} + gh_buildx_driver: ${{ steps.set_env_variables.outputs.BUILDX_DRIVER }} + gh_buildx_version: ${{ steps.set_env_variables.outputs.BUILDX_VERSION }} + gh_buildx_platforms: ${{ steps.set_env_variables.outputs.BUILDX_PLATFORMS }} + gh_buildx_endpoint: ${{ steps.set_env_variables.outputs.BUILDX_ENDPOINT }} + build_web: ${{ steps.changed_files.outputs.web_any_changed }} + build_admin: ${{ steps.changed_files.outputs.admin_any_changed }} + build_space: ${{ steps.changed_files.outputs.space_any_changed }} + build_apiserver: ${{ steps.changed_files.outputs.apiserver_any_changed }} + build_proxy: ${{ steps.changed_files.outputs.proxy_any_changed }} + build_monitor: ${{ steps.changed_files.outputs.monitor_any_changed }} + artifact_upload_to_s3: ${{ steps.set_env_variables.outputs.artifact_upload_to_s3 }} + artifact_s3_suffix: ${{ steps.set_env_variables.outputs.artifact_s3_suffix }} + + steps: + - id: set_env_variables + name: Set Environment Variables + run: | + if [ "${{ env.ARM64_BUILD }}" == "true" ] || ([ "${{ github.event_name }}" == "release" ] && [ "${{ env.IS_PRERELEASE }}" != "true" ]); then + echo "BUILDX_DRIVER=cloud" >> $GITHUB_OUTPUT + echo "BUILDX_VERSION=lab:latest" >> $GITHUB_OUTPUT + echo "BUILDX_PLATFORMS=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT + echo "BUILDX_ENDPOINT=makeplane/plane-dev" >> $GITHUB_OUTPUT + else + echo "BUILDX_DRIVER=docker-container" >> $GITHUB_OUTPUT + echo "BUILDX_VERSION=latest" >> $GITHUB_OUTPUT + echo "BUILDX_PLATFORMS=linux/amd64" >> $GITHUB_OUTPUT + echo "BUILDX_ENDPOINT=" >> $GITHUB_OUTPUT + fi + BR_NAME=$( echo "${{ env.TARGET_BRANCH }}" | tr / -) + echo "TARGET_BRANCH=$BR_NAME" >> $GITHUB_OUTPUT + + if [ "${{ github.event_name }}" == "release" ]; then + echo "artifact_upload_to_s3=true" >> $GITHUB_OUTPUT + echo "artifact_s3_suffix=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT + elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then + echo "artifact_upload_to_s3=true" >> $GITHUB_OUTPUT + echo "artifact_s3_suffix=latest" >> $GITHUB_OUTPUT + elif [ "${{ env.TARGET_BRANCH }}" == "preview" ] || [ "${{ env.TARGET_BRANCH }}" == "develop" ]; then + echo "artifact_upload_to_s3=true" >> $GITHUB_OUTPUT + echo "artifact_s3_suffix=${{ env.TARGET_BRANCH }}" >> $GITHUB_OUTPUT + else + echo "artifact_upload_to_s3=false" >> $GITHUB_OUTPUT + echo "artifact_s3_suffix=$BR_NAME" >> $GITHUB_OUTPUT + fi + + - id: checkout_files + name: Checkout Files + uses: actions/checkout@v4 + + - name: Get changed files + id: changed_files + uses: tj-actions/changed-files@v42 + with: + files_yaml: | + apiserver: + - apiserver/** + proxy: + - nginx/** + admin: + - admin/** + - packages/** + - "package.json" + - "yarn.lock" + - "tsconfig.json" + - "turbo.json" + space: + - space/** + - packages/** + - "package.json" + - "yarn.lock" + - "tsconfig.json" + - "turbo.json" + web: + - web/** + - packages/** + - "package.json" + - "yarn.lock" + - "tsconfig.json" + - "turbo.json" + monitor: + - monitor/** + + - name: Generate Keypair + run: | + if [ "${{ github.event_name }}" == "release" ]; then + ssh-keygen -t ed25519 -m PEM -f monitor/prime.key -N "" + echo "-----------------" + echo "" + cat monitor/prime.key.pub + echo "" + echo "-----------------" + else + echo "${{ secrets.DEFAULT_PRIME_PRIVATE_KEY }}" > monitor/prime.key + fi + + branch_build_push_admin: + if: ${{ needs.branch_build_setup.outputs.build_admin== 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }} + name: Build-Push Admin Docker Image + runs-on: ${{vars.ACTION_RUNS_ON}} + needs: [branch_build_setup] + env: + ADMIN_TAG: makeplane/admin-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }} + BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }} + BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }} + BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }} + BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }} + steps: + - name: Set Admin Docker Tag + run: | + if [ "${{ github.event_name }}" == "release" ]; then + TAG=makeplane/admin-enterprise:stable + TAG=${TAG},makeplane/admin-enterprise:${{ github.event.release.tag_name }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/admin-enterprise:stable + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/admin-enterprise:${{ github.event.release.tag_name }} + elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then + TAG=makeplane/admin-enterprise:latest + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/admin-enterprise:latest + else + TAG=${{ env.ADMIN_TAG }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/admin-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + fi + echo "ADMIN_TAG=${TAG}" >> $GITHUB_ENV + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to Harbor + uses: docker/login-action@v3 + with: + username: ${{ secrets.HARBOR_USERNAME }} + password: ${{ secrets.HARBOR_TOKEN }} + registry: ${{ vars.HARBOR_REGISTRY }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: ${{ env.BUILDX_DRIVER }} + version: ${{ env.BUILDX_VERSION }} + endpoint: ${{ env.BUILDX_ENDPOINT }} + + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Build and Push Frontend to Docker Container Registry + uses: docker/build-push-action@v5.1.0 + with: + context: . + file: ./admin/Dockerfile.admin + platforms: ${{ env.BUILDX_PLATFORMS }} + tags: ${{ env.ADMIN_TAG }} + push: true + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + + branch_build_push_web: + if: ${{ needs.branch_build_setup.outputs.build_web == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }} + name: Build-Push Web Docker Image + runs-on: ${{vars.ACTION_RUNS_ON}} + needs: [branch_build_setup] + env: + WEB_TAG: makeplane/web-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }} + BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }} + BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }} + BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }} + BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }} + steps: + - name: Set Web Docker Tag + run: | + if [ "${{ github.event_name }}" == "release" ]; then + TAG=makeplane/web-enterprise:stable + TAG=${TAG},makeplane/web-enterprise:${{ github.event.release.tag_name }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/web-enterprise:stable + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/web-enterprise:${{ github.event.release.tag_name }} + elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then + TAG=makeplane/web-enterprise:latest + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/web-enterprise:latest + else + TAG=${{ env.WEB_TAG }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/web-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + fi + echo "WEB_TAG=${TAG}" >> $GITHUB_ENV + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to Harbor + uses: docker/login-action@v3 + with: + username: ${{ secrets.HARBOR_USERNAME }} + password: ${{ secrets.HARBOR_TOKEN }} + registry: ${{ vars.HARBOR_REGISTRY }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: ${{ env.BUILDX_DRIVER }} + version: ${{ env.BUILDX_VERSION }} + endpoint: ${{ env.BUILDX_ENDPOINT }} + + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Build and Push Web to Docker Container Registry + uses: docker/build-push-action@v5.1.0 + with: + context: . + file: ./web/Dockerfile.web + platforms: ${{ env.BUILDX_PLATFORMS }} + tags: ${{ env.WEB_TAG }} + push: true + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + + branch_build_push_space: + if: ${{ needs.branch_build_setup.outputs.build_space == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }} + name: Build-Push Space Docker Image + runs-on: ${{vars.ACTION_RUNS_ON}} + needs: [branch_build_setup] + env: + SPACE_TAG: makeplane/space-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }} + BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }} + BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }} + BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }} + BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }} + steps: + - name: Set Space Docker Tag + run: | + if [ "${{ github.event_name }}" == "release" ]; then + TAG=makeplane/space-enterprise:stable + TAG=${TAG},makeplane/space-enterprise:${{ github.event.release.tag_name }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/space-enterprise:stable + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/space-enterprise:${{ github.event.release.tag_name }} + elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then + TAG=makeplane/space-enterprise:latest + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/space-enterprise:latest + else + TAG=${{ env.SPACE_TAG }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/space-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + fi + echo "SPACE_TAG=${TAG}" >> $GITHUB_ENV + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to Harbor + uses: docker/login-action@v3 + with: + username: ${{ secrets.HARBOR_USERNAME }} + password: ${{ secrets.HARBOR_TOKEN }} + registry: ${{ vars.HARBOR_REGISTRY }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: ${{ env.BUILDX_DRIVER }} + version: ${{ env.BUILDX_VERSION }} + endpoint: ${{ env.BUILDX_ENDPOINT }} + + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Build and Push Space to Docker Hub + uses: docker/build-push-action@v5.1.0 + with: + context: . + file: ./space/Dockerfile.space + platforms: ${{ env.BUILDX_PLATFORMS }} + tags: ${{ env.SPACE_TAG }} + push: true + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + + branch_build_push_apiserver: + if: ${{ needs.branch_build_setup.outputs.build_apiserver == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }} + name: Build-Push API Server Docker Image + runs-on: ${{vars.ACTION_RUNS_ON}} + needs: [branch_build_setup] + env: + BACKEND_TAG: makeplane/backend-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }} + BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }} + BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }} + BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }} + BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }} + steps: + - name: Set Backend Docker Tag + run: | + if [ "${{ github.event_name }}" == "release" ]; then + TAG=makeplane/backend-enterprise:stable + TAG=${TAG},makeplane/backend-enterprise:${{ github.event.release.tag_name }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/backend-enterprise:stable + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/backend-enterprise:${{ github.event.release.tag_name }} + elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then + TAG=makeplane/backend-enterprise:latest + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/backend-enterprise:latest + else + TAG=${{ env.BACKEND_TAG }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/backend-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + fi + echo "BACKEND_TAG=${TAG}" >> $GITHUB_ENV + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to Harbor + uses: docker/login-action@v3 + with: + username: ${{ secrets.HARBOR_USERNAME }} + password: ${{ secrets.HARBOR_TOKEN }} + registry: ${{ vars.HARBOR_REGISTRY }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: ${{ env.BUILDX_DRIVER }} + version: ${{ env.BUILDX_VERSION }} + endpoint: ${{ env.BUILDX_ENDPOINT }} + + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Build and Push Backend to Docker Hub + uses: docker/build-push-action@v5.1.0 + with: + context: ./apiserver + file: ./apiserver/Dockerfile.api + platforms: ${{ env.BUILDX_PLATFORMS }} + push: true + tags: ${{ env.BACKEND_TAG }} + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + + branch_build_push_proxy: + if: ${{ needs.branch_build_setup.outputs.build_proxy == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }} + name: Build-Push Proxy Docker Image + runs-on: ${{vars.ACTION_RUNS_ON}} + needs: [branch_build_setup] + env: + PROXY_TAG: makeplane/proxy-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }} + BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }} + BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }} + BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }} + BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }} + steps: + - name: Set Proxy Docker Tag + run: | + if [ "${{ github.event_name }}" == "release" ]; then + TAG=makeplane/proxy-enterprise:stable + TAG=${TAG},makeplane/proxy-enterprise:${{ github.event.release.tag_name }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/proxy-enterprise:stable + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/proxy-enterprise:${{ github.event.release.tag_name }} + elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then + TAG=makeplane/proxy-enterprise:latest + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/proxy-enterprise:latest + else + TAG=${{ env.PROXY_TAG }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/proxy-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + fi + echo "PROXY_TAG=${TAG}" >> $GITHUB_ENV + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to Harbor + uses: docker/login-action@v3 + with: + username: ${{ secrets.HARBOR_USERNAME }} + password: ${{ secrets.HARBOR_TOKEN }} + registry: ${{ vars.HARBOR_REGISTRY }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: ${{ env.BUILDX_DRIVER }} + version: ${{ env.BUILDX_VERSION }} + endpoint: ${{ env.BUILDX_ENDPOINT }} + + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Build and Push Plane-Proxy to Docker Hub + uses: docker/build-push-action@v5.1.0 + with: + context: ./nginx + file: ./nginx/Dockerfile + platforms: ${{ env.BUILDX_PLATFORMS }} + tags: ${{ env.PROXY_TAG }} + push: true + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + + branch_build_push_monitor: + if: ${{ needs.branch_build_setup.outputs.build_monitor == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }} + name: Build-Push Monitor Docker Image + runs-on: ${{vars.ACTION_RUNS_ON}} + needs: [branch_build_setup] + env: + MONITOR_TAG: makeplane/monitor-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }} + BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }} + BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }} + BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }} + BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }} + steps: + - name: Set Monitor Docker Tag + run: | + if [ "${{ github.event_name }}" == "release" ]; then + TAG=makeplane/monitor-enterprise:stable + TAG=${TAG},makeplane/monitor-enterprise:${{ github.event.release.tag_name }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/monitor-enterprise:stable + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/monitor-enterprise:${{ github.event.release.tag_name }} + elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then + TAG=makeplane/monitor-enterprise:latest + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/monitor-enterprise:latest + else + TAG=${{ env.MONITOR_TAG }} + TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/monitor-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }} + fi + echo "MONITOR_TAG=${TAG}" >> $GITHUB_ENV + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to Harbor + uses: docker/login-action@v3 + with: + username: ${{ secrets.HARBOR_USERNAME }} + password: ${{ secrets.HARBOR_TOKEN }} + registry: ${{ vars.HARBOR_REGISTRY }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: ${{ env.BUILDX_DRIVER }} + version: ${{ env.BUILDX_VERSION }} + endpoint: ${{ env.BUILDX_ENDPOINT }} + + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Build and Push Monitor to Docker Container Registry + uses: docker/build-push-action@v5.1.0 + with: + context: ./monitor + file: ./monitor/Dockerfile + platforms: ${{ env.BUILDX_PLATFORMS }} + tags: ${{ env.MONITOR_TAG }} + push: true + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + + upload_artifacts_s3: + if: ${{ needs.branch_build_setup.outputs.artifact_upload_to_s3 == 'true' }} + name: Upload artifacts to S3 Bucket + runs-on: ${{vars.ACTION_RUNS_ON}} + needs: [branch_build_setup] + container: + image: docker:20.10.7 + credentials: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + env: + ARTIFACT_SUFFIX: ${{ needs.branch_build_setup.outputs.artifact_s3_suffix }} + AWS_ACCESS_KEY_ID: ${{ secrets.SELF_HOST_BUCKET_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.SELF_HOST_BUCKET_SECRET_KEY }} + TARGET_BRANCH: ${{ github.ref_name || github.event.release.target_commitish }} + steps: + - id: checkout_files + name: Checkout Files + uses: actions/checkout@v4 + + - name: Upload artifacts + run: | + apk update + apk add --no-cache aws-cli + + mkdir -p ~/${{ env.ARTIFACT_SUFFIX }} + + cp deploy/cli-install/variables.env ~/${{ env.ARTIFACT_SUFFIX }}/variables.env + cp deploy/cli-install/Caddyfile ~/${{ env.ARTIFACT_SUFFIX }}/Caddyfile + sed -e 's@${APP_RELEASE_VERSION}@'${{ env.ARTIFACT_SUFFIX }}'@' deploy/cli-install/docker-compose.yml > ~/${{ env.ARTIFACT_SUFFIX }}/docker-compose.yml + sed -e 's@${APP_RELEASE_VERSION}@'${{ env.ARTIFACT_SUFFIX }}'@' deploy/cli-install/docker-compose-caddy.yml > ~/${{ env.ARTIFACT_SUFFIX }}/docker-compose-caddy.yml + + aws s3 cp ~/${{ env.ARTIFACT_SUFFIX }} s3://${{ vars.SELF_HOST_BUCKET_NAME }}/plane-enterprise/${{ env.ARTIFACT_SUFFIX }} --recursive + + rm -rf ~/${{ env.ARTIFACT_SUFFIX }} diff --git a/.github/workflows/build-test-pull-request-ee.yml b/.github/workflows/build-test-pull-request-ee.yml new file mode 100644 index 0000000000..a30db970bd --- /dev/null +++ b/.github/workflows/build-test-pull-request-ee.yml @@ -0,0 +1,162 @@ +name: Build and Lint on Pull Request EE + +on: + workflow_dispatch: + issue_comment: + types: [created] + +jobs: + get-changed-files: + if: github.event.issue.pull_request != '' && github.event.comment.body == 'build-test-pr' + runs-on: ubuntu-latest + outputs: + apiserver_changed: ${{ steps.changed-files.outputs.apiserver_any_changed }} + admin_changed: ${{ steps.changed-files.outputs.admin_any_changed }} + space_changed: ${{ steps.changed-files.outputs.space_any_changed }} + web_changed: ${{ steps.changed-files.outputs.web_any_changed }} + monitor_changed: ${{ steps.changed-files.outputs.monitor_any_changed }} + steps: + - uses: actions/checkout@v4 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v44 + with: + files_yaml: | + apiserver: + - apiserver/** + admin: + - admin/** + - packages/** + - 'package.json' + - 'yarn.lock' + - 'tsconfig.json' + - 'turbo.json' + space: + - space/** + - packages/** + - 'package.json' + - 'yarn.lock' + - 'tsconfig.json' + - 'turbo.json' + web: + - web/** + - packages/** + - 'package.json' + - 'yarn.lock' + - 'tsconfig.json' + - 'turbo.json' + monitor: + - monitor/** + + lint-apiserver: + needs: get-changed-files + runs-on: ubuntu-latest + if: needs.get-changed-files.outputs.apiserver_changed == 'true' + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" # Specify the Python version you need + - name: Install Pylint + run: python -m pip install ruff + - name: Install Apiserver Dependencies + run: cd apiserver && pip install -r requirements.txt + - name: Lint apiserver + run: ruff check --fix apiserver + + lint-admin: + needs: get-changed-files + if: needs.get-changed-files.outputs.admin_changed == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.x + - run: yarn install + - run: yarn lint --filter=admin + + lint-space: + needs: get-changed-files + if: needs.get-changed-files.outputs.space_changed == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.x + - run: yarn install + - run: yarn lint --filter=space + + lint-web: + needs: get-changed-files + if: needs.get-changed-files.outputs.web_changed == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.x + - run: yarn install + - run: yarn lint --filter=web + + test-monitor: + needs: get-changed-files + if: needs.get-changed-files.outputs.monitor_changed == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.22.2" + - run: cd ./monitor && make test + + build-admin: + needs: lint-admin + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.x + - run: yarn install + - run: yarn build --filter=admin + + build-space: + needs: lint-space + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.x + - run: yarn install + - run: yarn build --filter=space + + build-web: + needs: lint-web + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.x + - run: yarn install + - run: yarn build --filter=web + + build-monitor: + needs: test-monitor + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.22.2" + - run: cd ./monitor && make build diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000000..3c6f5a0056 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,70 @@ +name: Manual Release Workflow + +on: + workflow_dispatch: + inputs: + release_tag: + description: 'Release Tag (e.g., v0.16-cannary-1)' + required: true + prerelease: + description: 'Pre-Release' + required: true + default: true + type: boolean + draft: + description: 'Draft' + required: true + default: true + type: boolean + +permissions: + contents: write + +jobs: + create-release: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 # Necessary to fetch all history for tags + + - name: Set up Git + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + + - name: Check for the Prerelease + run: | + echo ${{ github.event.release.prerelease }} + + - name: Generate Release Notes + id: generate_notes + run: | + bash ./generate_release_notes.sh + # Directly use the content of RELEASE_NOTES.md for the release body + RELEASE_NOTES=$(cat RELEASE_NOTES.md) + echo "RELEASE_NOTES<> $GITHUB_ENV + echo "$RELEASE_NOTES" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Create Tag + run: | + git tag ${{ github.event.inputs.release_tag }} + git push origin ${{ github.event.inputs.release_tag }} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.event.inputs.release_tag }} + body_path: RELEASE_NOTES.md + draft: ${{ github.event.inputs.draft }} + prerelease: ${{ github.event.inputs.prerelease }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + + + + + diff --git a/.gitignore b/.gitignore index 80607b92f2..2dc2b85715 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,5 @@ deploy/selfhost/plane-app/ ## Storybook *storybook.log output.css +monitor/prime.key +monitor/prime.key.pub diff --git a/admin/app/authentication/oidc/form.tsx b/admin/app/authentication/oidc/form.tsx new file mode 100644 index 0000000000..3d40967a27 --- /dev/null +++ b/admin/app/authentication/oidc/form.tsx @@ -0,0 +1,244 @@ +import { FC, useState } from "react"; +import Link from "next/link"; +import { useForm } from "react-hook-form"; +// types +import { IFormattedInstanceConfiguration, TInstanceOIDCAuthenticationConfigurationKeys } from "@plane/types"; +// ui +import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui"; +// components +import { + ConfirmDiscardModal, + ControllerInput, + TControllerInputFormField, + CopyField, + TCopyField, + CodeBlock, +} from "@/components/common"; +// helpers +import { cn } from "@/helpers/common.helper"; +// hooks +import { useInstance } from "@/hooks/store"; + +type Props = { + config: IFormattedInstanceConfiguration; +}; + +type OIDCConfigFormValues = Record; + +export const InstanceOIDCConfigForm: FC = (props) => { + const { config } = props; + // states + const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false); + // store hooks + const { updateInstanceConfigurations } = useInstance(); + // form data + const { + handleSubmit, + control, + reset, + formState: { errors, isDirty, isSubmitting }, + } = useForm({ + defaultValues: { + OIDC_CLIENT_ID: config["OIDC_CLIENT_ID"], + OIDC_CLIENT_SECRET: config["OIDC_CLIENT_SECRET"], + OIDC_TOKEN_URL: config["OIDC_TOKEN_URL"], + OIDC_USERINFO_URL: config["OIDC_USERINFO_URL"], + OIDC_AUTHORIZE_URL: config["OIDC_AUTHORIZE_URL"], + OIDC_LOGOUT_URL: config["OIDC_LOGOUT_URL"], + OIDC_PROVIDER_NAME: config["OIDC_PROVIDER_NAME"], + }, + }); + + const originURL = typeof window !== "undefined" ? window.location.origin : ""; + + const OIDC_FORM_FIELDS: TControllerInputFormField[] = [ + { + key: "OIDC_CLIENT_ID", + type: "text", + label: "Client ID", + description: "A unique ID for this Plane app that you register on your IdP", + placeholder: "abc123xyz789", + error: Boolean(errors.OIDC_CLIENT_ID), + required: true, + }, + { + key: "OIDC_CLIENT_SECRET", + type: "password", + label: "Client secret", + description: "The secret key that authenticates this Plane app to your IdP", + placeholder: "s3cr3tK3y123!", + error: Boolean(errors.OIDC_CLIENT_SECRET), + required: true, + }, + { + key: "OIDC_AUTHORIZE_URL", + type: "text", + label: "Authorize URL", + description: ( + <> + The URL that brings up your IdP{"'"}s authentication screen when your users click the{" "} + {"Continue with"} + + ), + placeholder: "https://example.com/", + error: Boolean(errors.OIDC_AUTHORIZE_URL), + required: true, + }, + { + key: "OIDC_TOKEN_URL", + type: "text", + label: "Token URL", + description: "The URL that talks to the IdP and persists user authentication on Plane", + placeholder: "https://example.com/oauth/token", + error: Boolean(errors.OIDC_TOKEN_URL), + required: true, + }, + { + key: "OIDC_USERINFO_URL", + type: "text", + label: "Users' info URL", + description: "The URL that fetches your users' info from your IdP", + placeholder: "https://example.com/userinfo", + error: Boolean(errors.OIDC_USERINFO_URL), + required: true, + }, + { + key: "OIDC_LOGOUT_URL", + type: "text", + label: "Logout URL", + description: "Optional field that controls where your users go after they log out of Plane", + placeholder: "https://example.com/logout", + error: Boolean(errors.OIDC_LOGOUT_URL), + required: false, + }, + { + key: "OIDC_PROVIDER_NAME", + type: "text", + label: "IdP's name", + description: ( + <> + Optional field for the name that your users see on the Continue with button + + ), + placeholder: "Okta", + error: Boolean(errors.OIDC_PROVIDER_NAME), + required: false, + }, + ]; + + const OIDC_SERVICE_DETAILS: TCopyField[] = [ + { + key: "Origin_URI", + label: "Origin URI", + url: `${originURL}/auth/oidc/`, + description: + "We will generate this for this Plane app. Add this as a trusted origin on your IdP's corresponding field.", + }, + { + key: "Callback_URI", + label: "Callback URI", + url: `${originURL}/auth/oidc/callback/`, + description: ( + <> + We will generate this for you.Add this in the{" "} + Sign-in redirect URI field of + your IdP. + + ), + }, + { + key: "Logout_URI", + label: "Logout URI", + url: `${originURL}/auth/oidc/logout/`, + description: ( + <> + We will generate this for you. Add this in the{" "} + Logout redirect URI field of + your IdP. + + ), + }, + ]; + + const onSubmit = async (formData: OIDCConfigFormValues) => { + const payload: Partial = { ...formData }; + + await updateInstanceConfigurations(payload) + .then((response = []) => { + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Done!", + message: "Your OIDC-based authentication is configured. You should test it now.", + }); + reset({ + OIDC_CLIENT_ID: response.find((item) => item.key === "OIDC_CLIENT_ID")?.value, + OIDC_CLIENT_SECRET: response.find((item) => item.key === "OIDC_CLIENT_SECRET")?.value, + OIDC_AUTHORIZE_URL: response.find((item) => item.key === "OIDC_AUTHORIZE_URL")?.value, + OIDC_TOKEN_URL: response.find((item) => item.key === "OIDC_TOKEN_URL")?.value, + OIDC_USERINFO_URL: response.find((item) => item.key === "OIDC_USERINFO_URL")?.value, + OIDC_LOGOUT_URL: response.find((item) => item.key === "OIDC_LOGOUT_URL")?.value, + OIDC_PROVIDER_NAME: response.find((item) => item.key === "OIDC_PROVIDER_NAME")?.value, + }); + }) + .catch((err) => console.error(err)); + }; + + const handleGoBack = (e: React.MouseEvent) => { + if (isDirty) { + e.preventDefault(); + setIsDiscardChangesModalOpen(true); + } + }; + + return ( + <> + setIsDiscardChangesModalOpen(false)} + /> +
+
+
+
IdP-provided details for Plane
+ {OIDC_FORM_FIELDS.map((field) => ( + + ))} +
+
+ + + Go back + +
+
+
+
+
+
Plane-provided details for your IdP
+ {OIDC_SERVICE_DETAILS.map((field) => ( + + ))} +
+
+
+
+ + ); +}; diff --git a/admin/app/authentication/oidc/page.tsx b/admin/app/authentication/oidc/page.tsx new file mode 100644 index 0000000000..c63c1dd308 --- /dev/null +++ b/admin/app/authentication/oidc/page.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { useState } from "react"; +import Image from "next/image"; +import { observer } from "mobx-react-lite"; +import useSWR from "swr"; +// hooks +import { useInstance } from "@/hooks/store"; +// ui +import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui"; +// components +import { PageHeader } from "@/components/common"; +import { AuthenticationMethodCard } from "@/components/authentication"; +import { InstanceOIDCConfigForm } from "./form"; +// icons +import OIDCLogo from "/public/logos/oidc-logo.svg"; + +const InstanceOIDCAuthenticationPage = observer(() => { + // store + const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance(); + // state + const [isSubmitting, setIsSubmitting] = useState(false); + // config + const enableOIDCConfig = formattedConfig?.IS_OIDC_ENABLED ?? ""; + + useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); + + const updateConfig = async (key: "IS_OIDC_ENABLED", value: string) => { + setIsSubmitting(true); + + const payload = { + [key]: value, + }; + + const updateConfigPromise = updateInstanceConfigurations(payload); + + setPromiseToast(updateConfigPromise, { + loading: "Saving Configuration...", + success: { + title: "Configuration saved", + message: () => `OIDC authentication is now ${value ? "active" : "disabled"}.`, + }, + error: { + title: "Error", + message: () => "Failed to save configuration", + }, + }); + + await updateConfigPromise + .then(() => { + setIsSubmitting(false); + }) + .catch((err) => { + console.error(err); + setIsSubmitting(false); + }); + }; + return ( + <> + +
+
+ } + config={ + { + Boolean(parseInt(enableOIDCConfig)) === true + ? updateConfig("IS_OIDC_ENABLED", "0") + : updateConfig("IS_OIDC_ENABLED", "1"); + }} + size="sm" + disabled={isSubmitting || !formattedConfig} + /> + } + disabled={isSubmitting || !formattedConfig} + withBorder={false} + /> +
+
+ {formattedConfig ? ( + + ) : ( + + + + + + + + + )} +
+
+ + ); +}); + +export default InstanceOIDCAuthenticationPage; diff --git a/admin/app/authentication/saml/form.tsx b/admin/app/authentication/saml/form.tsx new file mode 100644 index 0000000000..87800fa53b --- /dev/null +++ b/admin/app/authentication/saml/form.tsx @@ -0,0 +1,245 @@ +import { FC, useState } from "react"; +import Link from "next/link"; +import { Controller, useForm } from "react-hook-form"; +// types +import { IFormattedInstanceConfiguration, TInstanceSAMLAuthenticationConfigurationKeys } from "@plane/types"; +// ui +import { Button, TOAST_TYPE, TextArea, getButtonStyling, setToast } from "@plane/ui"; +// components +import { + ConfirmDiscardModal, + ControllerInput, + TControllerInputFormField, + CopyField, + TCopyField, + CodeBlock, +} from "@/components/common"; +// helpers +import { cn } from "@/helpers/common.helper"; +// hooks +import { useInstance } from "@/hooks/store"; +import { SAMLAttributeMappingTable } from "@/plane-admin/components/authentication"; + +type Props = { + config: IFormattedInstanceConfiguration; +}; + +type SAMLConfigFormValues = Record; + +export const InstanceSAMLConfigForm: FC = (props) => { + const { config } = props; + // states + const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false); + // store hooks + const { updateInstanceConfigurations } = useInstance(); + // form data + const { + handleSubmit, + control, + reset, + formState: { errors, isDirty, isSubmitting }, + } = useForm({ + defaultValues: { + SAML_ENTITY_ID: config["SAML_ENTITY_ID"], + SAML_SSO_URL: config["SAML_SSO_URL"], + SAML_LOGOUT_URL: config["SAML_LOGOUT_URL"], + SAML_CERTIFICATE: config["SAML_CERTIFICATE"], + SAML_PROVIDER_NAME: config["SAML_PROVIDER_NAME"], + }, + }); + + const originURL = typeof window !== "undefined" ? window.location.origin : ""; + + const SAML_FORM_FIELDS: TControllerInputFormField[] = [ + { + key: "SAML_ENTITY_ID", + type: "text", + label: "Entity ID", + description: "A unique ID for this Plane app that you register on your IdP", + placeholder: "70a44354520df8bd9bcd", + error: Boolean(errors.SAML_ENTITY_ID), + required: true, + }, + { + key: "SAML_SSO_URL", + type: "text", + label: "SSO URL", + description: ( + <> + The URL that brings up your IdP{"'"}s authentication screen when your users click the{" "} + {"Continue with"} button + + ), + placeholder: "https://example.com/sso", + error: Boolean(errors.SAML_SSO_URL), + required: true, + }, + { + key: "SAML_LOGOUT_URL", + type: "text", + label: "Logout URL", + description: "Optional field that tells your IdP your users have logged out of this Plane app", + placeholder: "https://example.com/logout", + error: Boolean(errors.SAML_LOGOUT_URL), + required: false, + }, + { + key: "SAML_PROVIDER_NAME", + type: "text", + label: "IdP's name", + description: ( + <> + Optional field for the name that your users see on the Continue with button + + ), + placeholder: "Okta", + error: Boolean(errors.SAML_PROVIDER_NAME), + required: false, + }, + ]; + + const SAML_SERVICE_DETAILS: TCopyField[] = [ + { + key: "Metadata_Information", + label: "Entity ID | Audience | Metadata information", + url: `${originURL}/auth/saml/metadata/`, + description: + "We will generate this bit of the metadata that identifies this Plane app as an authorized service on your IdP.", + }, + { + key: "Callback_URI", + label: "Callback URI", + url: `${originURL}/auth/saml/callback/`, + description: ( + <> + We will generate this{" "} + http-post request URL that you + should paste into your ACS URL{" "} + or Sign-in call back URL field + on your IdP. + + ), + }, + { + key: "Logout_URI", + label: "Logout URI", + url: `${originURL}/auth/saml/logout/`, + description: ( + <> + We will generate this{" "} + http-redirect request URL that + you should paste into your{" "} + SLS URL or{" "} + Logout URL + field on your IdP. + + ), + }, + ]; + + const onSubmit = async (formData: SAMLConfigFormValues) => { + const payload: Partial = { ...formData }; + + await updateInstanceConfigurations(payload) + .then((response = []) => { + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Done!", + message: "Your SAML-based authentication is configured. You should test it now.", + }); + reset({ + SAML_ENTITY_ID: response.find((item) => item.key === "SAML_ENTITY_ID")?.value, + SAML_SSO_URL: response.find((item) => item.key === "SAML_SSO_URL")?.value, + SAML_LOGOUT_URL: response.find((item) => item.key === "SAML_LOGOUT_URL")?.value, + SAML_CERTIFICATE: response.find((item) => item.key === "SAML_CERTIFICATE")?.value, + SAML_PROVIDER_NAME: response.find((item) => item.key === "SAML_PROVIDER_NAME")?.value, + }); + }) + .catch((err) => console.error(err)); + }; + + const handleGoBack = (e: React.MouseEvent) => { + if (isDirty) { + e.preventDefault(); + setIsDiscardChangesModalOpen(true); + } + }; + + return ( + <> + setIsDiscardChangesModalOpen(false)} + /> +
+
+
+
IdP-provided details for Plane
+ {SAML_FORM_FIELDS.map((field) => ( + + ))} +
+

SAML certificate

+ ( +