release: v0.28.0 #7488
This commit is contained in:
commit
1e235600b7
3921 changed files with 23914 additions and 16337 deletions
16
.env.example
16
.env.example
|
|
@ -15,12 +15,15 @@ RABBITMQ_USER="plane"
|
||||||
RABBITMQ_PASSWORD="plane"
|
RABBITMQ_PASSWORD="plane"
|
||||||
RABBITMQ_VHOST="plane"
|
RABBITMQ_VHOST="plane"
|
||||||
|
|
||||||
|
LISTEN_HTTP_PORT=80
|
||||||
|
LISTEN_HTTPS_PORT=443
|
||||||
|
|
||||||
# AWS Settings
|
# AWS Settings
|
||||||
AWS_REGION=""
|
AWS_REGION=""
|
||||||
AWS_ACCESS_KEY_ID="access-key"
|
AWS_ACCESS_KEY_ID="access-key"
|
||||||
AWS_SECRET_ACCESS_KEY="secret-key"
|
AWS_SECRET_ACCESS_KEY="secret-key"
|
||||||
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
|
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
|
||||||
# Changing this requires change in the nginx.conf for uploads if using minio setup
|
# Changing this requires change in the proxy config for uploads if using minio setup
|
||||||
AWS_S3_BUCKET_NAME="uploads"
|
AWS_S3_BUCKET_NAME="uploads"
|
||||||
# Maximum file upload limit
|
# Maximum file upload limit
|
||||||
FILE_SIZE_LIMIT=5242880
|
FILE_SIZE_LIMIT=5242880
|
||||||
|
|
@ -36,8 +39,15 @@ DOCKERIZED=1 # deprecated
|
||||||
# set to 1 If using the pre-configured minio setup
|
# set to 1 If using the pre-configured minio setup
|
||||||
USE_MINIO=1
|
USE_MINIO=1
|
||||||
|
|
||||||
# Nginx Configuration
|
# If SSL Cert to be generated, set CERT_EMAIl="email <EMAIL_ADDRESS>"
|
||||||
NGINX_PORT=80
|
CERT_ACME_CA=https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
TRUSTED_PROXIES=0.0.0.0/0
|
||||||
|
SITE_ADDRESS=:80
|
||||||
|
CERT_EMAIL=
|
||||||
|
|
||||||
|
# For DNS Challenge based certificate generation, set the CERT_ACME_DNS, CERT_EMAIL
|
||||||
|
# CERT_ACME_DNS="acme_dns <CERT_DNS_PROVIDER> <CERT_DNS_PROVIDER_API_KEY>"
|
||||||
|
CERT_ACME_DNS=
|
||||||
|
|
||||||
# Force HTTPS for handling SSL Termination
|
# Force HTTPS for handling SSL Termination
|
||||||
MINIO_ENDPOINT_SSL=0
|
MINIO_ENDPOINT_SSL=0
|
||||||
|
|
|
||||||
139
.github/workflows/build-aio-base.yml
vendored
139
.github/workflows/build-aio-base.yml
vendored
|
|
@ -1,139 +0,0 @@
|
||||||
name: Build AIO Base Image
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
base_tag_name:
|
|
||||||
description: 'Base Tag Name'
|
|
||||||
required: false
|
|
||||||
default: ''
|
|
||||||
|
|
||||||
env:
|
|
||||||
TARGET_BRANCH: ${{ github.ref_name }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
base_build_setup:
|
|
||||||
name: Build Preparation
|
|
||||||
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 }}
|
|
||||||
image_tag: ${{ steps.set_env_variables.outputs.IMAGE_TAG }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- id: set_env_variables
|
|
||||||
name: Set Environment Variables
|
|
||||||
run: |
|
|
||||||
echo "TARGET_BRANCH=${{ env.TARGET_BRANCH }}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
if [ "${{ github.event.inputs.base_tag_name }}" != "" ]; then
|
|
||||||
echo "IMAGE_TAG=${{ github.event.inputs.base_tag_name }}" >> $GITHUB_OUTPUT
|
|
||||||
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
|
|
||||||
echo "IMAGE_TAG=latest" >> $GITHUB_OUTPUT
|
|
||||||
elif [ "${{ env.TARGET_BRANCH }}" == "preview" ]; then
|
|
||||||
echo "IMAGE_TAG=preview" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "IMAGE_TAG=develop" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [ "${{ env.TARGET_BRANCH }}" == "master" ]; 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
|
|
||||||
|
|
||||||
- id: checkout_files
|
|
||||||
name: Checkout Files
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
full_base_build_push:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [base_build_setup]
|
|
||||||
env:
|
|
||||||
BASE_IMG_TAG: makeplane/plane-aio-base:full-${{ needs.base_build_setup.outputs.image_tag }}
|
|
||||||
BUILDX_DRIVER: ${{ needs.base_build_setup.outputs.gh_buildx_driver }}
|
|
||||||
BUILDX_VERSION: ${{ needs.base_build_setup.outputs.gh_buildx_version }}
|
|
||||||
BUILDX_PLATFORMS: ${{ needs.base_build_setup.outputs.gh_buildx_platforms }}
|
|
||||||
BUILDX_ENDPOINT: ${{ needs.base_build_setup.outputs.gh_buildx_endpoint }}
|
|
||||||
steps:
|
|
||||||
- name: Check out the repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- 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: Build and Push to Docker Hub
|
|
||||||
uses: docker/build-push-action@v6.9.0
|
|
||||||
with:
|
|
||||||
context: ./aio
|
|
||||||
file: ./aio/Dockerfile-base-full
|
|
||||||
platforms: ${{ env.BUILDX_PLATFORMS }}
|
|
||||||
tags: ${{ env.BASE_IMG_TAG }}
|
|
||||||
push: true
|
|
||||||
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_base_build_push:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [base_build_setup]
|
|
||||||
env:
|
|
||||||
BASE_IMG_TAG: makeplane/plane-aio-base:slim-${{ needs.base_build_setup.outputs.image_tag }}
|
|
||||||
BUILDX_DRIVER: ${{ needs.base_build_setup.outputs.gh_buildx_driver }}
|
|
||||||
BUILDX_VERSION: ${{ needs.base_build_setup.outputs.gh_buildx_version }}
|
|
||||||
BUILDX_PLATFORMS: ${{ needs.base_build_setup.outputs.gh_buildx_platforms }}
|
|
||||||
BUILDX_ENDPOINT: ${{ needs.base_build_setup.outputs.gh_buildx_endpoint }}
|
|
||||||
steps:
|
|
||||||
- name: Check out the repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- 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: Build and Push to Docker Hub
|
|
||||||
uses: docker/build-push-action@v6.9.0
|
|
||||||
with:
|
|
||||||
context: ./aio
|
|
||||||
file: ./aio/Dockerfile-base-slim
|
|
||||||
platforms: ${{ env.BUILDX_PLATFORMS }}
|
|
||||||
tags: ${{ env.BASE_IMG_TAG }}
|
|
||||||
push: true
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
env:
|
|
||||||
DOCKER_BUILDKIT: 1
|
|
||||||
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
207
.github/workflows/build-aio-branch.yml
vendored
207
.github/workflows/build-aio-branch.yml
vendored
|
|
@ -1,207 +0,0 @@
|
||||||
name: Branch Build AIO
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
full:
|
|
||||||
description: 'Run full build'
|
|
||||||
type: boolean
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
slim:
|
|
||||||
description: 'Run slim build'
|
|
||||||
type: boolean
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
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 }}
|
|
||||||
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 }}
|
|
||||||
flat_branch_name: ${{ steps.set_env_variables.outputs.FLAT_BRANCH_NAME }}
|
|
||||||
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 [ "${{ env.TARGET_BRANCH }}" == "master" ] || [ "${{ github.event_name }}" == "release" ]; 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_name}}" == "workflow_dispatch" ] && [ "${{ 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
|
|
||||||
|
|
||||||
FLAT_BRANCH_NAME=$(echo "${{ env.TARGET_BRANCH }}" | sed 's/[^a-zA-Z0-9]/-/g')
|
|
||||||
echo "FLAT_BRANCH_NAME=$FLAT_BRANCH_NAME" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- 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-22.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:full-${{ needs.branch_build_setup.outputs.flat_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:${{env.BUILD_TYPE}}-stable,makeplane/plane-aio:${{env.BUILD_TYPE}}-${{ github.event.release.tag_name }}
|
|
||||||
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
|
|
||||||
TAG=makeplane/plane-aio:${{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@v6.9.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./aio/Dockerfile-app
|
|
||||||
platforms: ${{ env.BUILDX_PLATFORMS }}
|
|
||||||
tags: ${{ env.AIO_IMAGE_TAGS }}
|
|
||||||
push: true
|
|
||||||
build-args: |
|
|
||||||
BASE_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-22.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:slim-${{ needs.branch_build_setup.outputs.flat_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:${{env.BUILD_TYPE}}-stable,makeplane/plane-aio:${{env.BUILD_TYPE}}-${{ github.event.release.tag_name }}
|
|
||||||
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
|
|
||||||
TAG=makeplane/plane-aio:${{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@v6.9.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./aio/Dockerfile-app
|
|
||||||
platforms: ${{ env.BUILDX_PLATFORMS }}
|
|
||||||
tags: ${{ env.AIO_IMAGE_TAGS }}
|
|
||||||
push: true
|
|
||||||
build-args: |
|
|
||||||
BASE_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 }}
|
|
||||||
143
.github/workflows/build-branch.yml
vendored
143
.github/workflows/build-branch.yml
vendored
|
|
@ -25,6 +25,11 @@ on:
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
|
aio_build:
|
||||||
|
description: "Build for AIO docker image"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- preview
|
- preview
|
||||||
|
|
@ -36,6 +41,7 @@ env:
|
||||||
BUILD_TYPE: ${{ github.event.inputs.build_type }}
|
BUILD_TYPE: ${{ github.event.inputs.build_type }}
|
||||||
RELEASE_VERSION: ${{ github.event.inputs.releaseVersion }}
|
RELEASE_VERSION: ${{ github.event.inputs.releaseVersion }}
|
||||||
IS_PRERELEASE: ${{ github.event.inputs.isPrerelease }}
|
IS_PRERELEASE: ${{ github.event.inputs.isPrerelease }}
|
||||||
|
AIO_BUILD: ${{ github.event.inputs.aio_build }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
branch_build_setup:
|
branch_build_setup:
|
||||||
|
|
@ -54,11 +60,13 @@ jobs:
|
||||||
dh_img_live: ${{ steps.set_env_variables.outputs.DH_IMG_LIVE }}
|
dh_img_live: ${{ steps.set_env_variables.outputs.DH_IMG_LIVE }}
|
||||||
dh_img_backend: ${{ steps.set_env_variables.outputs.DH_IMG_BACKEND }}
|
dh_img_backend: ${{ steps.set_env_variables.outputs.DH_IMG_BACKEND }}
|
||||||
dh_img_proxy: ${{ steps.set_env_variables.outputs.DH_IMG_PROXY }}
|
dh_img_proxy: ${{ steps.set_env_variables.outputs.DH_IMG_PROXY }}
|
||||||
|
dh_img_aio: ${{ steps.set_env_variables.outputs.DH_IMG_AIO }}
|
||||||
|
|
||||||
build_type: ${{steps.set_env_variables.outputs.BUILD_TYPE}}
|
build_type: ${{steps.set_env_variables.outputs.BUILD_TYPE}}
|
||||||
build_release: ${{ steps.set_env_variables.outputs.BUILD_RELEASE }}
|
build_release: ${{ steps.set_env_variables.outputs.BUILD_RELEASE }}
|
||||||
build_prerelease: ${{ steps.set_env_variables.outputs.BUILD_PRERELEASE }}
|
build_prerelease: ${{ steps.set_env_variables.outputs.BUILD_PRERELEASE }}
|
||||||
release_version: ${{ steps.set_env_variables.outputs.RELEASE_VERSION }}
|
release_version: ${{ steps.set_env_variables.outputs.RELEASE_VERSION }}
|
||||||
|
aio_build: ${{ steps.set_env_variables.outputs.AIO_BUILD }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: set_env_variables
|
- id: set_env_variables
|
||||||
|
|
@ -84,12 +92,15 @@ jobs:
|
||||||
echo "DH_IMG_LIVE=plane-live" >> $GITHUB_OUTPUT
|
echo "DH_IMG_LIVE=plane-live" >> $GITHUB_OUTPUT
|
||||||
echo "DH_IMG_BACKEND=plane-backend" >> $GITHUB_OUTPUT
|
echo "DH_IMG_BACKEND=plane-backend" >> $GITHUB_OUTPUT
|
||||||
echo "DH_IMG_PROXY=plane-proxy" >> $GITHUB_OUTPUT
|
echo "DH_IMG_PROXY=plane-proxy" >> $GITHUB_OUTPUT
|
||||||
|
echo "DH_IMG_AIO=plane-aio-community" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
echo "BUILD_TYPE=${{env.BUILD_TYPE}}" >> $GITHUB_OUTPUT
|
echo "BUILD_TYPE=${{env.BUILD_TYPE}}" >> $GITHUB_OUTPUT
|
||||||
BUILD_RELEASE=false
|
BUILD_RELEASE=false
|
||||||
BUILD_PRERELEASE=false
|
BUILD_PRERELEASE=false
|
||||||
RELVERSION="latest"
|
RELVERSION="latest"
|
||||||
|
|
||||||
|
BUILD_AIO=${{ env.AIO_BUILD }}
|
||||||
|
|
||||||
if [ "${{ env.BUILD_TYPE }}" == "Release" ]; then
|
if [ "${{ env.BUILD_TYPE }}" == "Release" ]; then
|
||||||
FLAT_RELEASE_VERSION=$(echo "${{ env.RELEASE_VERSION }}" | sed 's/[^a-zA-Z0-9.-]//g')
|
FLAT_RELEASE_VERSION=$(echo "${{ env.RELEASE_VERSION }}" | sed 's/[^a-zA-Z0-9.-]//g')
|
||||||
echo "FLAT_RELEASE_VERSION=${FLAT_RELEASE_VERSION}" >> $GITHUB_OUTPUT
|
echo "FLAT_RELEASE_VERSION=${FLAT_RELEASE_VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
|
@ -108,10 +119,14 @@ jobs:
|
||||||
if [ "${{ env.IS_PRERELEASE }}" == "true" ]; then
|
if [ "${{ env.IS_PRERELEASE }}" == "true" ]; then
|
||||||
BUILD_PRERELEASE=true
|
BUILD_PRERELEASE=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
BUILD_AIO=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "BUILD_RELEASE=${BUILD_RELEASE}" >> $GITHUB_OUTPUT
|
echo "BUILD_RELEASE=${BUILD_RELEASE}" >> $GITHUB_OUTPUT
|
||||||
echo "BUILD_PRERELEASE=${BUILD_PRERELEASE}" >> $GITHUB_OUTPUT
|
echo "BUILD_PRERELEASE=${BUILD_PRERELEASE}" >> $GITHUB_OUTPUT
|
||||||
echo "RELEASE_VERSION=${RELVERSION}" >> $GITHUB_OUTPUT
|
echo "RELEASE_VERSION=${RELVERSION}" >> $GITHUB_OUTPUT
|
||||||
|
echo "AIO_BUILD=${BUILD_AIO}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- id: checkout_files
|
- id: checkout_files
|
||||||
name: Checkout Files
|
name: Checkout Files
|
||||||
|
|
@ -133,7 +148,7 @@ jobs:
|
||||||
docker-image-owner: makeplane
|
docker-image-owner: makeplane
|
||||||
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_admin }}
|
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_admin }}
|
||||||
build-context: .
|
build-context: .
|
||||||
dockerfile-path: ./admin/Dockerfile.admin
|
dockerfile-path: ./apps/admin/Dockerfile.admin
|
||||||
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
||||||
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
||||||
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
||||||
|
|
@ -155,7 +170,7 @@ jobs:
|
||||||
docker-image-owner: makeplane
|
docker-image-owner: makeplane
|
||||||
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_web }}
|
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_web }}
|
||||||
build-context: .
|
build-context: .
|
||||||
dockerfile-path: ./web/Dockerfile.web
|
dockerfile-path: ./apps/web/Dockerfile.web
|
||||||
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
||||||
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
||||||
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
||||||
|
|
@ -177,7 +192,7 @@ jobs:
|
||||||
docker-image-owner: makeplane
|
docker-image-owner: makeplane
|
||||||
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_space }}
|
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_space }}
|
||||||
build-context: .
|
build-context: .
|
||||||
dockerfile-path: ./space/Dockerfile.space
|
dockerfile-path: ./apps/space/Dockerfile.space
|
||||||
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
||||||
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
||||||
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
||||||
|
|
@ -199,13 +214,13 @@ jobs:
|
||||||
docker-image-owner: makeplane
|
docker-image-owner: makeplane
|
||||||
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_live }}
|
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_live }}
|
||||||
build-context: .
|
build-context: .
|
||||||
dockerfile-path: ./live/Dockerfile.live
|
dockerfile-path: ./apps/live/Dockerfile.live
|
||||||
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
||||||
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
||||||
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
||||||
buildx-endpoint: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
|
buildx-endpoint: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
|
||||||
|
|
||||||
branch_build_push_apiserver:
|
branch_build_push_api:
|
||||||
name: Build-Push API Server Docker Image
|
name: Build-Push API Server Docker Image
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: [branch_build_setup]
|
needs: [branch_build_setup]
|
||||||
|
|
@ -220,8 +235,8 @@ jobs:
|
||||||
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
|
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
docker-image-owner: makeplane
|
docker-image-owner: makeplane
|
||||||
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_backend }}
|
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_backend }}
|
||||||
build-context: ./apiserver
|
build-context: ./apps/api
|
||||||
dockerfile-path: ./apiserver/Dockerfile.api
|
dockerfile-path: ./apps/api/Dockerfile.api
|
||||||
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
||||||
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
||||||
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
||||||
|
|
@ -242,13 +257,102 @@ jobs:
|
||||||
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
|
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
docker-image-owner: makeplane
|
docker-image-owner: makeplane
|
||||||
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_proxy }}
|
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_proxy }}
|
||||||
build-context: ./nginx
|
build-context: ./apps/proxy
|
||||||
dockerfile-path: ./nginx/Dockerfile
|
dockerfile-path: ./apps/proxy/Dockerfile.ce
|
||||||
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
||||||
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
||||||
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
||||||
buildx-endpoint: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
|
buildx-endpoint: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
|
||||||
|
|
||||||
|
branch_build_push_aio:
|
||||||
|
if: ${{ needs.branch_build_setup.outputs.aio_build == 'true' }}
|
||||||
|
name: Build-Push AIO Docker Image
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: [
|
||||||
|
branch_build_setup,
|
||||||
|
branch_build_push_admin,
|
||||||
|
branch_build_push_web,
|
||||||
|
branch_build_push_space,
|
||||||
|
branch_build_push_live,
|
||||||
|
branch_build_push_api,
|
||||||
|
branch_build_push_proxy
|
||||||
|
]
|
||||||
|
steps:
|
||||||
|
- name: Checkout Files
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Prepare AIO Assets
|
||||||
|
id: prepare_aio_assets
|
||||||
|
run: |
|
||||||
|
cd deployments/aio/community
|
||||||
|
|
||||||
|
if [ "${{ needs.branch_build_setup.outputs.build_type }}" == "Release" ]; then
|
||||||
|
aio_version=${{ needs.branch_build_setup.outputs.release_version }}
|
||||||
|
else
|
||||||
|
aio_version=${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||||
|
fi
|
||||||
|
bash ./build.sh --release $aio_version
|
||||||
|
echo "AIO_BUILD_VERSION=${aio_version}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload AIO Assets
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./deployments/aio/community/dist
|
||||||
|
name: aio-assets-dist
|
||||||
|
|
||||||
|
- name: AIO Build and Push
|
||||||
|
uses: makeplane/actions/build-push@v1.1.0
|
||||||
|
with:
|
||||||
|
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
|
||||||
|
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
|
||||||
|
release-version: ${{ needs.branch_build_setup.outputs.release_version }}
|
||||||
|
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
docker-image-owner: makeplane
|
||||||
|
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_aio }}
|
||||||
|
build-context: ./deployments/aio/community
|
||||||
|
dockerfile-path: ./deployments/aio/community/Dockerfile
|
||||||
|
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 }}
|
||||||
|
additional-assets: aio-assets-dist
|
||||||
|
additional-assets-dir: ./deployments/aio/community/dist
|
||||||
|
build-args: |
|
||||||
|
PLANE_VERSION=${{ steps.prepare_aio_assets.outputs.AIO_BUILD_VERSION }}
|
||||||
|
|
||||||
|
upload_build_assets:
|
||||||
|
name: Upload Build Assets
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: [branch_build_setup, branch_build_push_admin, branch_build_push_web, branch_build_push_space, branch_build_push_live, branch_build_push_api, branch_build_push_proxy]
|
||||||
|
steps:
|
||||||
|
- name: Checkout Files
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Update Assets
|
||||||
|
run: |
|
||||||
|
if [ "${{ needs.branch_build_setup.outputs.build_type }}" == "Release" ]; then
|
||||||
|
REL_VERSION=${{ needs.branch_build_setup.outputs.release_version }}
|
||||||
|
else
|
||||||
|
REL_VERSION=${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp ./deployments/cli/community/install.sh deployments/cli/community/setup.sh
|
||||||
|
sed -i 's/${APP_RELEASE:-stable}/${APP_RELEASE:-'${REL_VERSION}'}/g' deployments/cli/community/docker-compose.yml
|
||||||
|
# sed -i 's/APP_RELEASE=stable/APP_RELEASE='${REL_VERSION}'/g' deployments/cli/community/variables.env
|
||||||
|
|
||||||
|
- name: Upload Assets
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: community-assets
|
||||||
|
path: |
|
||||||
|
./deployments/cli/community/setup.sh
|
||||||
|
./deployments/cli/community/restore.sh
|
||||||
|
./deployments/cli/community/restore-airgapped.sh
|
||||||
|
./deployments/cli/community/docker-compose.yml
|
||||||
|
./deployments/cli/community/variables.env
|
||||||
|
./deployments/swarm/community/swarm.sh
|
||||||
|
|
||||||
publish_release:
|
publish_release:
|
||||||
if: ${{ needs.branch_build_setup.outputs.build_type == 'Release' }}
|
if: ${{ needs.branch_build_setup.outputs.build_type == 'Release' }}
|
||||||
name: Build Release
|
name: Build Release
|
||||||
|
|
@ -260,7 +364,7 @@ jobs:
|
||||||
branch_build_push_web,
|
branch_build_push_web,
|
||||||
branch_build_push_space,
|
branch_build_push_space,
|
||||||
branch_build_push_live,
|
branch_build_push_live,
|
||||||
branch_build_push_apiserver,
|
branch_build_push_api,
|
||||||
branch_build_push_proxy,
|
branch_build_push_proxy,
|
||||||
]
|
]
|
||||||
env:
|
env:
|
||||||
|
|
@ -271,9 +375,9 @@ jobs:
|
||||||
|
|
||||||
- name: Update Assets
|
- name: Update Assets
|
||||||
run: |
|
run: |
|
||||||
cp ./deploy/selfhost/install.sh deploy/selfhost/setup.sh
|
cp ./deployments/cli/community/install.sh deployments/cli/community/setup.sh
|
||||||
sed -i 's/${APP_RELEASE:-stable}/${APP_RELEASE:-'${REL_VERSION}'}/g' deploy/selfhost/docker-compose.yml
|
sed -i 's/${APP_RELEASE:-stable}/${APP_RELEASE:-'${REL_VERSION}'}/g' deployments/cli/community/docker-compose.yml
|
||||||
# sed -i 's/APP_RELEASE=stable/APP_RELEASE='${REL_VERSION}'/g' deploy/selfhost/variables.env
|
# sed -i 's/APP_RELEASE=stable/APP_RELEASE='${REL_VERSION}'/g' deployments/cli/community/variables.env
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
id: create_release
|
id: create_release
|
||||||
|
|
@ -287,9 +391,10 @@ jobs:
|
||||||
prerelease: ${{ env.IS_PRERELEASE }}
|
prerelease: ${{ env.IS_PRERELEASE }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
${{ github.workspace }}/deploy/selfhost/setup.sh
|
${{ github.workspace }}/deployments/cli/community/setup.sh
|
||||||
${{ github.workspace }}/deploy/selfhost/swarm.sh
|
${{ github.workspace }}/deployments/cli/community/restore.sh
|
||||||
${{ github.workspace }}/deploy/selfhost/restore.sh
|
${{ github.workspace }}/deployments/cli/community/restore-airgapped.sh
|
||||||
${{ github.workspace }}/deploy/selfhost/restore-airgapped.sh
|
${{ github.workspace }}/deployments/cli/community/docker-compose.yml
|
||||||
${{ github.workspace }}/deploy/selfhost/docker-compose.yml
|
${{ github.workspace }}/deployments/cli/community/variables.env
|
||||||
${{ github.workspace }}/deploy/selfhost/variables.env
|
${{ github.workspace }}/deployments/swarm/community/swarm.sh
|
||||||
|
|
||||||
|
|
|
||||||
10
.github/workflows/build-test-pull-request.yml
vendored
10
.github/workflows/build-test-pull-request.yml
vendored
|
|
@ -6,7 +6,7 @@ on:
|
||||||
types: ["opened", "synchronize", "ready_for_review"]
|
types: ["opened", "synchronize", "ready_for_review"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint-apiserver:
|
lint-server:
|
||||||
if: github.event.pull_request.draft == false
|
if: github.event.pull_request.draft == false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -17,10 +17,10 @@ jobs:
|
||||||
python-version: "3.x" # Specify the Python version you need
|
python-version: "3.x" # Specify the Python version you need
|
||||||
- name: Install Pylint
|
- name: Install Pylint
|
||||||
run: python -m pip install ruff
|
run: python -m pip install ruff
|
||||||
- name: Install Apiserver Dependencies
|
- name: Install Server Dependencies
|
||||||
run: cd apiserver && pip install -r requirements.txt
|
run: cd apps/server && pip install -r requirements.txt
|
||||||
- name: Lint apiserver
|
- name: Lint apps/server
|
||||||
run: ruff check --fix apiserver
|
run: ruff check --fix apps/server
|
||||||
|
|
||||||
lint-admin:
|
lint-admin:
|
||||||
if: github.event.pull_request.draft == false
|
if: github.event.pull_request.draft == false
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ When opening a new issue, please use a clear and concise title that follows this
|
||||||
- For documentation: `📘 Docs: [short description]`
|
- For documentation: `📘 Docs: [short description]`
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
|
|
||||||
- `🐛 Bug: API token expiry time not saving correctly`
|
- `🐛 Bug: API token expiry time not saving correctly`
|
||||||
- `📘 Docs: Clarify RAM requirement for local setup`
|
- `📘 Docs: Clarify RAM requirement for local setup`
|
||||||
- `🚀 Feature: Allow custom time selection for token expiration`
|
- `🚀 Feature: Allow custom time selection for token expiration`
|
||||||
|
|
@ -47,7 +48,7 @@ This helps us triage and manage issues more efficiently.
|
||||||
|
|
||||||
The project is a monorepo, with backend api and frontend in a single repo.
|
The project is a monorepo, with backend api and frontend in a single repo.
|
||||||
|
|
||||||
The backend is a django project which is kept inside apiserver
|
The backend is a django project which is kept inside apps/api
|
||||||
|
|
||||||
1. Clone the repo
|
1. Clone the repo
|
||||||
|
|
||||||
|
|
@ -105,11 +106,13 @@ To ensure consistency throughout the source code, please keep these rules in min
|
||||||
- **Improve documentation** - fix incomplete or missing [docs](https://docs.plane.so/), bad wording, examples or explanations.
|
- **Improve documentation** - fix incomplete or missing [docs](https://docs.plane.so/), bad wording, examples or explanations.
|
||||||
|
|
||||||
## Contributing to language support
|
## Contributing to language support
|
||||||
|
|
||||||
This guide is designed to help contributors understand how to add or update translations in the application.
|
This guide is designed to help contributors understand how to add or update translations in the application.
|
||||||
|
|
||||||
### Understanding translation structure
|
### Understanding translation structure
|
||||||
|
|
||||||
#### File organization
|
#### File organization
|
||||||
|
|
||||||
Translations are organized by language in the locales directory. Each language has its own folder containing JSON files for translations. Here's how it looks:
|
Translations are organized by language in the locales directory. Each language has its own folder containing JSON files for translations. Here's how it looks:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -122,7 +125,9 @@ packages/i18n/src/locales/
|
||||||
└── [language]/
|
└── [language]/
|
||||||
└── translations.json
|
└── translations.json
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Nested structure
|
#### Nested structure
|
||||||
|
|
||||||
To keep translations organized, we use a nested structure for keys. This makes it easier to manage and locate specific translations. For example:
|
To keep translations organized, we use a nested structure for keys. This makes it easier to manage and locate specific translations. For example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
@ -137,32 +142,37 @@ To keep translations organized, we use a nested structure for keys. This makes i
|
||||||
```
|
```
|
||||||
|
|
||||||
### Translation formatting guide
|
### Translation formatting guide
|
||||||
|
|
||||||
We use [IntlMessageFormat](https://formatjs.github.io/docs/intl-messageformat/) to handle dynamic content, such as variables and pluralization. Here's how to format your translations:
|
We use [IntlMessageFormat](https://formatjs.github.io/docs/intl-messageformat/) to handle dynamic content, such as variables and pluralization. Here's how to format your translations:
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
- **Simple variables**
|
- **Simple variables**
|
||||||
```json
|
|
||||||
{
|
```json
|
||||||
|
{
|
||||||
"greeting": "Hello, {name}!"
|
"greeting": "Hello, {name}!"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Pluralization**
|
- **Pluralization**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"items": "{count, plural, one {Work item} other {Work items}}"
|
"items": "{count, plural, one {Work item} other {Work items}}"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Contributing guidelines
|
### Contributing guidelines
|
||||||
|
|
||||||
#### Updating existing translations
|
#### Updating existing translations
|
||||||
|
|
||||||
1. Locate the key in `locales/<language>/translations.json`.
|
1. Locate the key in `locales/<language>/translations.json`.
|
||||||
|
|
||||||
2. Update the value while ensuring the key structure remains intact.
|
2. Update the value while ensuring the key structure remains intact.
|
||||||
3. Preserve any existing ICU formats (e.g., variables, pluralization).
|
3. Preserve any existing ICU formats (e.g., variables, pluralization).
|
||||||
|
|
||||||
#### Adding new translation keys
|
#### Adding new translation keys
|
||||||
|
|
||||||
1. When introducing a new key, ensure it is added to **all** language files, even if translations are not immediately available. Use English as a placeholder if needed.
|
1. When introducing a new key, ensure it is added to **all** language files, even if translations are not immediately available. Use English as a placeholder if needed.
|
||||||
|
|
||||||
2. Keep the nesting structure consistent across all languages.
|
2. Keep the nesting structure consistent across all languages.
|
||||||
|
|
@ -170,48 +180,48 @@ We use [IntlMessageFormat](https://formatjs.github.io/docs/intl-messageformat/)
|
||||||
3. If the new key requires dynamic content (e.g., variables or pluralization), ensure the ICU format is applied uniformly across all languages.
|
3. If the new key requires dynamic content (e.g., variables or pluralization), ensure the ICU format is applied uniformly across all languages.
|
||||||
|
|
||||||
### Adding new languages
|
### Adding new languages
|
||||||
|
|
||||||
Adding a new language involves several steps to ensure it integrates seamlessly with the project. Follow these instructions carefully:
|
Adding a new language involves several steps to ensure it integrates seamlessly with the project. Follow these instructions carefully:
|
||||||
|
|
||||||
1. **Update type definitions**
|
1. **Update type definitions**
|
||||||
Add the new language to the TLanguage type in the language definitions file:
|
Add the new language to the TLanguage type in the language definitions file:
|
||||||
|
|
||||||
```typescript
|
```ts
|
||||||
// types/language.ts
|
// packages/i18n/src/types/language.ts
|
||||||
export type TLanguage = "en" | "fr" | "your-lang";
|
export type TLanguage = "en" | "fr" | "your-lang";
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Add language configuration**
|
1. **Add language configuration**
|
||||||
Include the new language in the list of supported languages:
|
Include the new language in the list of supported languages:
|
||||||
|
```ts
|
||||||
|
// packages/i18n/src/constants/language.ts
|
||||||
|
export const SUPPORTED_LANGUAGES: ILanguageOption[] = [
|
||||||
|
{ label: "English", value: "en" },
|
||||||
|
{ label: "Your Language", value: "your-lang" }
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
```typescript
|
2. **Create translation files**
|
||||||
// constants/language.ts
|
1. Create a new folder for your language under locales (e.g., `locales/your-lang/`).
|
||||||
export const SUPPORTED_LANGUAGES: ILanguageOption[] = [
|
|
||||||
{ label: "English", value: "en" },
|
|
||||||
{ label: "Your Language", value: "your-lang" }
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Create translation files**
|
|
||||||
1. Create a new folder for your language under locales (e.g., `locales/your-lang/`).
|
|
||||||
|
|
||||||
2. Add a `translations.json` file inside the folder.
|
2. Add a `translations.json` file inside the folder.
|
||||||
|
|
||||||
3. Copy the structure from an existing translation file and translate all keys.
|
3. Copy the structure from an existing translation file and translate all keys.
|
||||||
|
|
||||||
4. **Update import logic**
|
3. **Update import logic**
|
||||||
Modify the language import logic to include your new language:
|
Modify the language import logic to include your new language:
|
||||||
|
```ts
|
||||||
```typescript
|
private importLanguageFile(language: TLanguage): Promise<any> {
|
||||||
private importLanguageFile(language: TLanguage): Promise<any> {
|
switch (language) {
|
||||||
switch (language) {
|
case "your-lang":
|
||||||
case "your-lang":
|
return import("../locales/your-lang/translations.json");
|
||||||
return import("../locales/your-lang/translations.json");
|
// ...
|
||||||
// ...
|
}
|
||||||
}
|
}
|
||||||
}
|
```
|
||||||
```
|
|
||||||
|
|
||||||
### Quality checklist
|
### Quality checklist
|
||||||
|
|
||||||
Before submitting your contribution, please ensure the following:
|
Before submitting your contribution, please ensure the following:
|
||||||
|
|
||||||
- All translation keys exist in every language file.
|
- All translation keys exist in every language file.
|
||||||
|
|
@ -222,6 +232,7 @@ Before submitting your contribution, please ensure the following:
|
||||||
- There are no missing or untranslated keys.
|
- There are no missing or untranslated keys.
|
||||||
|
|
||||||
#### Pro tips
|
#### Pro tips
|
||||||
|
|
||||||
- When in doubt, refer to the English translations for context.
|
- When in doubt, refer to the English translations for context.
|
||||||
- Verify pluralization works with different numbers.
|
- Verify pluralization works with different numbers.
|
||||||
- Ensure dynamic values (e.g., `{name}`) are correctly interpolated.
|
- Ensure dynamic values (e.g., `{name}`) are correctly interpolated.
|
||||||
|
|
|
||||||
88
ENV_SETUP.md
88
ENV_SETUP.md
|
|
@ -1,88 +0,0 @@
|
||||||
# Environment Variables
|
|
||||||
|
|
||||||
Environment variables are distributed in various files. Please refer them carefully.
|
|
||||||
|
|
||||||
## {PROJECT_FOLDER}/.env
|
|
||||||
|
|
||||||
File is available in the project root folder
|
|
||||||
|
|
||||||
```
|
|
||||||
# Database Settings
|
|
||||||
POSTGRES_USER="plane"
|
|
||||||
POSTGRES_PASSWORD="plane"
|
|
||||||
POSTGRES_DB="plane"
|
|
||||||
PGDATA="/var/lib/postgresql/data"
|
|
||||||
# Redis Settings
|
|
||||||
REDIS_HOST="plane-redis"
|
|
||||||
REDIS_PORT="6379"
|
|
||||||
# AWS Settings
|
|
||||||
AWS_REGION=""
|
|
||||||
AWS_ACCESS_KEY_ID="access-key"
|
|
||||||
AWS_SECRET_ACCESS_KEY="secret-key"
|
|
||||||
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
|
|
||||||
# Changing this requires change in the nginx.conf for uploads if using minio setup
|
|
||||||
AWS_S3_BUCKET_NAME="uploads"
|
|
||||||
# Maximum file upload limit
|
|
||||||
FILE_SIZE_LIMIT=5242880
|
|
||||||
# GPT settings
|
|
||||||
OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
|
|
||||||
OPENAI_API_KEY="sk-" # deprecated
|
|
||||||
GPT_ENGINE="gpt-3.5-turbo" # deprecated
|
|
||||||
# Settings related to Docker
|
|
||||||
DOCKERIZED=1 # deprecated
|
|
||||||
# set to 1 If using the pre-configured minio setup
|
|
||||||
USE_MINIO=1
|
|
||||||
# Nginx Configuration
|
|
||||||
NGINX_PORT=80
|
|
||||||
```
|
|
||||||
|
|
||||||
## {PROJECT_FOLDER}/apiserver/.env
|
|
||||||
|
|
||||||
```
|
|
||||||
# Backend
|
|
||||||
# Debug value for api server use it as 0 for production use
|
|
||||||
DEBUG=0
|
|
||||||
CORS_ALLOWED_ORIGINS="http://localhost"
|
|
||||||
# Database Settings
|
|
||||||
POSTGRES_USER="plane"
|
|
||||||
POSTGRES_PASSWORD="plane"
|
|
||||||
POSTGRES_HOST="plane-db"
|
|
||||||
POSTGRES_DB="plane"
|
|
||||||
POSTGRES_PORT=5432
|
|
||||||
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
|
||||||
# Redis Settings
|
|
||||||
REDIS_HOST="plane-redis"
|
|
||||||
REDIS_PORT="6379"
|
|
||||||
REDIS_URL="redis://${REDIS_HOST}:6379/"
|
|
||||||
# AWS Settings
|
|
||||||
AWS_REGION=""
|
|
||||||
AWS_ACCESS_KEY_ID="access-key"
|
|
||||||
AWS_SECRET_ACCESS_KEY="secret-key"
|
|
||||||
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
|
|
||||||
# Changing this requires change in the nginx.conf for uploads if using minio setup
|
|
||||||
AWS_S3_BUCKET_NAME="uploads"
|
|
||||||
# Maximum file upload limit
|
|
||||||
FILE_SIZE_LIMIT=5242880
|
|
||||||
# Settings related to Docker
|
|
||||||
DOCKERIZED=1 # deprecated
|
|
||||||
# set to 1 If using the pre-configured minio setup
|
|
||||||
USE_MINIO=1
|
|
||||||
# Nginx Configuration
|
|
||||||
NGINX_PORT=80
|
|
||||||
# Email redirections and minio domain settings
|
|
||||||
WEB_URL="http://localhost"
|
|
||||||
# Gunicorn Workers
|
|
||||||
GUNICORN_WORKERS=2
|
|
||||||
# Base URLs
|
|
||||||
ADMIN_BASE_URL=
|
|
||||||
SPACE_BASE_URL=
|
|
||||||
APP_BASE_URL=
|
|
||||||
SECRET_KEY="gxoytl7dmnc1y37zahah820z5iq3iozu38cnfjtu3yaau9cd9z"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Updates
|
|
||||||
|
|
||||||
- The naming convention for containers and images has been updated.
|
|
||||||
- The plane-worker image will no longer be maintained, as it has been merged with plane-backend.
|
|
||||||
- The Tiptap pro-extension dependency has been removed, eliminating the need for Tiptap API keys.
|
|
||||||
- The image name for Plane deployment has been changed to plane-space.
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { Loader } from "@plane/ui";
|
|
||||||
// hooks
|
|
||||||
import { useInstance } from "@/hooks/store";
|
|
||||||
// components
|
|
||||||
import { InstanceEmailForm } from "./email-config-form";
|
|
||||||
|
|
||||||
const InstanceEmailPage = observer(() => {
|
|
||||||
// store
|
|
||||||
const { fetchInstanceConfigurations, formattedConfig } = useInstance();
|
|
||||||
|
|
||||||
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
|
|
||||||
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
|
|
||||||
<div className="text-xl font-medium text-custom-text-100">Secure emails from your own instance</div>
|
|
||||||
<div className="text-sm font-normal text-custom-text-300">
|
|
||||||
Plane can send useful emails to you and your users from your own instance without talking to the Internet.
|
|
||||||
<div className="text-sm font-normal text-custom-text-300">
|
|
||||||
Set it up below and please test your settings before you save them.
|
|
||||||
<span className="text-red-400">Misconfigs can lead to email bounces and errors.</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
|
|
||||||
{formattedConfig ? (
|
|
||||||
<InstanceEmailForm config={formattedConfig} />
|
|
||||||
) : (
|
|
||||||
<Loader className="space-y-10">
|
|
||||||
<Loader.Item height="50px" width="75%" />
|
|
||||||
<Loader.Item height="50px" width="75%" />
|
|
||||||
<Loader.Item height="50px" width="40%" />
|
|
||||||
<Loader.Item height="50px" width="40%" />
|
|
||||||
<Loader.Item height="50px" width="20%" />
|
|
||||||
</Loader>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default InstanceEmailPage;
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
import { ThemeProvider, useTheme } from "next-themes";
|
|
||||||
import { SWRConfig } from "swr";
|
|
||||||
// plane imports
|
|
||||||
import { ADMIN_BASE_PATH, DEFAULT_SWR_CONFIG } from "@plane/constants";
|
|
||||||
import { Toast } from "@plane/ui";
|
|
||||||
import { resolveGeneralTheme } from "@plane/utils";
|
|
||||||
// lib
|
|
||||||
import { InstanceProvider } from "@/lib/instance-provider";
|
|
||||||
import { StoreProvider } from "@/lib/store-provider";
|
|
||||||
import { UserProvider } from "@/lib/user-provider";
|
|
||||||
// styles
|
|
||||||
import "@/styles/globals.css";
|
|
||||||
|
|
||||||
const ToastWithTheme = () => {
|
|
||||||
const { resolvedTheme } = useTheme();
|
|
||||||
return <Toast theme={resolveGeneralTheme(resolvedTheme)} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
||||||
const ASSET_PREFIX = ADMIN_BASE_PATH;
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href={`${ASSET_PREFIX}/favicon/apple-touch-icon.png`} />
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href={`${ASSET_PREFIX}/favicon/favicon-32x32.png`} />
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href={`${ASSET_PREFIX}/favicon/favicon-16x16.png`} />
|
|
||||||
<link rel="manifest" href={`${ASSET_PREFIX}/site.webmanifest.json`} />
|
|
||||||
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
|
|
||||||
</head>
|
|
||||||
<body className={`antialiased`}>
|
|
||||||
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
|
||||||
<ToastWithTheme />
|
|
||||||
<SWRConfig value={DEFAULT_SWR_CONFIG}>
|
|
||||||
<StoreProvider>
|
|
||||||
<InstanceProvider>
|
|
||||||
<UserProvider>{children}</UserProvider>
|
|
||||||
</InstanceProvider>
|
|
||||||
</StoreProvider>
|
|
||||||
</SWRConfig>
|
|
||||||
</ThemeProvider>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { Metadata } from "next";
|
|
||||||
// components
|
|
||||||
import { InstanceSignInForm } from "@/components/login";
|
|
||||||
// layouts
|
|
||||||
import { DefaultLayout } from "@/layouts/default-layout";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "Plane | Simple, extensible, open-source project management tool.",
|
|
||||||
description:
|
|
||||||
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.",
|
|
||||||
openGraph: {
|
|
||||||
title: "Plane | Simple, extensible, open-source project management tool.",
|
|
||||||
description:
|
|
||||||
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.",
|
|
||||||
url: "https://plane.so/",
|
|
||||||
},
|
|
||||||
keywords:
|
|
||||||
"software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration",
|
|
||||||
twitter: {
|
|
||||||
site: "@planepowers",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function LoginPage() {
|
|
||||||
return (
|
|
||||||
<DefaultLayout>
|
|
||||||
<InstanceSignInForm />
|
|
||||||
</DefaultLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
// types
|
|
||||||
import {
|
|
||||||
TGetBaseAuthenticationModeProps,
|
|
||||||
TInstanceAuthenticationMethodKeys,
|
|
||||||
TInstanceAuthenticationModes,
|
|
||||||
} from "@plane/types";
|
|
||||||
// components
|
|
||||||
import { AuthenticationMethodCard } from "@/components/authentication";
|
|
||||||
// helpers
|
|
||||||
import { getBaseAuthenticationModes } from "@/lib/auth-helpers";
|
|
||||||
// plane admin components
|
|
||||||
import { UpgradeButton } from "@/plane-admin/components/common";
|
|
||||||
// images
|
|
||||||
import OIDCLogo from "@/public/logos/oidc-logo.svg";
|
|
||||||
import SAMLLogo from "@/public/logos/saml-logo.svg";
|
|
||||||
|
|
||||||
export type TAuthenticationModeProps = {
|
|
||||||
disabled: boolean;
|
|
||||||
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Authentication methods
|
|
||||||
export const getAuthenticationModes: (props: TGetBaseAuthenticationModeProps) => TInstanceAuthenticationModes[] = ({
|
|
||||||
disabled,
|
|
||||||
updateConfig,
|
|
||||||
resolvedTheme,
|
|
||||||
}) => [
|
|
||||||
...getBaseAuthenticationModes({ disabled, updateConfig, resolvedTheme }),
|
|
||||||
{
|
|
||||||
key: "oidc",
|
|
||||||
name: "OIDC",
|
|
||||||
description: "Authenticate your users via the OpenID Connect protocol.",
|
|
||||||
icon: <Image src={OIDCLogo} height={22} width={22} alt="OIDC Logo" />,
|
|
||||||
config: <UpgradeButton />,
|
|
||||||
unavailable: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "saml",
|
|
||||||
name: "SAML",
|
|
||||||
description: "Authenticate your users via the Security Assertion Markup Language protocol.",
|
|
||||||
icon: <Image src={SAMLLogo} height={22} width={22} alt="SAML Logo" className="pl-0.5" />,
|
|
||||||
config: <UpgradeButton />,
|
|
||||||
unavailable: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const AuthenticationModes: React.FC<TAuthenticationModeProps> = observer((props) => {
|
|
||||||
const { disabled, updateConfig } = props;
|
|
||||||
// next-themes
|
|
||||||
const { resolvedTheme } = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{getAuthenticationModes({ disabled, updateConfig, resolvedTheme }).map((method) => (
|
|
||||||
<AuthenticationMethodCard
|
|
||||||
key={method.key}
|
|
||||||
name={method.name}
|
|
||||||
description={method.description}
|
|
||||||
icon={method.icon}
|
|
||||||
config={method.config}
|
|
||||||
disabled={disabled}
|
|
||||||
unavailable={method.unavailable}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export * from "./root";
|
|
||||||
export * from "./help-section";
|
|
||||||
export * from "./sidebar-menu";
|
|
||||||
export * from "./sidebar-dropdown";
|
|
||||||
export * from "./sidebar-menu-hamburger-toogle";
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { FC } from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
// hooks
|
|
||||||
import { Menu } from "lucide-react";
|
|
||||||
import { useTheme } from "@/hooks/store";
|
|
||||||
// icons
|
|
||||||
|
|
||||||
export const SidebarHamburgerToggle: FC = observer(() => {
|
|
||||||
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="w-7 h-7 rounded flex justify-center items-center bg-custom-background-80 transition-all hover:bg-custom-background-90 cursor-pointer group md:hidden"
|
|
||||||
onClick={() => toggleSidebar(!isSidebarCollapsed)}
|
|
||||||
>
|
|
||||||
<Menu size={14} className="text-custom-text-200 group-hover:text-custom-text-100 transition-all" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
export * from "./auth-banner";
|
|
||||||
export * from "./email-config-switch";
|
|
||||||
export * from "./password-config-switch";
|
|
||||||
export * from "./authentication-method-card";
|
|
||||||
export * from "./gitlab-config";
|
|
||||||
export * from "./github-config";
|
|
||||||
export * from "./google-config";
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
export * from "./breadcrumb-link";
|
|
||||||
export * from "./confirm-discard-modal";
|
|
||||||
export * from "./controller-input";
|
|
||||||
export * from "./copy-field";
|
|
||||||
export * from "./password-strength-meter";
|
|
||||||
export * from "./banner";
|
|
||||||
export * from "./empty-state";
|
|
||||||
export * from "./logo-spinner";
|
|
||||||
export * from "./page-header";
|
|
||||||
export * from "./code-block";
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export * from "./instance-not-ready";
|
|
||||||
export * from "./instance-failure-view";
|
|
||||||
export * from "./setup-form";
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from "./sign-in-form";
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { FC, useEffect, useMemo, useState } from "react";
|
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
|
||||||
// plane internal packages
|
|
||||||
import { API_BASE_URL, EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants";
|
|
||||||
import { AuthService } from "@plane/services";
|
|
||||||
import { Button, Input, Spinner } from "@plane/ui";
|
|
||||||
// components
|
|
||||||
import { Banner } from "@/components/common";
|
|
||||||
// helpers
|
|
||||||
import { authErrorHandler } from "@/lib/auth-helpers";
|
|
||||||
// local components
|
|
||||||
import { AuthBanner } from "../authentication";
|
|
||||||
|
|
||||||
// service initialization
|
|
||||||
const authService = new AuthService();
|
|
||||||
|
|
||||||
// error codes
|
|
||||||
enum EErrorCodes {
|
|
||||||
INSTANCE_NOT_CONFIGURED = "INSTANCE_NOT_CONFIGURED",
|
|
||||||
REQUIRED_EMAIL_PASSWORD = "REQUIRED_EMAIL_PASSWORD",
|
|
||||||
INVALID_EMAIL = "INVALID_EMAIL",
|
|
||||||
USER_DOES_NOT_EXIST = "USER_DOES_NOT_EXIST",
|
|
||||||
AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED",
|
|
||||||
}
|
|
||||||
|
|
||||||
type TError = {
|
|
||||||
type: EErrorCodes | undefined;
|
|
||||||
message: string | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
// form data
|
|
||||||
type TFormData = {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultFromData: TFormData = {
|
|
||||||
email: "",
|
|
||||||
password: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InstanceSignInForm: FC = (props) => {
|
|
||||||
const {} = props;
|
|
||||||
// search params
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const emailParam = searchParams.get("email") || undefined;
|
|
||||||
const errorCode = searchParams.get("error_code") || undefined;
|
|
||||||
const errorMessage = searchParams.get("error_message") || undefined;
|
|
||||||
// state
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
|
|
||||||
const [formData, setFormData] = useState<TFormData>(defaultFromData);
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
||||||
const [errorInfo, setErrorInfo] = useState<TAdminAuthErrorInfo | undefined>(undefined);
|
|
||||||
|
|
||||||
const handleFormChange = (key: keyof TFormData, value: string | boolean) =>
|
|
||||||
setFormData((prev) => ({ ...prev, [key]: value }));
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (csrfToken === undefined)
|
|
||||||
authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
|
|
||||||
}, [csrfToken]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (emailParam) setFormData((prev) => ({ ...prev, email: emailParam }));
|
|
||||||
}, [emailParam]);
|
|
||||||
|
|
||||||
// derived values
|
|
||||||
const errorData: TError = useMemo(() => {
|
|
||||||
if (errorCode && errorMessage) {
|
|
||||||
switch (errorCode) {
|
|
||||||
case EErrorCodes.INSTANCE_NOT_CONFIGURED:
|
|
||||||
return { type: EErrorCodes.INVALID_EMAIL, message: errorMessage };
|
|
||||||
case EErrorCodes.REQUIRED_EMAIL_PASSWORD:
|
|
||||||
return { type: EErrorCodes.REQUIRED_EMAIL_PASSWORD, message: errorMessage };
|
|
||||||
case EErrorCodes.INVALID_EMAIL:
|
|
||||||
return { type: EErrorCodes.INVALID_EMAIL, message: errorMessage };
|
|
||||||
case EErrorCodes.USER_DOES_NOT_EXIST:
|
|
||||||
return { type: EErrorCodes.USER_DOES_NOT_EXIST, message: errorMessage };
|
|
||||||
case EErrorCodes.AUTHENTICATION_FAILED:
|
|
||||||
return { type: EErrorCodes.AUTHENTICATION_FAILED, message: errorMessage };
|
|
||||||
default:
|
|
||||||
return { type: undefined, message: undefined };
|
|
||||||
}
|
|
||||||
} else return { type: undefined, message: undefined };
|
|
||||||
}, [errorCode, errorMessage]);
|
|
||||||
|
|
||||||
const isButtonDisabled = useMemo(
|
|
||||||
() => (!isSubmitting && formData.email && formData.password ? false : true),
|
|
||||||
[formData.email, formData.password, isSubmitting]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (errorCode) {
|
|
||||||
const errorDetail = authErrorHandler(errorCode?.toString() as EAdminAuthErrorCodes);
|
|
||||||
if (errorDetail) {
|
|
||||||
setErrorInfo(errorDetail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [errorCode]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
|
|
||||||
<div className="relative flex flex-col space-y-6">
|
|
||||||
<div className="text-center space-y-1">
|
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
|
||||||
Manage your Plane instance
|
|
||||||
</h3>
|
|
||||||
<p className="font-medium text-onboarding-text-400">
|
|
||||||
Configure instance-wide settings to secure your instance
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{errorData.type && errorData?.message ? (
|
|
||||||
<Banner type="error" message={errorData?.message} />
|
|
||||||
) : (
|
|
||||||
<>{errorInfo && <AuthBanner bannerData={errorInfo} handleBannerData={(value) => setErrorInfo(value)} />}</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<form
|
|
||||||
className="space-y-4"
|
|
||||||
method="POST"
|
|
||||||
action={`${API_BASE_URL}/api/instances/admins/sign-in/`}
|
|
||||||
onSubmit={() => setIsSubmitting(true)}
|
|
||||||
onError={() => setIsSubmitting(false)}
|
|
||||||
>
|
|
||||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
|
||||||
|
|
||||||
<div className="w-full space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
|
||||||
Email <span className="text-red-500">*</span>
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
inputSize="md"
|
|
||||||
placeholder="name@company.com"
|
|
||||||
value={formData.email}
|
|
||||||
onChange={(e) => handleFormChange("email", e.target.value)}
|
|
||||||
autoComplete="on"
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
|
||||||
Password <span className="text-red-500">*</span>
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
inputSize="md"
|
|
||||||
placeholder="Enter your password"
|
|
||||||
value={formData.password}
|
|
||||||
onChange={(e) => handleFormChange("password", e.target.value)}
|
|
||||||
autoComplete="on"
|
|
||||||
/>
|
|
||||||
{showPassword ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
|
||||||
onClick={() => setShowPassword(false)}
|
|
||||||
>
|
|
||||||
<EyeOff className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
|
||||||
onClick={() => setShowPassword(true)}
|
|
||||||
>
|
|
||||||
<Eye className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="py-2">
|
|
||||||
<Button type="submit" size="lg" className="w-full" disabled={isButtonDisabled}>
|
|
||||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Sign in"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from "./list-item";
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
import { FC, ReactNode } from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import useSWR from "swr";
|
|
||||||
// components
|
|
||||||
import { LogoSpinner } from "@/components/common";
|
|
||||||
import { InstanceSetupForm, InstanceFailureView } from "@/components/instance";
|
|
||||||
// hooks
|
|
||||||
import { useInstance } from "@/hooks/store";
|
|
||||||
// layout
|
|
||||||
import { DefaultLayout } from "@/layouts/default-layout";
|
|
||||||
|
|
||||||
type InstanceProviderProps = {
|
|
||||||
children: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InstanceProvider: FC<InstanceProviderProps> = observer((props) => {
|
|
||||||
const { children } = props;
|
|
||||||
// store hooks
|
|
||||||
const { instance, error, fetchInstanceInfo } = useInstance();
|
|
||||||
// fetching instance details
|
|
||||||
useSWR("INSTANCE_DETAILS", () => fetchInstanceInfo(), {
|
|
||||||
revalidateOnFocus: false,
|
|
||||||
revalidateIfStale: false,
|
|
||||||
errorRetryCount: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!instance && !error)
|
|
||||||
return (
|
|
||||||
<div className="flex h-screen min-h-[500px] w-full justify-center items-center">
|
|
||||||
<LogoSpinner />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<DefaultLayout>
|
|
||||||
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
|
|
||||||
<InstanceFailureView />
|
|
||||||
</div>
|
|
||||||
</DefaultLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instance?.is_setup_done) {
|
|
||||||
return (
|
|
||||||
<DefaultLayout>
|
|
||||||
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
|
|
||||||
<InstanceSetupForm />
|
|
||||||
</div>
|
|
||||||
</DefaultLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>{children}</>;
|
|
||||||
});
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
/** @type {import('next').NextConfig} */
|
|
||||||
|
|
||||||
const nextConfig = {
|
|
||||||
trailingSlash: true,
|
|
||||||
reactStrictMode: false,
|
|
||||||
swcMinify: true,
|
|
||||||
output: "standalone",
|
|
||||||
images: {
|
|
||||||
unoptimized: true,
|
|
||||||
},
|
|
||||||
basePath: process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "",
|
|
||||||
transpilePackages: [
|
|
||||||
"@plane/constants",
|
|
||||||
"@plane/editor",
|
|
||||||
"@plane/hooks",
|
|
||||||
"@plane/i18n",
|
|
||||||
"@plane/logger",
|
|
||||||
"@plane/propel",
|
|
||||||
"@plane/services",
|
|
||||||
"@plane/shared-state",
|
|
||||||
"@plane/types",
|
|
||||||
"@plane/ui",
|
|
||||||
"@plane/utils",
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = nextConfig;
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
ARG BASE_TAG=develop
|
|
||||||
ARG BUILD_TYPE=full
|
|
||||||
# *****************************************************************************
|
|
||||||
# STAGE 1: Build the project
|
|
||||||
# *****************************************************************************
|
|
||||||
FROM node:18-alpine AS builder
|
|
||||||
RUN apk add --no-cache libc6-compat
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN yarn global add turbo
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN turbo prune --scope=web --scope=space --scope=admin --docker
|
|
||||||
|
|
||||||
# *****************************************************************************
|
|
||||||
# STAGE 2: Install dependencies & build the project
|
|
||||||
# *****************************************************************************
|
|
||||||
# Add lockfile and package.json's of isolated subworkspace
|
|
||||||
FROM node:18-alpine AS installer
|
|
||||||
|
|
||||||
RUN apk add --no-cache libc6-compat
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# First install the dependencies (as they change less often)
|
|
||||||
COPY .gitignore .gitignore
|
|
||||||
COPY --from=builder /app/out/json/ .
|
|
||||||
COPY --from=builder /app/out/yarn.lock ./yarn.lock
|
|
||||||
RUN yarn install
|
|
||||||
|
|
||||||
# # Build the project
|
|
||||||
COPY --from=builder /app/out/full/ .
|
|
||||||
COPY turbo.json turbo.json
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
|
||||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
|
|
||||||
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
|
|
||||||
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
|
|
||||||
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
|
|
||||||
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_WEB_BASE_URL=""
|
|
||||||
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
|
|
||||||
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
|
||||||
ENV TURBO_TELEMETRY_DISABLED=1
|
|
||||||
|
|
||||||
RUN yarn turbo run build --filter=web --filter=space --filter=admin
|
|
||||||
|
|
||||||
# *****************************************************************************
|
|
||||||
# STAGE 3: Copy the project and start it
|
|
||||||
# *****************************************************************************
|
|
||||||
FROM makeplane/plane-aio-base:${BUILD_TYPE}-${BASE_TAG} AS runner
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
SHELL [ "/bin/bash", "-c" ]
|
|
||||||
|
|
||||||
# PYTHON APPLICATION SETUP
|
|
||||||
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1
|
|
||||||
ENV PYTHONUNBUFFERED=1
|
|
||||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
|
||||||
|
|
||||||
COPY apiserver/requirements.txt ./api/
|
|
||||||
COPY apiserver/requirements ./api/requirements
|
|
||||||
|
|
||||||
RUN pip install -r ./api/requirements.txt --compile --no-cache-dir
|
|
||||||
|
|
||||||
# Add in Django deps and generate Django's static files
|
|
||||||
COPY apiserver/manage.py ./api/manage.py
|
|
||||||
COPY apiserver/plane ./api/plane/
|
|
||||||
COPY apiserver/templates ./api/templates/
|
|
||||||
COPY package.json ./api/package.json
|
|
||||||
|
|
||||||
COPY apiserver/bin ./api/bin/
|
|
||||||
|
|
||||||
RUN chmod +x ./api/bin/*
|
|
||||||
RUN chmod -R 777 ./api/
|
|
||||||
|
|
||||||
# NEXTJS BUILDS
|
|
||||||
COPY --from=installer /app/web/next.config.js ./web/
|
|
||||||
COPY --from=installer /app/web/package.json ./web/
|
|
||||||
COPY --from=installer /app/web/.next/standalone ./web
|
|
||||||
COPY --from=installer /app/web/.next/static ./web/web/.next/static
|
|
||||||
COPY --from=installer /app/web/public ./web/web/public
|
|
||||||
|
|
||||||
COPY --from=installer /app/space/next.config.js ./space/
|
|
||||||
COPY --from=installer /app/space/package.json ./space/
|
|
||||||
COPY --from=installer /app/space/.next/standalone ./space
|
|
||||||
COPY --from=installer /app/space/.next/static ./space/space/.next/static
|
|
||||||
COPY --from=installer /app/space/public ./space/space/public
|
|
||||||
|
|
||||||
COPY --from=installer /app/admin/next.config.js ./admin/
|
|
||||||
COPY --from=installer /app/admin/package.json ./admin/
|
|
||||||
COPY --from=installer /app/admin/.next/standalone ./admin
|
|
||||||
COPY --from=installer /app/admin/.next/static ./admin/admin/.next/static
|
|
||||||
COPY --from=installer /app/admin/public ./admin/admin/public
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
|
||||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
|
|
||||||
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
|
|
||||||
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
|
|
||||||
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
|
|
||||||
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_WEB_BASE_URL=""
|
|
||||||
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
|
|
||||||
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
|
||||||
ENV TURBO_TELEMETRY_DISABLED=1
|
|
||||||
|
|
||||||
ARG BUILD_TYPE=full
|
|
||||||
ENV BUILD_TYPE=$BUILD_TYPE
|
|
||||||
|
|
||||||
COPY aio/supervisord-${BUILD_TYPE}-base /app/supervisord.conf
|
|
||||||
COPY aio/supervisord-app /app/supervisord-app
|
|
||||||
RUN cat /app/supervisord-app >> /app/supervisord.conf && \
|
|
||||||
rm /app/supervisord-app
|
|
||||||
|
|
||||||
COPY ./aio/nginx.conf /etc/nginx/nginx.conf.template
|
|
||||||
|
|
||||||
# if build type is full, run the below copy pg-setup.sh
|
|
||||||
COPY aio/postgresql.conf /etc/postgresql/postgresql.conf
|
|
||||||
COPY aio/pg-setup.sh /app/pg-setup.sh
|
|
||||||
RUN chmod +x /app/pg-setup.sh
|
|
||||||
|
|
||||||
# *****************************************************************************
|
|
||||||
# APPLICATION ENVIRONMENT SETTINGS
|
|
||||||
# *****************************************************************************
|
|
||||||
ENV APP_DOMAIN=localhost
|
|
||||||
ENV WEB_URL=http://${APP_DOMAIN}
|
|
||||||
ENV DEBUG=0
|
|
||||||
ENV CORS_ALLOWED_ORIGINS=http://${APP_DOMAIN},https://${APP_DOMAIN}
|
|
||||||
# Secret Key
|
|
||||||
ENV SECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5
|
|
||||||
# Gunicorn Workers
|
|
||||||
ENV GUNICORN_WORKERS=1
|
|
||||||
|
|
||||||
ENV POSTGRES_USER="plane"
|
|
||||||
ENV POSTGRES_PASSWORD="plane"
|
|
||||||
ENV POSTGRES_DB="plane"
|
|
||||||
ENV POSTGRES_HOST="localhost"
|
|
||||||
ENV POSTGRES_PORT="5432"
|
|
||||||
ENV DATABASE_URL="postgresql://plane:plane@localhost:5432/plane"
|
|
||||||
|
|
||||||
ENV REDIS_HOST="localhost"
|
|
||||||
ENV REDIS_PORT="6379"
|
|
||||||
ENV REDIS_URL="redis://localhost:6379"
|
|
||||||
|
|
||||||
ENV USE_MINIO="1"
|
|
||||||
ENV AWS_REGION=""
|
|
||||||
ENV AWS_ACCESS_KEY_ID="access-key"
|
|
||||||
ENV AWS_SECRET_ACCESS_KEY="secret-key"
|
|
||||||
ENV AWS_S3_ENDPOINT_URL="http://localhost:9000"
|
|
||||||
ENV AWS_S3_BUCKET_NAME="uploads"
|
|
||||||
ENV MINIO_ROOT_USER="access-key"
|
|
||||||
ENV MINIO_ROOT_PASSWORD="secret-key"
|
|
||||||
ENV BUCKET_NAME="uploads"
|
|
||||||
ENV FILE_SIZE_LIMIT="5242880"
|
|
||||||
|
|
||||||
# *****************************************************************************
|
|
||||||
|
|
||||||
RUN /app/pg-setup.sh
|
|
||||||
|
|
||||||
CMD ["/usr/bin/supervisord", "-c", "/app/supervisord.conf"]
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/binfmt AS binfmt
|
|
||||||
|
|
||||||
FROM python:3.12-slim
|
|
||||||
|
|
||||||
# Set environment variables to non-interactive for apt
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
ENV BUILD_TYPE=full
|
|
||||||
|
|
||||||
SHELL [ "/bin/bash", "-c" ]
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN mkdir -p /app/{data,logs} && \
|
|
||||||
mkdir -p /app/data/{redis,pg,minio,nginx} && \
|
|
||||||
mkdir -p /app/logs/{access,error} && \
|
|
||||||
mkdir -p /etc/supervisor/conf.d
|
|
||||||
|
|
||||||
# Update the package list and install prerequisites
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y \
|
|
||||||
gnupg2 curl ca-certificates lsb-release software-properties-common \
|
|
||||||
build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \
|
|
||||||
libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev xz-utils \
|
|
||||||
tk-dev libffi-dev liblzma-dev supervisor nginx nano vim ncdu \
|
|
||||||
sudo lsof net-tools libpq-dev procps gettext
|
|
||||||
|
|
||||||
# Install Redis 7.2
|
|
||||||
RUN echo "deb http://deb.debian.org/debian $(lsb_release -cs)-backports main" > /etc/apt/sources.list.d/backports.list && \
|
|
||||||
curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg && \
|
|
||||||
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" > /etc/apt/sources.list.d/redis.list && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -y redis-server
|
|
||||||
|
|
||||||
# Install PostgreSQL 15
|
|
||||||
ENV POSTGRES_VERSION=15
|
|
||||||
RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/pgdg-archive-keyring.gpg && \
|
|
||||||
echo "deb [signed-by=/usr/share/keyrings/pgdg-archive-keyring.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -y postgresql-$POSTGRES_VERSION postgresql-client-$POSTGRES_VERSION && \
|
|
||||||
mkdir -p /var/lib/postgresql/data && \
|
|
||||||
chown -R postgres:postgres /var/lib/postgresql
|
|
||||||
COPY postgresql.conf /etc/postgresql/postgresql.conf
|
|
||||||
RUN sudo -u postgres /usr/lib/postgresql/$POSTGRES_VERSION/bin/initdb -D /var/lib/postgresql/data
|
|
||||||
|
|
||||||
# Install MinIO
|
|
||||||
ARG TARGETARCH
|
|
||||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
|
||||||
curl -fSl https://dl.min.io/server/minio/release/linux-amd64/minio -o /usr/local/bin/minio; \
|
|
||||||
elif [ "$TARGETARCH" = "arm64" ]; then \
|
|
||||||
curl -fSl https://dl.min.io/server/minio/release/linux-arm64/minio -o /usr/local/bin/minio; \
|
|
||||||
else \
|
|
||||||
echo "Unsupported architecture: $TARGETARCH"; exit 1; \
|
|
||||||
fi && \
|
|
||||||
chmod +x /usr/local/bin/minio
|
|
||||||
|
|
||||||
# Install Node.js 18
|
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
|
|
||||||
apt-get install -y nodejs && \
|
|
||||||
python -m pip install --upgrade pip && \
|
|
||||||
apt-get clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Create Supervisor configuration file
|
|
||||||
COPY supervisord-full-base /app/supervisord.conf
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf.template
|
|
||||||
COPY env.sh /app/nginx-start.sh
|
|
||||||
RUN chmod +x /app/nginx-start.sh
|
|
||||||
|
|
||||||
# Expose ports for Redis, PostgreSQL, and MinIO
|
|
||||||
EXPOSE 6379 5432 9000 80 443
|
|
||||||
|
|
||||||
# Start Supervisor
|
|
||||||
CMD ["/usr/bin/supervisord", "-c", "/app/supervisord.conf"]
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/binfmt AS binfmt
|
|
||||||
|
|
||||||
FROM python:3.12-slim
|
|
||||||
|
|
||||||
# Set environment variables to non-interactive for apt
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
ENV BUILD_TYPE=slim
|
|
||||||
|
|
||||||
SHELL [ "/bin/bash", "-c" ]
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN mkdir -p /app/{data,logs} && \
|
|
||||||
mkdir -p /app/data/{nginx} && \
|
|
||||||
mkdir -p /app/logs/{access,error} && \
|
|
||||||
mkdir -p /etc/supervisor/conf.d
|
|
||||||
|
|
||||||
# Update the package list and install prerequisites
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y \
|
|
||||||
gnupg2 curl ca-certificates lsb-release software-properties-common \
|
|
||||||
build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \
|
|
||||||
libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev xz-utils \
|
|
||||||
tk-dev libffi-dev liblzma-dev supervisor nginx nano vim ncdu \
|
|
||||||
sudo lsof net-tools libpq-dev procps gettext
|
|
||||||
|
|
||||||
# Install Node.js 18
|
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
|
|
||||||
apt-get install -y nodejs
|
|
||||||
|
|
||||||
RUN python -m pip install --upgrade pip && \
|
|
||||||
apt-get clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Create Supervisor configuration file
|
|
||||||
COPY supervisord-slim-base /app/supervisord.conf
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf.template
|
|
||||||
COPY env.sh /app/nginx-start.sh
|
|
||||||
RUN chmod +x /app/nginx-start.sh
|
|
||||||
|
|
||||||
# Expose ports for Redis, PostgreSQL, and MinIO
|
|
||||||
EXPOSE 80 443
|
|
||||||
|
|
||||||
# Start Supervisor
|
|
||||||
CMD ["/usr/bin/supervisord", "-c", "/app/supervisord.conf"]
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
export dollar="$"
|
|
||||||
export http_upgrade="http_upgrade"
|
|
||||||
export scheme="scheme"
|
|
||||||
envsubst < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
|
|
||||||
exec nginx -g 'daemon off;'
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
events {
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
sendfile on;
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
root /www/data/;
|
|
||||||
access_log /var/log/nginx/access.log;
|
|
||||||
|
|
||||||
client_max_body_size ${FILE_SIZE_LIMIT};
|
|
||||||
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
|
||||||
add_header Permissions-Policy "interest-cohort=()" always;
|
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
||||||
add_header X-Forwarded-Proto "${dollar}scheme";
|
|
||||||
add_header X-Forwarded-Host "${dollar}host";
|
|
||||||
add_header X-Forwarded-For "${dollar}proxy_add_x_forwarded_for";
|
|
||||||
add_header X-Real-IP "${dollar}remote_addr";
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade ${dollar}http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host ${dollar}http_host;
|
|
||||||
proxy_pass http://localhost:3001/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /spaces/ {
|
|
||||||
rewrite ^/spaces/?$ /spaces/login break;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade ${dollar}http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host ${dollar}http_host;
|
|
||||||
proxy_pass http://localhost:3002/spaces/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /god-mode/ {
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade ${dollar}http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host ${dollar}http_host;
|
|
||||||
proxy_pass http://localhost:3003/god-mode/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /api/ {
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade ${dollar}http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host ${dollar}http_host;
|
|
||||||
proxy_pass http://localhost:8000/api/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /auth/ {
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade ${dollar}http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host ${dollar}http_host;
|
|
||||||
proxy_pass http://localhost:8000/auth/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /${BUCKET_NAME}/ {
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade ${dollar}http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host ${dollar}http_host;
|
|
||||||
proxy_pass http://localhost:9000/uploads/;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [ "$BUILD_TYPE" == "full" ]; then
|
|
||||||
|
|
||||||
export PGHOST=localhost
|
|
||||||
|
|
||||||
sudo -u postgres "/usr/lib/postgresql/${POSTGRES_VERSION}/bin/pg_ctl" -D /var/lib/postgresql/data start
|
|
||||||
sudo -u postgres "/usr/lib/postgresql/${POSTGRES_VERSION}/bin/psql" --command "CREATE USER $POSTGRES_USER WITH SUPERUSER PASSWORD '$POSTGRES_PASSWORD';" && \
|
|
||||||
sudo -u postgres "/usr/lib/postgresql/${POSTGRES_VERSION}/bin/createdb" -O "$POSTGRES_USER" "$POSTGRES_DB" && \
|
|
||||||
sudo -u postgres "/usr/lib/postgresql/${POSTGRES_VERSION}/bin/psql" --command "GRANT ALL PRIVILEGES ON DATABASE $POSTGRES_DB TO $POSTGRES_USER;" && \
|
|
||||||
sudo -u postgres "/usr/lib/postgresql/${POSTGRES_VERSION}/bin/pg_ctl" -D /var/lib/postgresql/data stop
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
@ -1,815 +0,0 @@
|
||||||
# -----------------------------
|
|
||||||
# PostgreSQL configuration file
|
|
||||||
# -----------------------------
|
|
||||||
#
|
|
||||||
# This file consists of lines of the form:
|
|
||||||
#
|
|
||||||
# name = value
|
|
||||||
#
|
|
||||||
# (The "=" is optional.) Whitespace may be used. Comments are introduced with
|
|
||||||
# "#" anywhere on a line. The complete list of parameter names and allowed
|
|
||||||
# values can be found in the PostgreSQL documentation.
|
|
||||||
#
|
|
||||||
# The commented-out settings shown in this file represent the default values.
|
|
||||||
# Re-commenting a setting is NOT sufficient to revert it to the default value;
|
|
||||||
# you need to reload the server.
|
|
||||||
#
|
|
||||||
# This file is read on server startup and when the server receives a SIGHUP
|
|
||||||
# signal. If you edit the file on a running system, you have to SIGHUP the
|
|
||||||
# server for the changes to take effect, run "pg_ctl reload", or execute
|
|
||||||
# "SELECT pg_reload_conf()". Some parameters, which are marked below,
|
|
||||||
# require a server shutdown and restart to take effect.
|
|
||||||
#
|
|
||||||
# Any parameter can also be given as a command-line option to the server, e.g.,
|
|
||||||
# "postgres -c log_connections=on". Some parameters can be changed at run time
|
|
||||||
# with the "SET" SQL command.
|
|
||||||
#
|
|
||||||
# Memory units: B = bytes Time units: us = microseconds
|
|
||||||
# kB = kilobytes ms = milliseconds
|
|
||||||
# MB = megabytes s = seconds
|
|
||||||
# GB = gigabytes min = minutes
|
|
||||||
# TB = terabytes h = hours
|
|
||||||
# d = days
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# FILE LOCATIONS
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# The default values of these variables are driven from the -D command-line
|
|
||||||
# option or PGDATA environment variable, represented here as ConfigDir.
|
|
||||||
|
|
||||||
data_directory = '/var/lib/postgresql/data' # use data in another directory
|
|
||||||
# (change requires restart)
|
|
||||||
hba_file = '/etc/postgresql/15/main/pg_hba.conf' # host-based authentication file
|
|
||||||
# (change requires restart)
|
|
||||||
ident_file = '/etc/postgresql/15/main/pg_ident.conf' # ident configuration file
|
|
||||||
# (change requires restart)
|
|
||||||
|
|
||||||
# If external_pid_file is not explicitly set, no extra PID file is written.
|
|
||||||
external_pid_file = '/var/run/postgresql/15-main.pid' # write an extra PID file
|
|
||||||
# (change requires restart)
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# CONNECTIONS AND AUTHENTICATION
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# - Connection Settings -
|
|
||||||
|
|
||||||
listen_addresses = 'localhost' # what IP address(es) to listen on;
|
|
||||||
# comma-separated list of addresses;
|
|
||||||
# defaults to 'localhost'; use '*' for all
|
|
||||||
# (change requires restart)
|
|
||||||
port = 5432 # (change requires restart)
|
|
||||||
max_connections = 200 # (change requires restart)
|
|
||||||
#superuser_reserved_connections = 3 # (change requires restart)
|
|
||||||
unix_socket_directories = '/var/run/postgresql' # comma-separated list of directories
|
|
||||||
# (change requires restart)
|
|
||||||
#unix_socket_group = '' # (change requires restart)
|
|
||||||
#unix_socket_permissions = 0777 # begin with 0 to use octal notation
|
|
||||||
# (change requires restart)
|
|
||||||
#bonjour = off # advertise server via Bonjour
|
|
||||||
# (change requires restart)
|
|
||||||
#bonjour_name = '' # defaults to the computer name
|
|
||||||
# (change requires restart)
|
|
||||||
|
|
||||||
# - TCP settings -
|
|
||||||
# see "man tcp" for details
|
|
||||||
|
|
||||||
#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds;
|
|
||||||
# 0 selects the system default
|
|
||||||
#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds;
|
|
||||||
# 0 selects the system default
|
|
||||||
#tcp_keepalives_count = 0 # TCP_KEEPCNT;
|
|
||||||
# 0 selects the system default
|
|
||||||
#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds;
|
|
||||||
# 0 selects the system default
|
|
||||||
|
|
||||||
#client_connection_check_interval = 0 # time between checks for client
|
|
||||||
# disconnection while running queries;
|
|
||||||
# 0 for never
|
|
||||||
|
|
||||||
# - Authentication -
|
|
||||||
|
|
||||||
#authentication_timeout = 1min # 1s-600s
|
|
||||||
#password_encryption = scram-sha-256 # scram-sha-256 or md5
|
|
||||||
#db_user_namespace = off
|
|
||||||
|
|
||||||
# GSSAPI using Kerberos
|
|
||||||
#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab'
|
|
||||||
#krb_caseins_users = off
|
|
||||||
|
|
||||||
# - SSL -
|
|
||||||
|
|
||||||
ssl = on
|
|
||||||
#ssl_ca_file = ''
|
|
||||||
ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
|
|
||||||
#ssl_crl_file = ''
|
|
||||||
#ssl_crl_dir = ''
|
|
||||||
ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'
|
|
||||||
#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
|
|
||||||
#ssl_prefer_server_ciphers = on
|
|
||||||
#ssl_ecdh_curve = 'prime256v1'
|
|
||||||
#ssl_min_protocol_version = 'TLSv1.2'
|
|
||||||
#ssl_max_protocol_version = ''
|
|
||||||
#ssl_dh_params_file = ''
|
|
||||||
#ssl_passphrase_command = ''
|
|
||||||
#ssl_passphrase_command_supports_reload = off
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# RESOURCE USAGE (except WAL)
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# - Memory -
|
|
||||||
|
|
||||||
shared_buffers = 256MB # min 128kB
|
|
||||||
# (change requires restart)
|
|
||||||
#huge_pages = try # on, off, or try
|
|
||||||
# (change requires restart)
|
|
||||||
#huge_page_size = 0 # zero for system default
|
|
||||||
# (change requires restart)
|
|
||||||
#temp_buffers = 8MB # min 800kB
|
|
||||||
#max_prepared_transactions = 0 # zero disables the feature
|
|
||||||
# (change requires restart)
|
|
||||||
# Caution: it is not advisable to set max_prepared_transactions nonzero unless
|
|
||||||
# you actively intend to use prepared transactions.
|
|
||||||
#work_mem = 4MB # min 64kB
|
|
||||||
#hash_mem_multiplier = 2.0 # 1-1000.0 multiplier on hash table work_mem
|
|
||||||
#maintenance_work_mem = 64MB # min 1MB
|
|
||||||
#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem
|
|
||||||
#logical_decoding_work_mem = 64MB # min 64kB
|
|
||||||
#max_stack_depth = 2MB # min 100kB
|
|
||||||
#shared_memory_type = mmap # the default is the first option
|
|
||||||
# supported by the operating system:
|
|
||||||
# mmap
|
|
||||||
# sysv
|
|
||||||
# windows
|
|
||||||
# (change requires restart)
|
|
||||||
dynamic_shared_memory_type = posix # the default is usually the first option
|
|
||||||
# supported by the operating system:
|
|
||||||
# posix
|
|
||||||
# sysv
|
|
||||||
# windows
|
|
||||||
# mmap
|
|
||||||
# (change requires restart)
|
|
||||||
#min_dynamic_shared_memory = 0MB # (change requires restart)
|
|
||||||
|
|
||||||
# - Disk -
|
|
||||||
|
|
||||||
#temp_file_limit = -1 # limits per-process temp file space
|
|
||||||
# in kilobytes, or -1 for no limit
|
|
||||||
|
|
||||||
# - Kernel Resources -
|
|
||||||
|
|
||||||
#max_files_per_process = 1000 # min 64
|
|
||||||
# (change requires restart)
|
|
||||||
|
|
||||||
# - Cost-Based Vacuum Delay -
|
|
||||||
|
|
||||||
#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables)
|
|
||||||
#vacuum_cost_page_hit = 1 # 0-10000 credits
|
|
||||||
#vacuum_cost_page_miss = 2 # 0-10000 credits
|
|
||||||
#vacuum_cost_page_dirty = 20 # 0-10000 credits
|
|
||||||
#vacuum_cost_limit = 200 # 1-10000 credits
|
|
||||||
|
|
||||||
# - Background Writer -
|
|
||||||
|
|
||||||
#bgwriter_delay = 200ms # 10-10000ms between rounds
|
|
||||||
#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables
|
|
||||||
#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round
|
|
||||||
#bgwriter_flush_after = 512kB # measured in pages, 0 disables
|
|
||||||
|
|
||||||
# - Asynchronous Behavior -
|
|
||||||
|
|
||||||
#backend_flush_after = 0 # measured in pages, 0 disables
|
|
||||||
#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching
|
|
||||||
#maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching
|
|
||||||
#max_worker_processes = 8 # (change requires restart)
|
|
||||||
#max_parallel_workers_per_gather = 2 # limited by max_parallel_workers
|
|
||||||
#max_parallel_maintenance_workers = 2 # limited by max_parallel_workers
|
|
||||||
#max_parallel_workers = 8 # number of max_worker_processes that
|
|
||||||
# can be used in parallel operations
|
|
||||||
#parallel_leader_participation = on
|
|
||||||
#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate
|
|
||||||
# (change requires restart)
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# WRITE-AHEAD LOG
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# - Settings -
|
|
||||||
|
|
||||||
#wal_level = replica # minimal, replica, or logical
|
|
||||||
# (change requires restart)
|
|
||||||
#fsync = on # flush data to disk for crash safety
|
|
||||||
# (turning this off can cause
|
|
||||||
# unrecoverable data corruption)
|
|
||||||
#synchronous_commit = on # synchronization level;
|
|
||||||
# off, local, remote_write, remote_apply, or on
|
|
||||||
#wal_sync_method = fsync # the default is the first option
|
|
||||||
# supported by the operating system:
|
|
||||||
# open_datasync
|
|
||||||
# fdatasync (default on Linux and FreeBSD)
|
|
||||||
# fsync
|
|
||||||
# fsync_writethrough
|
|
||||||
# open_sync
|
|
||||||
#full_page_writes = on # recover from partial page writes
|
|
||||||
#wal_log_hints = off # also do full page writes of non-critical updates
|
|
||||||
# (change requires restart)
|
|
||||||
#wal_compression = off # enables compression of full-page writes;
|
|
||||||
# off, pglz, lz4, zstd, or on
|
|
||||||
#wal_init_zero = on # zero-fill new WAL files
|
|
||||||
#wal_recycle = on # recycle WAL files
|
|
||||||
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
|
|
||||||
# (change requires restart)
|
|
||||||
#wal_writer_delay = 200ms # 1-10000 milliseconds
|
|
||||||
#wal_writer_flush_after = 1MB # measured in pages, 0 disables
|
|
||||||
#wal_skip_threshold = 2MB
|
|
||||||
|
|
||||||
#commit_delay = 0 # range 0-100000, in microseconds
|
|
||||||
#commit_siblings = 5 # range 1-1000
|
|
||||||
|
|
||||||
# - Checkpoints -
|
|
||||||
|
|
||||||
#checkpoint_timeout = 5min # range 30s-1d
|
|
||||||
#checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0
|
|
||||||
#checkpoint_flush_after = 256kB # measured in pages, 0 disables
|
|
||||||
#checkpoint_warning = 30s # 0 disables
|
|
||||||
max_wal_size = 1GB
|
|
||||||
min_wal_size = 80MB
|
|
||||||
|
|
||||||
# - Prefetching during recovery -
|
|
||||||
|
|
||||||
#recovery_prefetch = try # prefetch pages referenced in the WAL?
|
|
||||||
#wal_decode_buffer_size = 512kB # lookahead window used for prefetching
|
|
||||||
# (change requires restart)
|
|
||||||
|
|
||||||
# - Archiving -
|
|
||||||
|
|
||||||
#archive_mode = off # enables archiving; off, on, or always
|
|
||||||
# (change requires restart)
|
|
||||||
#archive_library = '' # library to use to archive a logfile segment
|
|
||||||
# (empty string indicates archive_command should
|
|
||||||
# be used)
|
|
||||||
#archive_command = '' # command to use to archive a logfile segment
|
|
||||||
# placeholders: %p = path of file to archive
|
|
||||||
# %f = file name only
|
|
||||||
# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
|
|
||||||
#archive_timeout = 0 # force a logfile segment switch after this
|
|
||||||
# number of seconds; 0 disables
|
|
||||||
|
|
||||||
# - Archive Recovery -
|
|
||||||
|
|
||||||
# These are only used in recovery mode.
|
|
||||||
|
|
||||||
#restore_command = '' # command to use to restore an archived logfile segment
|
|
||||||
# placeholders: %p = path of file to restore
|
|
||||||
# %f = file name only
|
|
||||||
# e.g. 'cp /mnt/server/archivedir/%f %p'
|
|
||||||
#archive_cleanup_command = '' # command to execute at every restartpoint
|
|
||||||
#recovery_end_command = '' # command to execute at completion of recovery
|
|
||||||
|
|
||||||
# - Recovery Target -
|
|
||||||
|
|
||||||
# Set these only when performing a targeted recovery.
|
|
||||||
|
|
||||||
#recovery_target = '' # 'immediate' to end recovery as soon as a
|
|
||||||
# consistent state is reached
|
|
||||||
# (change requires restart)
|
|
||||||
#recovery_target_name = '' # the named restore point to which recovery will proceed
|
|
||||||
# (change requires restart)
|
|
||||||
#recovery_target_time = '' # the time stamp up to which recovery will proceed
|
|
||||||
# (change requires restart)
|
|
||||||
#recovery_target_xid = '' # the transaction ID up to which recovery will proceed
|
|
||||||
# (change requires restart)
|
|
||||||
#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed
|
|
||||||
# (change requires restart)
|
|
||||||
#recovery_target_inclusive = on # Specifies whether to stop:
|
|
||||||
# just after the specified recovery target (on)
|
|
||||||
# just before the recovery target (off)
|
|
||||||
# (change requires restart)
|
|
||||||
#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID
|
|
||||||
# (change requires restart)
|
|
||||||
#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown'
|
|
||||||
# (change requires restart)
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# REPLICATION
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# - Sending Servers -
|
|
||||||
|
|
||||||
# Set these on the primary and on any standby that will send replication data.
|
|
||||||
|
|
||||||
#max_wal_senders = 10 # max number of walsender processes
|
|
||||||
# (change requires restart)
|
|
||||||
#max_replication_slots = 10 # max number of replication slots
|
|
||||||
# (change requires restart)
|
|
||||||
#wal_keep_size = 0 # in megabytes; 0 disables
|
|
||||||
#max_slot_wal_keep_size = -1 # in megabytes; -1 disables
|
|
||||||
#wal_sender_timeout = 60s # in milliseconds; 0 disables
|
|
||||||
#track_commit_timestamp = off # collect timestamp of transaction commit
|
|
||||||
# (change requires restart)
|
|
||||||
|
|
||||||
# - Primary Server -
|
|
||||||
|
|
||||||
# These settings are ignored on a standby server.
|
|
||||||
|
|
||||||
#synchronous_standby_names = '' # standby servers that provide sync rep
|
|
||||||
# method to choose sync standbys, number of sync standbys,
|
|
||||||
# and comma-separated list of application_name
|
|
||||||
# from standby(s); '*' = all
|
|
||||||
#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed
|
|
||||||
|
|
||||||
# - Standby Servers -
|
|
||||||
|
|
||||||
# These settings are ignored on a primary server.
|
|
||||||
|
|
||||||
#primary_conninfo = '' # connection string to sending server
|
|
||||||
#primary_slot_name = '' # replication slot on sending server
|
|
||||||
#promote_trigger_file = '' # file name whose presence ends recovery
|
|
||||||
#hot_standby = on # "off" disallows queries during recovery
|
|
||||||
# (change requires restart)
|
|
||||||
#max_standby_archive_delay = 30s # max delay before canceling queries
|
|
||||||
# when reading WAL from archive;
|
|
||||||
# -1 allows indefinite delay
|
|
||||||
#max_standby_streaming_delay = 30s # max delay before canceling queries
|
|
||||||
# when reading streaming WAL;
|
|
||||||
# -1 allows indefinite delay
|
|
||||||
#wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name
|
|
||||||
# is not set
|
|
||||||
#wal_receiver_status_interval = 10s # send replies at least this often
|
|
||||||
# 0 disables
|
|
||||||
#hot_standby_feedback = off # send info from standby to prevent
|
|
||||||
# query conflicts
|
|
||||||
#wal_receiver_timeout = 60s # time that receiver waits for
|
|
||||||
# communication from primary
|
|
||||||
# in milliseconds; 0 disables
|
|
||||||
#wal_retrieve_retry_interval = 5s # time to wait before retrying to
|
|
||||||
# retrieve WAL after a failed attempt
|
|
||||||
#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery
|
|
||||||
|
|
||||||
# - Subscribers -
|
|
||||||
|
|
||||||
# These settings are ignored on a publisher.
|
|
||||||
|
|
||||||
#max_logical_replication_workers = 4 # taken from max_worker_processes
|
|
||||||
# (change requires restart)
|
|
||||||
#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# QUERY TUNING
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# - Planner Method Configuration -
|
|
||||||
|
|
||||||
#enable_async_append = on
|
|
||||||
#enable_bitmapscan = on
|
|
||||||
#enable_gathermerge = on
|
|
||||||
#enable_hashagg = on
|
|
||||||
#enable_hashjoin = on
|
|
||||||
#enable_incremental_sort = on
|
|
||||||
#enable_indexscan = on
|
|
||||||
#enable_indexonlyscan = on
|
|
||||||
#enable_material = on
|
|
||||||
#enable_memoize = on
|
|
||||||
#enable_mergejoin = on
|
|
||||||
#enable_nestloop = on
|
|
||||||
#enable_parallel_append = on
|
|
||||||
#enable_parallel_hash = on
|
|
||||||
#enable_partition_pruning = on
|
|
||||||
#enable_partitionwise_join = off
|
|
||||||
#enable_partitionwise_aggregate = off
|
|
||||||
#enable_seqscan = on
|
|
||||||
#enable_sort = on
|
|
||||||
#enable_tidscan = on
|
|
||||||
|
|
||||||
# - Planner Cost Constants -
|
|
||||||
|
|
||||||
#seq_page_cost = 1.0 # measured on an arbitrary scale
|
|
||||||
#random_page_cost = 4.0 # same scale as above
|
|
||||||
#cpu_tuple_cost = 0.01 # same scale as above
|
|
||||||
#cpu_index_tuple_cost = 0.005 # same scale as above
|
|
||||||
#cpu_operator_cost = 0.0025 # same scale as above
|
|
||||||
#parallel_setup_cost = 1000.0 # same scale as above
|
|
||||||
#parallel_tuple_cost = 0.1 # same scale as above
|
|
||||||
#min_parallel_table_scan_size = 8MB
|
|
||||||
#min_parallel_index_scan_size = 512kB
|
|
||||||
#effective_cache_size = 4GB
|
|
||||||
|
|
||||||
#jit_above_cost = 100000 # perform JIT compilation if available
|
|
||||||
# and query more expensive than this;
|
|
||||||
# -1 disables
|
|
||||||
#jit_inline_above_cost = 500000 # inline small functions if query is
|
|
||||||
# more expensive than this; -1 disables
|
|
||||||
#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if
|
|
||||||
# query is more expensive than this;
|
|
||||||
# -1 disables
|
|
||||||
|
|
||||||
# - Genetic Query Optimizer -
|
|
||||||
|
|
||||||
#geqo = on
|
|
||||||
#geqo_threshold = 12
|
|
||||||
#geqo_effort = 5 # range 1-10
|
|
||||||
#geqo_pool_size = 0 # selects default based on effort
|
|
||||||
#geqo_generations = 0 # selects default based on effort
|
|
||||||
#geqo_selection_bias = 2.0 # range 1.5-2.0
|
|
||||||
#geqo_seed = 0.0 # range 0.0-1.0
|
|
||||||
|
|
||||||
# - Other Planner Options -
|
|
||||||
|
|
||||||
#default_statistics_target = 100 # range 1-10000
|
|
||||||
#constraint_exclusion = partition # on, off, or partition
|
|
||||||
#cursor_tuple_fraction = 0.1 # range 0.0-1.0
|
|
||||||
#from_collapse_limit = 8
|
|
||||||
#jit = on # allow JIT compilation
|
|
||||||
#join_collapse_limit = 8 # 1 disables collapsing of explicit
|
|
||||||
# JOIN clauses
|
|
||||||
#plan_cache_mode = auto # auto, force_generic_plan or
|
|
||||||
# force_custom_plan
|
|
||||||
#recursive_worktable_factor = 10.0 # range 0.001-1000000
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# REPORTING AND LOGGING
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# - Where to Log -
|
|
||||||
|
|
||||||
#log_destination = 'stderr' # Valid values are combinations of
|
|
||||||
# stderr, csvlog, jsonlog, syslog, and
|
|
||||||
# eventlog, depending on platform.
|
|
||||||
# csvlog and jsonlog require
|
|
||||||
# logging_collector to be on.
|
|
||||||
|
|
||||||
# This is used when logging to stderr:
|
|
||||||
#logging_collector = off # Enable capturing of stderr, jsonlog,
|
|
||||||
# and csvlog into log files. Required
|
|
||||||
# to be on for csvlogs and jsonlogs.
|
|
||||||
# (change requires restart)
|
|
||||||
|
|
||||||
# These are only used if logging_collector is on:
|
|
||||||
#log_directory = 'log' # directory where log files are written,
|
|
||||||
# can be absolute or relative to PGDATA
|
|
||||||
#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern,
|
|
||||||
# can include strftime() escapes
|
|
||||||
#log_file_mode = 0600 # creation mode for log files,
|
|
||||||
# begin with 0 to use octal notation
|
|
||||||
#log_rotation_age = 1d # Automatic rotation of logfiles will
|
|
||||||
# happen after that time. 0 disables.
|
|
||||||
#log_rotation_size = 10MB # Automatic rotation of logfiles will
|
|
||||||
# happen after that much log output.
|
|
||||||
# 0 disables.
|
|
||||||
#log_truncate_on_rotation = off # If on, an existing log file with the
|
|
||||||
# same name as the new log file will be
|
|
||||||
# truncated rather than appended to.
|
|
||||||
# But such truncation only occurs on
|
|
||||||
# time-driven rotation, not on restarts
|
|
||||||
# or size-driven rotation. Default is
|
|
||||||
# off, meaning append to existing files
|
|
||||||
# in all cases.
|
|
||||||
|
|
||||||
# These are relevant when logging to syslog:
|
|
||||||
#syslog_facility = 'LOCAL0'
|
|
||||||
#syslog_ident = 'postgres'
|
|
||||||
#syslog_sequence_numbers = on
|
|
||||||
#syslog_split_messages = on
|
|
||||||
|
|
||||||
# This is only relevant when logging to eventlog (Windows):
|
|
||||||
# (change requires restart)
|
|
||||||
#event_source = 'PostgreSQL'
|
|
||||||
|
|
||||||
# - When to Log -
|
|
||||||
|
|
||||||
#log_min_messages = warning # values in order of decreasing detail:
|
|
||||||
# debug5
|
|
||||||
# debug4
|
|
||||||
# debug3
|
|
||||||
# debug2
|
|
||||||
# debug1
|
|
||||||
# info
|
|
||||||
# notice
|
|
||||||
# warning
|
|
||||||
# error
|
|
||||||
# log
|
|
||||||
# fatal
|
|
||||||
# panic
|
|
||||||
|
|
||||||
#log_min_error_statement = error # values in order of decreasing detail:
|
|
||||||
# debug5
|
|
||||||
# debug4
|
|
||||||
# debug3
|
|
||||||
# debug2
|
|
||||||
# debug1
|
|
||||||
# info
|
|
||||||
# notice
|
|
||||||
# warning
|
|
||||||
# error
|
|
||||||
# log
|
|
||||||
# fatal
|
|
||||||
# panic (effectively off)
|
|
||||||
|
|
||||||
#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements
|
|
||||||
# and their durations, > 0 logs only
|
|
||||||
# statements running at least this number
|
|
||||||
# of milliseconds
|
|
||||||
|
|
||||||
#log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements
|
|
||||||
# and their durations, > 0 logs only a sample of
|
|
||||||
# statements running at least this number
|
|
||||||
# of milliseconds;
|
|
||||||
# sample fraction is determined by log_statement_sample_rate
|
|
||||||
|
|
||||||
#log_statement_sample_rate = 1.0 # fraction of logged statements exceeding
|
|
||||||
# log_min_duration_sample to be logged;
|
|
||||||
# 1.0 logs all such statements, 0.0 never logs
|
|
||||||
|
|
||||||
|
|
||||||
#log_transaction_sample_rate = 0.0 # fraction of transactions whose statements
|
|
||||||
# are logged regardless of their duration; 1.0 logs all
|
|
||||||
# statements from all transactions, 0.0 never logs
|
|
||||||
|
|
||||||
#log_startup_progress_interval = 10s # Time between progress updates for
|
|
||||||
# long-running startup operations.
|
|
||||||
# 0 disables the feature, > 0 indicates
|
|
||||||
# the interval in milliseconds.
|
|
||||||
|
|
||||||
# - What to Log -
|
|
||||||
|
|
||||||
#debug_print_parse = off
|
|
||||||
#debug_print_rewritten = off
|
|
||||||
#debug_print_plan = off
|
|
||||||
#debug_pretty_print = on
|
|
||||||
#log_autovacuum_min_duration = 10min # log autovacuum activity;
|
|
||||||
# -1 disables, 0 logs all actions and
|
|
||||||
# their durations, > 0 logs only
|
|
||||||
# actions running at least this number
|
|
||||||
# of milliseconds.
|
|
||||||
#log_checkpoints = on
|
|
||||||
#log_connections = off
|
|
||||||
#log_disconnections = off
|
|
||||||
#log_duration = off
|
|
||||||
#log_error_verbosity = default # terse, default, or verbose messages
|
|
||||||
#log_hostname = off
|
|
||||||
log_line_prefix = '%m [%p] %q%u@%d ' # special values:
|
|
||||||
# %a = application name
|
|
||||||
# %u = user name
|
|
||||||
# %d = database name
|
|
||||||
# %r = remote host and port
|
|
||||||
# %h = remote host
|
|
||||||
# %b = backend type
|
|
||||||
# %p = process ID
|
|
||||||
# %P = process ID of parallel group leader
|
|
||||||
# %t = timestamp without milliseconds
|
|
||||||
# %m = timestamp with milliseconds
|
|
||||||
# %n = timestamp with milliseconds (as a Unix epoch)
|
|
||||||
# %Q = query ID (0 if none or not computed)
|
|
||||||
# %i = command tag
|
|
||||||
# %e = SQL state
|
|
||||||
# %c = session ID
|
|
||||||
# %l = session line number
|
|
||||||
# %s = session start timestamp
|
|
||||||
# %v = virtual transaction ID
|
|
||||||
# %x = transaction ID (0 if none)
|
|
||||||
# %q = stop here in non-session
|
|
||||||
# processes
|
|
||||||
# %% = '%'
|
|
||||||
# e.g. '<%u%%%d> '
|
|
||||||
#log_lock_waits = off # log lock waits >= deadlock_timeout
|
|
||||||
#log_recovery_conflict_waits = off # log standby recovery conflict waits
|
|
||||||
# >= deadlock_timeout
|
|
||||||
#log_parameter_max_length = -1 # when logging statements, limit logged
|
|
||||||
# bind-parameter values to N bytes;
|
|
||||||
# -1 means print in full, 0 disables
|
|
||||||
#log_parameter_max_length_on_error = 0 # when logging an error, limit logged
|
|
||||||
# bind-parameter values to N bytes;
|
|
||||||
# -1 means print in full, 0 disables
|
|
||||||
#log_statement = 'none' # none, ddl, mod, all
|
|
||||||
#log_replication_commands = off
|
|
||||||
#log_temp_files = -1 # log temporary files equal or larger
|
|
||||||
# than the specified size in kilobytes;
|
|
||||||
# -1 disables, 0 logs all temp files
|
|
||||||
log_timezone = 'Etc/UTC'
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# PROCESS TITLE
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
cluster_name = '15/main' # added to process titles if nonempty
|
|
||||||
# (change requires restart)
|
|
||||||
#update_process_title = on
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# STATISTICS
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# - Cumulative Query and Index Statistics -
|
|
||||||
|
|
||||||
#track_activities = on
|
|
||||||
#track_activity_query_size = 1024 # (change requires restart)
|
|
||||||
#track_counts = on
|
|
||||||
#track_io_timing = off
|
|
||||||
#track_wal_io_timing = off
|
|
||||||
#track_functions = none # none, pl, all
|
|
||||||
#stats_fetch_consistency = cache
|
|
||||||
|
|
||||||
|
|
||||||
# - Monitoring -
|
|
||||||
|
|
||||||
#compute_query_id = auto
|
|
||||||
#log_statement_stats = off
|
|
||||||
#log_parser_stats = off
|
|
||||||
#log_planner_stats = off
|
|
||||||
#log_executor_stats = off
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# AUTOVACUUM
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#autovacuum = on # Enable autovacuum subprocess? 'on'
|
|
||||||
# requires track_counts to also be on.
|
|
||||||
#autovacuum_max_workers = 3 # max number of autovacuum subprocesses
|
|
||||||
# (change requires restart)
|
|
||||||
#autovacuum_naptime = 1min # time between autovacuum runs
|
|
||||||
#autovacuum_vacuum_threshold = 50 # min number of row updates before
|
|
||||||
# vacuum
|
|
||||||
#autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts
|
|
||||||
# before vacuum; -1 disables insert
|
|
||||||
# vacuums
|
|
||||||
#autovacuum_analyze_threshold = 50 # min number of row updates before
|
|
||||||
# analyze
|
|
||||||
#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum
|
|
||||||
#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table
|
|
||||||
# size before insert vacuum
|
|
||||||
#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze
|
|
||||||
#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
|
|
||||||
# (change requires restart)
|
|
||||||
#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age
|
|
||||||
# before forced vacuum
|
|
||||||
# (change requires restart)
|
|
||||||
#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for
|
|
||||||
# autovacuum, in milliseconds;
|
|
||||||
# -1 means use vacuum_cost_delay
|
|
||||||
#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for
|
|
||||||
# autovacuum, -1 means use
|
|
||||||
# vacuum_cost_limit
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# CLIENT CONNECTION DEFAULTS
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# - Statement Behavior -
|
|
||||||
|
|
||||||
#client_min_messages = notice # values in order of decreasing detail:
|
|
||||||
# debug5
|
|
||||||
# debug4
|
|
||||||
# debug3
|
|
||||||
# debug2
|
|
||||||
# debug1
|
|
||||||
# log
|
|
||||||
# notice
|
|
||||||
# warning
|
|
||||||
# error
|
|
||||||
#search_path = '"$user", public' # schema names
|
|
||||||
#row_security = on
|
|
||||||
#default_table_access_method = 'heap'
|
|
||||||
#default_tablespace = '' # a tablespace name, '' uses the default
|
|
||||||
#default_toast_compression = 'pglz' # 'pglz' or 'lz4'
|
|
||||||
#temp_tablespaces = '' # a list of tablespace names, '' uses
|
|
||||||
# only default tablespace
|
|
||||||
#check_function_bodies = on
|
|
||||||
#default_transaction_isolation = 'read committed'
|
|
||||||
#default_transaction_read_only = off
|
|
||||||
#default_transaction_deferrable = off
|
|
||||||
#session_replication_role = 'origin'
|
|
||||||
#statement_timeout = 0 # in milliseconds, 0 is disabled
|
|
||||||
#lock_timeout = 0 # in milliseconds, 0 is disabled
|
|
||||||
#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled
|
|
||||||
#idle_session_timeout = 0 # in milliseconds, 0 is disabled
|
|
||||||
#vacuum_freeze_table_age = 150000000
|
|
||||||
#vacuum_freeze_min_age = 50000000
|
|
||||||
#vacuum_failsafe_age = 1600000000
|
|
||||||
#vacuum_multixact_freeze_table_age = 150000000
|
|
||||||
#vacuum_multixact_freeze_min_age = 5000000
|
|
||||||
#vacuum_multixact_failsafe_age = 1600000000
|
|
||||||
#bytea_output = 'hex' # hex, escape
|
|
||||||
#xmlbinary = 'base64'
|
|
||||||
#xmloption = 'content'
|
|
||||||
#gin_pending_list_limit = 4MB
|
|
||||||
|
|
||||||
# - Locale and Formatting -
|
|
||||||
|
|
||||||
datestyle = 'iso, mdy'
|
|
||||||
#intervalstyle = 'postgres'
|
|
||||||
timezone = 'Etc/UTC'
|
|
||||||
#timezone_abbreviations = 'Default' # Select the set of available time zone
|
|
||||||
# abbreviations. Currently, there are
|
|
||||||
# Default
|
|
||||||
# Australia (historical usage)
|
|
||||||
# India
|
|
||||||
# You can create your own file in
|
|
||||||
# share/timezonesets/.
|
|
||||||
#extra_float_digits = 1 # min -15, max 3; any value >0 actually
|
|
||||||
# selects precise output mode
|
|
||||||
#client_encoding = sql_ascii # actually, defaults to database
|
|
||||||
# encoding
|
|
||||||
|
|
||||||
# These settings are initialized by initdb, but they can be changed.
|
|
||||||
lc_messages = 'C.UTF-8' # locale for system error message
|
|
||||||
# strings
|
|
||||||
lc_monetary = 'C.UTF-8' # locale for monetary formatting
|
|
||||||
lc_numeric = 'C.UTF-8' # locale for number formatting
|
|
||||||
lc_time = 'C.UTF-8' # locale for time formatting
|
|
||||||
|
|
||||||
# default configuration for text search
|
|
||||||
default_text_search_config = 'pg_catalog.english'
|
|
||||||
|
|
||||||
# - Shared Library Preloading -
|
|
||||||
|
|
||||||
#local_preload_libraries = ''
|
|
||||||
#session_preload_libraries = ''
|
|
||||||
#shared_preload_libraries = '' # (change requires restart)
|
|
||||||
#jit_provider = 'llvmjit' # JIT library to use
|
|
||||||
|
|
||||||
# - Other Defaults -
|
|
||||||
|
|
||||||
#dynamic_library_path = '$libdir'
|
|
||||||
#extension_destdir = '' # prepend path when loading extensions
|
|
||||||
# and shared objects (added by Debian)
|
|
||||||
#gin_fuzzy_search_limit = 0
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# LOCK MANAGEMENT
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#deadlock_timeout = 1s
|
|
||||||
#max_locks_per_transaction = 64 # min 10
|
|
||||||
# (change requires restart)
|
|
||||||
#max_pred_locks_per_transaction = 64 # min 10
|
|
||||||
# (change requires restart)
|
|
||||||
#max_pred_locks_per_relation = -2 # negative values mean
|
|
||||||
# (max_pred_locks_per_transaction
|
|
||||||
# / -max_pred_locks_per_relation) - 1
|
|
||||||
#max_pred_locks_per_page = 2 # min 0
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# VERSION AND PLATFORM COMPATIBILITY
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# - Previous PostgreSQL Versions -
|
|
||||||
|
|
||||||
#array_nulls = on
|
|
||||||
#backslash_quote = safe_encoding # on, off, or safe_encoding
|
|
||||||
#escape_string_warning = on
|
|
||||||
#lo_compat_privileges = off
|
|
||||||
#quote_all_identifiers = off
|
|
||||||
#standard_conforming_strings = on
|
|
||||||
#synchronize_seqscans = on
|
|
||||||
|
|
||||||
# - Other Platforms and Clients -
|
|
||||||
|
|
||||||
#transform_null_equals = off
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# ERROR HANDLING
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#exit_on_error = off # terminate session on any error?
|
|
||||||
#restart_after_crash = on # reinitialize after backend crash?
|
|
||||||
#data_sync_retry = off # retry or panic on failure to fsync
|
|
||||||
# data?
|
|
||||||
# (change requires restart)
|
|
||||||
#recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+)
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# CONFIG FILE INCLUDES
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# These options allow settings to be loaded from files other than the
|
|
||||||
# default postgresql.conf. Note that these are directives, not variable
|
|
||||||
# assignments, so they can usefully be given more than once.
|
|
||||||
|
|
||||||
# include_dir = 'conf.d' # include files ending in '.conf' from
|
|
||||||
# a directory, e.g., 'conf.d'
|
|
||||||
#include_if_exists = '...' # include file only if it exists
|
|
||||||
#include = '...' # include file
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# CUSTOMIZED OPTIONS
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Add settings for extensions here
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
|
|
||||||
[program:web]
|
|
||||||
command=node /app/web/web/server.js
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stdout
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
environment=PORT=3001,HOSTNAME=0.0.0.0
|
|
||||||
|
|
||||||
[program:space]
|
|
||||||
command=node /app/space/space/server.js
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stdout
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
environment=PORT=3002,HOSTNAME=0.0.0.0
|
|
||||||
|
|
||||||
[program:admin]
|
|
||||||
command=node /app/admin/admin/server.js
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stdout
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
environment=PORT=3003,HOSTNAME=0.0.0.0
|
|
||||||
|
|
||||||
[program:migrator]
|
|
||||||
directory=/app/api
|
|
||||||
command=sh -c "./bin/docker-entrypoint-migrator.sh"
|
|
||||||
autostart=true
|
|
||||||
autorestart=false
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stdout
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
|
|
||||||
[program:api]
|
|
||||||
directory=/app/api
|
|
||||||
command=sh -c "./bin/docker-entrypoint-api.sh"
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stdout
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
|
|
||||||
[program:worker]
|
|
||||||
directory=/app/api
|
|
||||||
command=sh -c "./bin/docker-entrypoint-worker.sh"
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stdout
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
|
|
||||||
[program:beat]
|
|
||||||
directory=/app/api
|
|
||||||
command=sh -c "./bin/docker-entrypoint-beat.sh"
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stdout
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
[supervisord]
|
|
||||||
user=root
|
|
||||||
nodaemon=true
|
|
||||||
stderr_logfile=/app/logs/error/supervisor.err.log
|
|
||||||
stdout_logfile=/app/logs/access/supervisor.log
|
|
||||||
|
|
||||||
[program:redis]
|
|
||||||
directory=/app/data/redis
|
|
||||||
command=redis-server
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stderr_logfile=/app/logs/error/redis.err.log
|
|
||||||
stdout_logfile=/app/logs/access/redis.log
|
|
||||||
|
|
||||||
[program:postgresql]
|
|
||||||
user=postgres
|
|
||||||
command=/usr/lib/postgresql/15/bin/postgres --config-file=/etc/postgresql/postgresql.conf
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stderr_logfile=/app/logs/error/postgresql.err.log
|
|
||||||
stdout_logfile=/app/logs/access/postgresql.log
|
|
||||||
|
|
||||||
[program:minio]
|
|
||||||
directory=/app/data/minio
|
|
||||||
command=minio server /app/data/minio
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stderr_logfile=/app/logs/error/minio.err.log
|
|
||||||
stdout_logfile=/app/logs/access/minio.log
|
|
||||||
|
|
||||||
[program:nginx]
|
|
||||||
directory=/app/data/nginx
|
|
||||||
command=/app/nginx-start.sh
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stderr_logfile=/app/logs/error/nginx.err.log
|
|
||||||
stdout_logfile=/app/logs/access/nginx.log
|
|
||||||
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
[supervisord]
|
|
||||||
user=root
|
|
||||||
nodaemon=true
|
|
||||||
stderr_logfile=/app/logs/error/supervisor.err.log
|
|
||||||
stdout_logfile=/app/logs/access/supervisor.log
|
|
||||||
|
|
||||||
[program:nginx]
|
|
||||||
directory=/app/data/nginx
|
|
||||||
command=/app/nginx-start.sh
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stderr_logfile=/app/logs/error/nginx.err.log
|
|
||||||
stdout_logfile=/app/logs/access/nginx.log
|
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:$PORT --max-requests 10000 --max-requests-jitter 1000 --access-logfile -
|
|
||||||
worker: celery -A plane worker -l info
|
|
||||||
beat: celery -A plane beat -l INFO
|
|
||||||
|
|
@ -1,224 +0,0 @@
|
||||||
# All the python scripts that are used for back migrations
|
|
||||||
import uuid
|
|
||||||
import random
|
|
||||||
from django.contrib.auth.hashers import make_password
|
|
||||||
from plane.db.models import ProjectIdentifier
|
|
||||||
from plane.db.models import (
|
|
||||||
Issue,
|
|
||||||
IssueComment,
|
|
||||||
User,
|
|
||||||
Project,
|
|
||||||
ProjectMember,
|
|
||||||
Label,
|
|
||||||
Integration,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Update description and description html values for old descriptions
|
|
||||||
def update_description():
|
|
||||||
try:
|
|
||||||
issues = Issue.objects.all()
|
|
||||||
updated_issues = []
|
|
||||||
|
|
||||||
for issue in issues:
|
|
||||||
issue.description_html = f"<p>{issue.description}</p>"
|
|
||||||
issue.description_stripped = issue.description
|
|
||||||
updated_issues.append(issue)
|
|
||||||
|
|
||||||
Issue.objects.bulk_update(
|
|
||||||
updated_issues, ["description_html", "description_stripped"], batch_size=100
|
|
||||||
)
|
|
||||||
print("Success")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
||||||
|
|
||||||
def update_comments():
|
|
||||||
try:
|
|
||||||
issue_comments = IssueComment.objects.all()
|
|
||||||
updated_issue_comments = []
|
|
||||||
|
|
||||||
for issue_comment in issue_comments:
|
|
||||||
issue_comment.comment_html = f"<p>{issue_comment.comment_stripped}</p>"
|
|
||||||
updated_issue_comments.append(issue_comment)
|
|
||||||
|
|
||||||
IssueComment.objects.bulk_update(
|
|
||||||
updated_issue_comments, ["comment_html"], batch_size=100
|
|
||||||
)
|
|
||||||
print("Success")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
||||||
|
|
||||||
def update_project_identifiers():
|
|
||||||
try:
|
|
||||||
project_identifiers = ProjectIdentifier.objects.filter(
|
|
||||||
workspace_id=None
|
|
||||||
).select_related("project", "project__workspace")
|
|
||||||
updated_identifiers = []
|
|
||||||
|
|
||||||
for identifier in project_identifiers:
|
|
||||||
identifier.workspace_id = identifier.project.workspace_id
|
|
||||||
updated_identifiers.append(identifier)
|
|
||||||
|
|
||||||
ProjectIdentifier.objects.bulk_update(
|
|
||||||
updated_identifiers, ["workspace_id"], batch_size=50
|
|
||||||
)
|
|
||||||
print("Success")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
||||||
|
|
||||||
def update_user_empty_password():
|
|
||||||
try:
|
|
||||||
users = User.objects.filter(password="")
|
|
||||||
updated_users = []
|
|
||||||
|
|
||||||
for user in users:
|
|
||||||
user.password = make_password(uuid.uuid4().hex)
|
|
||||||
user.is_password_autoset = True
|
|
||||||
updated_users.append(user)
|
|
||||||
|
|
||||||
User.objects.bulk_update(updated_users, ["password"], batch_size=50)
|
|
||||||
print("Success")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
||||||
|
|
||||||
def updated_issue_sort_order():
|
|
||||||
try:
|
|
||||||
issues = Issue.objects.all()
|
|
||||||
updated_issues = []
|
|
||||||
|
|
||||||
for issue in issues:
|
|
||||||
issue.sort_order = issue.sequence_id * random.randint(100, 500)
|
|
||||||
updated_issues.append(issue)
|
|
||||||
|
|
||||||
Issue.objects.bulk_update(updated_issues, ["sort_order"], batch_size=100)
|
|
||||||
print("Success")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
||||||
|
|
||||||
def update_project_cover_images():
|
|
||||||
try:
|
|
||||||
project_cover_images = [
|
|
||||||
"https://images.unsplash.com/photo-1677432658720-3d84f9d657b4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1661107564401-57497d8fe86f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1677352241429-dc90cfc7a623?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1677196728306-eeafea692454?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1331&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1660902179734-c94c944f7830?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1255&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1672243775941-10d763d9adef?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1677040628614-53936ff66632?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1676920410907-8d5f8dd4b5ba?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1676846328604-ce831c481346?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1155&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1676744843212-09b7e64c3a05?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1676798531090-1608bedeac7b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1597088758740-56fd7ec8a3f0?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1169&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1676638392418-80aad7c87b96?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1649639194967-2fec0b4ea7bc?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1675883086902-b453b3f8146e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1675887057159-40fca28fdc5d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1173&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1675373980203-f84c5a672aa5?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1675191475318-d2bf6bad1200?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1675456230532-2194d0c4bcc0?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
|
|
||||||
"https://images.unsplash.com/photo-1675371788315-60fa0ef48267?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
|
|
||||||
]
|
|
||||||
|
|
||||||
projects = Project.objects.all()
|
|
||||||
updated_projects = []
|
|
||||||
for project in projects:
|
|
||||||
project.cover_image = project_cover_images[random.randint(0, 19)]
|
|
||||||
updated_projects.append(project)
|
|
||||||
|
|
||||||
Project.objects.bulk_update(updated_projects, ["cover_image"], batch_size=100)
|
|
||||||
print("Success")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
||||||
|
|
||||||
def update_user_view_property():
|
|
||||||
try:
|
|
||||||
project_members = ProjectMember.objects.all()
|
|
||||||
updated_project_members = []
|
|
||||||
for project_member in project_members:
|
|
||||||
project_member.default_props = {
|
|
||||||
"filters": {"type": None},
|
|
||||||
"orderBy": "-created_at",
|
|
||||||
"collapsed": True,
|
|
||||||
"issueView": "list",
|
|
||||||
"filterIssue": None,
|
|
||||||
"groupByProperty": None,
|
|
||||||
"showEmptyGroups": True,
|
|
||||||
}
|
|
||||||
updated_project_members.append(project_member)
|
|
||||||
|
|
||||||
ProjectMember.objects.bulk_update(
|
|
||||||
updated_project_members, ["default_props"], batch_size=100
|
|
||||||
)
|
|
||||||
print("Success")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
||||||
|
|
||||||
def update_label_color():
|
|
||||||
try:
|
|
||||||
labels = Label.objects.filter(color="")
|
|
||||||
updated_labels = []
|
|
||||||
for label in labels:
|
|
||||||
label.color = f"#{random.randint(0, 0xFFFFFF+1):06X}"
|
|
||||||
updated_labels.append(label)
|
|
||||||
|
|
||||||
Label.objects.bulk_update(updated_labels, ["color"], batch_size=100)
|
|
||||||
print("Success")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
||||||
|
|
||||||
def create_slack_integration():
|
|
||||||
try:
|
|
||||||
_ = Integration.objects.create(provider="slack", network=2, title="Slack")
|
|
||||||
print("Success")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
||||||
|
|
||||||
def update_integration_verified():
|
|
||||||
try:
|
|
||||||
integrations = Integration.objects.all()
|
|
||||||
updated_integrations = []
|
|
||||||
for integration in integrations:
|
|
||||||
integration.verified = True
|
|
||||||
updated_integrations.append(integration)
|
|
||||||
|
|
||||||
Integration.objects.bulk_update(
|
|
||||||
updated_integrations, ["verified"], batch_size=10
|
|
||||||
)
|
|
||||||
print("Success")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
||||||
|
|
||||||
def update_start_date():
|
|
||||||
try:
|
|
||||||
issues = Issue.objects.filter(state__group__in=["started", "completed"])
|
|
||||||
updated_issues = []
|
|
||||||
for issue in issues:
|
|
||||||
issue.start_date = issue.created_at.date()
|
|
||||||
updated_issues.append(issue)
|
|
||||||
Issue.objects.bulk_update(updated_issues, ["start_date"], batch_size=500)
|
|
||||||
print("Success")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("Failed")
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
python-3.12.6
|
|
||||||
73
app.json
73
app.json
|
|
@ -1,73 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Plane",
|
|
||||||
"description": "Plane helps you track your issues, epics, and product roadmaps.",
|
|
||||||
"repository": "http://github.com/makeplane/plane",
|
|
||||||
"logo": "https://avatars.githubusercontent.com/u/115727700?s=200&v=4",
|
|
||||||
"website": "https://plane.so/",
|
|
||||||
"success_url": "/",
|
|
||||||
"stack": "heroku-22",
|
|
||||||
"keywords": ["plane", "project management", "django", "next"],
|
|
||||||
"addons": ["heroku-postgresql:mini", "heroku-redis:mini"],
|
|
||||||
"buildpacks": [
|
|
||||||
{
|
|
||||||
"url": "https://github.com/heroku/heroku-buildpack-python.git"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/heroku/heroku-buildpack-nodejs#v176"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"EMAIL_HOST": {
|
|
||||||
"description": "Email host to send emails from",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"EMAIL_HOST_USER": {
|
|
||||||
"description": "Email host to send emails from",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"EMAIL_HOST_PASSWORD": {
|
|
||||||
"description": "Email host to send emails from",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"EMAIL_FROM": {
|
|
||||||
"description": "Email Sender",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"EMAIL_PORT": {
|
|
||||||
"description": "The default Email PORT to use",
|
|
||||||
"value": "587"
|
|
||||||
},
|
|
||||||
"AWS_REGION": {
|
|
||||||
"description": "AWS Region to use for S3",
|
|
||||||
"value": "false"
|
|
||||||
},
|
|
||||||
"AWS_ACCESS_KEY_ID": {
|
|
||||||
"description": "AWS Access Key ID to use for S3",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"AWS_SECRET_ACCESS_KEY": {
|
|
||||||
"description": "AWS Secret Access Key to use for S3",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"AWS_S3_BUCKET_NAME": {
|
|
||||||
"description": "AWS Bucket Name to use for S3",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"WEB_URL": {
|
|
||||||
"description": "Web URL for Plane this will be used for redirections in the emails",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"GITHUB_CLIENT_SECRET": {
|
|
||||||
"description": "GitHub Client Secret",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"NEXT_PUBLIC_API_BASE_URL": {
|
|
||||||
"description": "Next Public API Base URL",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"SECRET_KEY": {
|
|
||||||
"description": "Django Secret Key",
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:20-alpine as base
|
FROM node:22-alpine AS base
|
||||||
|
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
# STAGE 1: Build the project
|
# STAGE 1: Build the project
|
||||||
|
|
@ -46,8 +46,8 @@ ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
|
||||||
ARG NEXT_PUBLIC_WEB_BASE_URL=""
|
ARG NEXT_PUBLIC_WEB_BASE_URL=""
|
||||||
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
|
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
|
||||||
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ENV TURBO_TELEMETRY_DISABLED 1
|
ENV TURBO_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
RUN yarn turbo run build --filter=admin
|
RUN yarn turbo run build --filter=admin
|
||||||
|
|
||||||
|
|
@ -57,12 +57,16 @@ RUN yarn turbo run build --filter=admin
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=installer /app/admin/next.config.js .
|
# Don't run production as root
|
||||||
COPY --from=installer /app/admin/package.json .
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
COPY --from=installer /app/admin/.next/standalone ./
|
# Automatically leverage output traces to reduce image size
|
||||||
COPY --from=installer /app/admin/.next/static ./admin/.next/static
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
COPY --from=installer /app/admin/public ./admin/public
|
COPY --from=installer /app/apps/admin/.next/standalone ./
|
||||||
|
COPY --from=installer /app/apps/admin/.next/static ./apps/admin/.next/static
|
||||||
|
COPY --from=installer /app/apps/admin/public ./apps/admin/public
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
ARG NEXT_PUBLIC_API_BASE_URL=""
|
||||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||||
|
|
@ -82,7 +86,9 @@ ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
|
||||||
ARG NEXT_PUBLIC_WEB_BASE_URL=""
|
ARG NEXT_PUBLIC_WEB_BASE_URL=""
|
||||||
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
|
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
|
||||||
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ENV TURBO_TELEMETRY_DISABLED 1
|
ENV TURBO_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "apps/admin/server.js"]
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:20-alpine
|
FROM node:22-alpine
|
||||||
RUN apk add --no-cache libc6-compat
|
RUN apk add --no-cache libc6-compat
|
||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
@ -5,7 +5,7 @@ import { Lightbulb } from "lucide-react";
|
||||||
import { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
|
import { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
|
||||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ControllerInput, TControllerInputFormField } from "@/components/common";
|
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Artificial Intelligence Settings - Plane Web",
|
title: "Artificial Intelligence Settings - God Mode",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AILayout({ children }: { children: ReactNode }) {
|
export default function AILayout({ children }: { children: ReactNode }) {
|
||||||
return <AdminLayout>{children}</AdminLayout>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
@ -10,14 +10,10 @@ import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigura
|
||||||
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import {
|
import { CodeBlock } from "@/components/common/code-block";
|
||||||
CodeBlock,
|
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
|
||||||
ConfirmDiscardModal,
|
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
|
||||||
ControllerInput,
|
import { CopyField, TCopyField } from "@/components/common/copy-field";
|
||||||
CopyField,
|
|
||||||
TControllerInputFormField,
|
|
||||||
TCopyField,
|
|
||||||
} from "@/components/common";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "GitHub Authentication - God Mode",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function GitHubAuthenticationLayout({ children }: { children: ReactNode }) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
@ -9,8 +9,7 @@ import useSWR from "swr";
|
||||||
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
||||||
import { resolveGeneralTheme } from "@plane/utils";
|
import { resolveGeneralTheme } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { AuthenticationMethodCard } from "@/components/authentication";
|
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||||
import { PageHeader } from "@/components/common";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
// icons
|
// icons
|
||||||
|
|
@ -61,9 +60,11 @@ const InstanceGithubAuthenticationPage = observer(() => {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isGithubEnabled = enableGithubConfig === "1";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader title="GitHub Authentication - Plane Web" />
|
|
||||||
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
|
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
|
||||||
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
|
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
|
||||||
<AuthenticationMethodCard
|
<AuthenticationMethodCard
|
||||||
|
|
@ -79,11 +80,9 @@ const InstanceGithubAuthenticationPage = observer(() => {
|
||||||
}
|
}
|
||||||
config={
|
config={
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
value={Boolean(parseInt(enableGithubConfig))}
|
value={isGithubEnabled}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
Boolean(parseInt(enableGithubConfig)) === true
|
updateConfig("IS_GITHUB_ENABLED", isGithubEnabled ? "0" : "1");
|
||||||
? updateConfig("IS_GITHUB_ENABLED", "0")
|
|
||||||
: updateConfig("IS_GITHUB_ENABLED", "1");
|
|
||||||
}}
|
}}
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={isSubmitting || !formattedConfig}
|
disabled={isSubmitting || !formattedConfig}
|
||||||
|
|
@ -8,14 +8,10 @@ import { IFormattedInstanceConfiguration, TInstanceGitlabAuthenticationConfigura
|
||||||
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import {
|
import { CodeBlock } from "@/components/common/code-block";
|
||||||
CodeBlock,
|
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
|
||||||
ConfirmDiscardModal,
|
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
|
||||||
ControllerInput,
|
import { CopyField, TCopyField } from "@/components/common/copy-field";
|
||||||
CopyField,
|
|
||||||
TControllerInputFormField,
|
|
||||||
TCopyField,
|
|
||||||
} from "@/components/common";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "GitLab Authentication - God Mode",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function GitlabAuthenticationLayout({ children }: { children: ReactNode }) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
@ -6,8 +6,7 @@ import Image from "next/image";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { AuthenticationMethodCard } from "@/components/authentication";
|
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||||
import { PageHeader } from "@/components/common";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
// icons
|
// icons
|
||||||
|
|
@ -57,7 +56,6 @@ const InstanceGitlabAuthenticationPage = observer(() => {
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader title="GitLab Authentication - Plane Web" />
|
|
||||||
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
|
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
|
||||||
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
|
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
|
||||||
<AuthenticationMethodCard
|
<AuthenticationMethodCard
|
||||||
|
|
@ -9,14 +9,10 @@ import { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigura
|
||||||
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import {
|
import { CodeBlock } from "@/components/common/code-block";
|
||||||
CodeBlock,
|
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
|
||||||
ConfirmDiscardModal,
|
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
|
||||||
ControllerInput,
|
import { CopyField, TCopyField } from "@/components/common/copy-field";
|
||||||
CopyField,
|
|
||||||
TControllerInputFormField,
|
|
||||||
TCopyField,
|
|
||||||
} from "@/components/common";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Google Authentication - God Mode",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function GoogleAuthenticationLayout({ children }: { children: ReactNode }) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
@ -6,8 +6,7 @@ import Image from "next/image";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { AuthenticationMethodCard } from "@/components/authentication";
|
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||||
import { PageHeader } from "@/components/common";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
// icons
|
// icons
|
||||||
|
|
@ -57,7 +56,6 @@ const InstanceGoogleAuthenticationPage = observer(() => {
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader title="Google Authentication - Plane Web" />
|
|
||||||
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
|
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
|
||||||
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
|
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
|
||||||
<AuthenticationMethodCard
|
<AuthenticationMethodCard
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Authentication Settings - Plane Web",
|
title: "Authentication Settings - Plane Web",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AuthenticationLayout({ children }: { children: ReactNode }) {
|
export default function AuthenticationLayout({ children }: { children: ReactNode }) {
|
||||||
return <AdminLayout>{children}</AdminLayout>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ import { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from
|
||||||
// ui
|
// ui
|
||||||
import { Button, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ControllerInput, TControllerInputFormField } from "@/components/common";
|
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
// local components
|
// local components
|
||||||
|
|
@ -49,9 +49,9 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
|
||||||
EMAIL_USE_TLS: config["EMAIL_USE_TLS"],
|
EMAIL_USE_TLS: config["EMAIL_USE_TLS"],
|
||||||
EMAIL_USE_SSL: config["EMAIL_USE_SSL"],
|
EMAIL_USE_SSL: config["EMAIL_USE_SSL"],
|
||||||
EMAIL_FROM: config["EMAIL_FROM"],
|
EMAIL_FROM: config["EMAIL_FROM"],
|
||||||
|
ENABLE_SMTP: config["ENABLE_SMTP"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emailFormFields: TControllerInputFormField[] = [
|
const emailFormFields: TControllerInputFormField[] = [
|
||||||
{
|
{
|
||||||
key: "EMAIL_HOST",
|
key: "EMAIL_HOST",
|
||||||
|
|
@ -101,7 +101,7 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
|
||||||
];
|
];
|
||||||
|
|
||||||
const onSubmit = async (formData: EmailFormValues) => {
|
const onSubmit = async (formData: EmailFormValues) => {
|
||||||
const payload: Partial<EmailFormValues> = { ...formData };
|
const payload: Partial<EmailFormValues> = { ...formData, ENABLE_SMTP: "1" };
|
||||||
|
|
||||||
await updateInstanceConfigurations(payload)
|
await updateInstanceConfigurations(payload)
|
||||||
.then(() =>
|
.then(() =>
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
|
||||||
|
|
||||||
interface EmailLayoutProps {
|
interface EmailLayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Email Settings - Plane Web",
|
title: "Email Settings - God Mode",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EmailLayout({ children }: EmailLayoutProps) {
|
export default function EmailLayout({ children }: EmailLayoutProps) {
|
||||||
return <AdminLayout>{children}</AdminLayout>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
93
apps/admin/app/(all)/(dashboard)/email/page.tsx
Normal file
93
apps/admin/app/(all)/(dashboard)/email/page.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import { Loader, setToast, TOAST_TYPE, ToggleSwitch } from "@plane/ui";
|
||||||
|
// hooks
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
// components
|
||||||
|
import { InstanceEmailForm } from "./email-config-form";
|
||||||
|
|
||||||
|
const InstanceEmailPage = observer(() => {
|
||||||
|
// store
|
||||||
|
const { fetchInstanceConfigurations, formattedConfig, disableEmail } = useInstance();
|
||||||
|
|
||||||
|
const { isLoading } = useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
|
||||||
|
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [isSMTPEnabled, setIsSMTPEnabled] = useState(false);
|
||||||
|
|
||||||
|
const handleToggle = async () => {
|
||||||
|
if (isSMTPEnabled) {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
try {
|
||||||
|
await disableEmail();
|
||||||
|
setIsSMTPEnabled(false);
|
||||||
|
setToast({
|
||||||
|
title: "Email feature disabled",
|
||||||
|
message: "Email feature has been disabled",
|
||||||
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setToast({
|
||||||
|
title: "Error disabling email",
|
||||||
|
message: "Failed to disable email feature. Please try again.",
|
||||||
|
type: TOAST_TYPE.ERROR,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsSMTPEnabled(true);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (formattedConfig) {
|
||||||
|
setIsSMTPEnabled(formattedConfig.ENABLE_SMTP === "1");
|
||||||
|
}
|
||||||
|
}, [formattedConfig]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
|
||||||
|
<div className="flex items-center justify-between gap-4 border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
|
||||||
|
<div className="py-4 space-y-1 flex-shrink-0">
|
||||||
|
<div className="text-xl font-medium text-custom-text-100">Secure emails from your own instance</div>
|
||||||
|
<div className="text-sm font-normal text-custom-text-300">
|
||||||
|
Plane can send useful emails to you and your users from your own instance without talking to the Internet.
|
||||||
|
<div className="text-sm font-normal text-custom-text-300">
|
||||||
|
Set it up below and please test your settings before you save them.
|
||||||
|
<span className="text-red-400">Misconfigs can lead to email bounces and errors.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isLoading ? (
|
||||||
|
<Loader>
|
||||||
|
<Loader.Item width="24px" height="16px" className="rounded-full" />
|
||||||
|
</Loader>
|
||||||
|
) : (
|
||||||
|
<ToggleSwitch value={isSMTPEnabled} onChange={handleToggle} size="sm" disabled={isSubmitting} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isSMTPEnabled && !isLoading && (
|
||||||
|
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
|
||||||
|
{formattedConfig ? (
|
||||||
|
<InstanceEmailForm config={formattedConfig} />
|
||||||
|
) : (
|
||||||
|
<Loader className="space-y-10">
|
||||||
|
<Loader.Item height="50px" width="75%" />
|
||||||
|
<Loader.Item height="50px" width="75%" />
|
||||||
|
<Loader.Item height="50px" width="40%" />
|
||||||
|
<Loader.Item height="50px" width="40%" />
|
||||||
|
<Loader.Item height="50px" width="20%" />
|
||||||
|
</Loader>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InstanceEmailPage;
|
||||||
|
|
@ -8,7 +8,7 @@ import { IInstance, IInstanceAdmin } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
|
import { Button, Input, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ControllerInput } from "@/components/common";
|
import { ControllerInput } from "@/components/common/controller-input";
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
import { IntercomConfig } from "./intercom";
|
import { IntercomConfig } from "./intercom";
|
||||||
// hooks
|
// hooks
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "General Settings - Plane Web",
|
title: "General Settings - God Mode",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function GeneralLayout({ children }: { children: ReactNode }) {
|
export default function GeneralLayout({ children }: { children: ReactNode }) {
|
||||||
return <AdminLayout>{children}</AdminLayout>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
@ -3,16 +3,27 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
// mobx
|
import { Menu, Settings } from "lucide-react";
|
||||||
// ui
|
|
||||||
import { Settings } from "lucide-react";
|
|
||||||
// icons
|
// icons
|
||||||
import { Breadcrumbs } from "@plane/ui";
|
import { Breadcrumbs } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { SidebarHamburgerToggle } from "@/components/admin-sidebar";
|
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
// hooks
|
||||||
|
import { useTheme } from "@/hooks/store";
|
||||||
|
|
||||||
export const InstanceHeader: FC = observer(() => {
|
export const HamburgerToggle: FC = observer(() => {
|
||||||
|
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="w-7 h-7 rounded flex justify-center items-center bg-custom-background-80 transition-all hover:bg-custom-background-90 cursor-pointer group md:hidden"
|
||||||
|
onClick={() => toggleSidebar(!isSidebarCollapsed)}
|
||||||
|
>
|
||||||
|
<Menu size={14} className="text-custom-text-200 group-hover:text-custom-text-100 transition-all" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AdminHeader: FC = observer(() => {
|
||||||
const pathName = usePathname();
|
const pathName = usePathname();
|
||||||
|
|
||||||
const getHeaderTitle = (pathName: string) => {
|
const getHeaderTitle = (pathName: string) => {
|
||||||
|
|
@ -61,9 +72,9 @@ export const InstanceHeader: FC = observer(() => {
|
||||||
const breadcrumbItems = generateBreadcrumbItems(pathName);
|
const breadcrumbItems = generateBreadcrumbItems(pathName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-sidebar-border-200 bg-custom-sidebar-background-100 p-4">
|
<div className="relative z-10 flex h-header w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-sidebar-border-200 bg-custom-sidebar-background-100 p-4">
|
||||||
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
|
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
|
||||||
<SidebarHamburgerToggle />
|
<HamburgerToggle />
|
||||||
{breadcrumbItems.length >= 0 && (
|
{breadcrumbItems.length >= 0 && (
|
||||||
<div>
|
<div>
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
|
|
@ -4,7 +4,7 @@ import { useForm } from "react-hook-form";
|
||||||
import { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
|
import { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
|
||||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ControllerInput } from "@/components/common";
|
import { ControllerInput } from "@/components/common/controller-input";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
|
||||||
|
|
||||||
interface ImageLayoutProps {
|
interface ImageLayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Images Settings - Plane Web",
|
title: "Images Settings - God Mode",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ImageLayout({ children }: ImageLayoutProps) {
|
export default function ImageLayout({ children }: ImageLayoutProps) {
|
||||||
return <AdminLayout>{children}</AdminLayout>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, ReactNode, useEffect } from "react";
|
import { FC, ReactNode, useEffect } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import { InstanceSidebar } from "@/components/admin-sidebar";
|
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||||
import { InstanceHeader } from "@/components/auth-header";
|
|
||||||
import { LogoSpinner } from "@/components/common";
|
|
||||||
import { NewUserPopup } from "@/components/new-user-popup";
|
import { NewUserPopup } from "@/components/new-user-popup";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useUser } from "@/hooks/store";
|
||||||
|
// local components
|
||||||
|
import { AdminHeader } from "./header";
|
||||||
|
import { AdminSidebar } from "./sidebar";
|
||||||
|
|
||||||
type TAdminLayout = {
|
type TAdminLayout = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AdminLayout: FC<TAdminLayout> = observer((props) => {
|
const AdminLayout: FC<TAdminLayout> = (props) => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -35,14 +37,20 @@ export const AdminLayout: FC<TAdminLayout> = observer((props) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (isUserLoggedIn) {
|
||||||
<div className="relative flex h-screen w-screen overflow-hidden">
|
return (
|
||||||
<InstanceSidebar />
|
<div className="relative flex h-screen w-screen overflow-hidden">
|
||||||
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
|
<AdminSidebar />
|
||||||
<InstanceHeader />
|
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
|
||||||
<div className="h-full w-full overflow-hidden">{children}</div>
|
<AdminHeader />
|
||||||
</main>
|
<div className="h-full w-full overflow-hidden">{children}</div>
|
||||||
<NewUserPopup />
|
</main>
|
||||||
</div>
|
<NewUserPopup />
|
||||||
);
|
</div>
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(AdminLayout);
|
||||||
|
|
@ -16,7 +16,7 @@ import { useTheme, useUser } from "@/hooks/store";
|
||||||
// service initialization
|
// service initialization
|
||||||
const authService = new AuthService();
|
const authService = new AuthService();
|
||||||
|
|
||||||
export const SidebarDropdown = observer(() => {
|
export const AdminSidebarDropdown = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { isSidebarCollapsed } = useTheme();
|
const { isSidebarCollapsed } = useTheme();
|
||||||
const { currentUser, signOut } = useUser();
|
const { currentUser, signOut } = useUser();
|
||||||
|
|
@ -77,7 +77,7 @@ export const SidebarDropdown = observer(() => {
|
||||||
}, [csrfToken]);
|
}, [csrfToken]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex max-h-[3.75rem] items-center gap-x-5 gap-y-2 border-b border-custom-sidebar-border-200 px-4 py-3.5">
|
<div className="flex max-h-header items-center gap-x-5 gap-y-2 border-b border-custom-sidebar-border-200 px-4 py-3.5">
|
||||||
<div className="h-full w-full truncate">
|
<div className="h-full w-full truncate">
|
||||||
<div
|
<div
|
||||||
className={`flex flex-grow items-center gap-x-2 truncate rounded py-1 ${
|
className={`flex flex-grow items-center gap-x-2 truncate rounded py-1 ${
|
||||||
|
|
@ -33,7 +33,7 @@ const helpOptions = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const HelpSection: FC = observer(() => {
|
export const AdminSidebarHelpSection: FC = observer(() => {
|
||||||
// states
|
// states
|
||||||
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
||||||
// store
|
// store
|
||||||
|
|
@ -49,7 +49,7 @@ const INSTANCE_ADMIN_LINKS = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SidebarMenu = observer(() => {
|
export const AdminSidebarMenu = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
||||||
// router
|
// router
|
||||||
|
|
@ -4,12 +4,14 @@ import { FC, useEffect, useRef } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// plane helpers
|
// plane helpers
|
||||||
import { useOutsideClickDetector } from "@plane/hooks";
|
import { useOutsideClickDetector } from "@plane/hooks";
|
||||||
// components
|
|
||||||
import { HelpSection, SidebarMenu, SidebarDropdown } from "@/components/admin-sidebar";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useTheme } from "@/hooks/store";
|
import { useTheme } from "@/hooks/store";
|
||||||
|
// components
|
||||||
|
import { AdminSidebarDropdown } from "./sidebar-dropdown";
|
||||||
|
import { AdminSidebarHelpSection } from "./sidebar-help-section";
|
||||||
|
import { AdminSidebarMenu } from "./sidebar-menu";
|
||||||
|
|
||||||
export const InstanceSidebar: FC = observer(() => {
|
export const AdminSidebar: FC = observer(() => {
|
||||||
// store
|
// store
|
||||||
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
||||||
|
|
||||||
|
|
@ -47,9 +49,9 @@ export const InstanceSidebar: FC = observer(() => {
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div ref={ref} className="flex h-full w-full flex-1 flex-col">
|
<div ref={ref} className="flex h-full w-full flex-1 flex-col">
|
||||||
<SidebarDropdown />
|
<AdminSidebarDropdown />
|
||||||
<SidebarMenu />
|
<AdminSidebarMenu />
|
||||||
<HelpSection />
|
<AdminSidebarHelpSection />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
// layouts
|
|
||||||
import { AdminLayout } from "@/layouts/admin-layout";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Workspace Management - Plane Web",
|
title: "Workspace Management - God Mode",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function WorkspaceManagementLayout({ children }: { children: ReactNode }) {
|
export default function WorkspaceManagementLayout({ children }: { children: ReactNode }) {
|
||||||
return <AdminLayout>{children}</AdminLayout>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ import { TInstanceConfigurationKeys } from "@plane/types";
|
||||||
import { Button, getButtonStyling, Loader, setPromiseToast, ToggleSwitch } from "@plane/ui";
|
import { Button, getButtonStyling, Loader, setPromiseToast, ToggleSwitch } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { WorkspaceListItem } from "@/components/workspace";
|
import { WorkspaceListItem } from "@/components/workspace/list-item";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance, useWorkspace } from "@/hooks/store";
|
import { useInstance, useWorkspace } from "@/hooks/store";
|
||||||
|
|
||||||
|
|
@ -7,13 +7,11 @@ import { SUPPORT_EMAIL, EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane
|
||||||
import { TGetBaseAuthenticationModeProps, TInstanceAuthenticationModes } from "@plane/types";
|
import { TGetBaseAuthenticationModeProps, TInstanceAuthenticationModes } from "@plane/types";
|
||||||
import { resolveGeneralTheme } from "@plane/utils";
|
import { resolveGeneralTheme } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import {
|
import { EmailCodesConfiguration } from "@/components/authentication/email-config-switch";
|
||||||
EmailCodesConfiguration,
|
import { GithubConfiguration } from "@/components/authentication/github-config";
|
||||||
GithubConfiguration,
|
import { GitlabConfiguration } from "@/components/authentication/gitlab-config";
|
||||||
GitlabConfiguration,
|
import { GoogleConfiguration } from "@/components/authentication/google-config";
|
||||||
GoogleConfiguration,
|
import { PasswordLoginConfiguration } from "@/components/authentication/password-config-switch";
|
||||||
PasswordLoginConfiguration,
|
|
||||||
} from "@/components/authentication";
|
|
||||||
// images
|
// images
|
||||||
import githubLightModeImage from "@/public/logos/github-black.png";
|
import githubLightModeImage from "@/public/logos/github-black.png";
|
||||||
import githubDarkModeImage from "@/public/logos/github-white.png";
|
import githubDarkModeImage from "@/public/logos/github-white.png";
|
||||||
|
|
@ -1,26 +1,18 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, ReactNode } from "react";
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
// logo/ images
|
// logo assets
|
||||||
import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg";
|
import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg";
|
||||||
import PlaneBackgroundPattern from "public/auth/background-pattern.svg";
|
import PlaneBackgroundPattern from "public/auth/background-pattern.svg";
|
||||||
import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png";
|
import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png";
|
||||||
import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png";
|
import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png";
|
||||||
|
|
||||||
type TDefaultLayout = {
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
children: ReactNode;
|
|
||||||
withoutBackground?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DefaultLayout: FC<TDefaultLayout> = (props) => {
|
|
||||||
const { children, withoutBackground = false } = props;
|
|
||||||
// hooks
|
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
const patternBackground = resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern;
|
|
||||||
|
|
||||||
|
const patternBackground = resolvedTheme === "light" ? PlaneBackgroundPattern : PlaneBackgroundPatternDark;
|
||||||
const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo;
|
const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -33,13 +25,11 @@ export const DefaultLayout: FC<TDefaultLayout> = (props) => {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!withoutBackground && (
|
<div className="absolute inset-0 z-0">
|
||||||
<div className="absolute inset-0 z-0">
|
<Image src={patternBackground} className="w-screen h-full object-cover" alt="Plane background pattern" />
|
||||||
<Image src={patternBackground} className="w-screen h-full object-cover" alt="Plane background pattern" />
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="relative z-10 flex-grow">{children}</div>
|
<div className="relative z-10 flex-grow">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
62
apps/admin/app/(all)/(home)/page.tsx
Normal file
62
apps/admin/app/(all)/(home)/page.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
// components
|
||||||
|
import { InstanceFailureView } from "@/components/instance/failure";
|
||||||
|
import { InstanceLoading } from "@/components/instance/loading";
|
||||||
|
import { InstanceSetupForm } from "@/components/instance/setup-form";
|
||||||
|
// hooks
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
// components
|
||||||
|
import { InstanceSignInForm } from "./sign-in-form";
|
||||||
|
|
||||||
|
const HomePage = () => {
|
||||||
|
// store hooks
|
||||||
|
const { instance, error } = useInstance();
|
||||||
|
|
||||||
|
// if instance is not fetched, show loading
|
||||||
|
if (!instance && !error) {
|
||||||
|
return (
|
||||||
|
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
|
||||||
|
<InstanceLoading />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if instance fetch fails, show failure view
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
|
||||||
|
<InstanceFailureView />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if instance is fetched and setup is not done, show setup form
|
||||||
|
if (instance && !instance?.is_setup_done) {
|
||||||
|
return (
|
||||||
|
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
|
||||||
|
<InstanceSetupForm />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if instance is fetched and setup is done, show sign in form
|
||||||
|
return (
|
||||||
|
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
|
||||||
|
<div className="relative flex flex-col space-y-6">
|
||||||
|
<div className="text-center space-y-1">
|
||||||
|
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||||
|
Manage your Plane instance
|
||||||
|
</h3>
|
||||||
|
<p className="font-medium text-onboarding-text-400">
|
||||||
|
Configure instance-wide settings to secure your instance
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<InstanceSignInForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(HomePage);
|
||||||
178
apps/admin/app/(all)/(home)/sign-in-form.tsx
Normal file
178
apps/admin/app/(all)/(home)/sign-in-form.tsx
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC, useEffect, useMemo, useState } from "react";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
|
// plane internal packages
|
||||||
|
import { API_BASE_URL, EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants";
|
||||||
|
import { AuthService } from "@plane/services";
|
||||||
|
import { Button, Input, Spinner } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { Banner } from "@/components/common/banner";
|
||||||
|
// local components
|
||||||
|
import { AuthBanner } from "./auth-banner";
|
||||||
|
import { authErrorHandler } from "./auth-helpers";
|
||||||
|
|
||||||
|
// service initialization
|
||||||
|
const authService = new AuthService();
|
||||||
|
|
||||||
|
// error codes
|
||||||
|
enum EErrorCodes {
|
||||||
|
INSTANCE_NOT_CONFIGURED = "INSTANCE_NOT_CONFIGURED",
|
||||||
|
REQUIRED_EMAIL_PASSWORD = "REQUIRED_EMAIL_PASSWORD",
|
||||||
|
INVALID_EMAIL = "INVALID_EMAIL",
|
||||||
|
USER_DOES_NOT_EXIST = "USER_DOES_NOT_EXIST",
|
||||||
|
AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED",
|
||||||
|
}
|
||||||
|
|
||||||
|
type TError = {
|
||||||
|
type: EErrorCodes | undefined;
|
||||||
|
message: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// form data
|
||||||
|
type TFormData = {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultFromData: TFormData = {
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InstanceSignInForm: FC = () => {
|
||||||
|
// search params
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const emailParam = searchParams.get("email") || undefined;
|
||||||
|
const errorCode = searchParams.get("error_code") || undefined;
|
||||||
|
const errorMessage = searchParams.get("error_message") || undefined;
|
||||||
|
// state
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
|
||||||
|
const [formData, setFormData] = useState<TFormData>(defaultFromData);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [errorInfo, setErrorInfo] = useState<TAdminAuthErrorInfo | undefined>(undefined);
|
||||||
|
|
||||||
|
const handleFormChange = (key: keyof TFormData, value: string | boolean) =>
|
||||||
|
setFormData((prev) => ({ ...prev, [key]: value }));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (csrfToken === undefined)
|
||||||
|
authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
|
||||||
|
}, [csrfToken]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (emailParam) setFormData((prev) => ({ ...prev, email: emailParam }));
|
||||||
|
}, [emailParam]);
|
||||||
|
|
||||||
|
// derived values
|
||||||
|
const errorData: TError = useMemo(() => {
|
||||||
|
if (errorCode && errorMessage) {
|
||||||
|
switch (errorCode) {
|
||||||
|
case EErrorCodes.INSTANCE_NOT_CONFIGURED:
|
||||||
|
return { type: EErrorCodes.INSTANCE_NOT_CONFIGURED, message: errorMessage };
|
||||||
|
case EErrorCodes.REQUIRED_EMAIL_PASSWORD:
|
||||||
|
return { type: EErrorCodes.REQUIRED_EMAIL_PASSWORD, message: errorMessage };
|
||||||
|
case EErrorCodes.INVALID_EMAIL:
|
||||||
|
return { type: EErrorCodes.INVALID_EMAIL, message: errorMessage };
|
||||||
|
case EErrorCodes.USER_DOES_NOT_EXIST:
|
||||||
|
return { type: EErrorCodes.USER_DOES_NOT_EXIST, message: errorMessage };
|
||||||
|
case EErrorCodes.AUTHENTICATION_FAILED:
|
||||||
|
return { type: EErrorCodes.AUTHENTICATION_FAILED, message: errorMessage };
|
||||||
|
default:
|
||||||
|
return { type: undefined, message: undefined };
|
||||||
|
}
|
||||||
|
} else return { type: undefined, message: undefined };
|
||||||
|
}, [errorCode, errorMessage]);
|
||||||
|
|
||||||
|
const isButtonDisabled = useMemo(
|
||||||
|
() => (!isSubmitting && formData.email && formData.password ? false : true),
|
||||||
|
[formData.email, formData.password, isSubmitting]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (errorCode) {
|
||||||
|
const errorDetail = authErrorHandler(errorCode?.toString() as EAdminAuthErrorCodes);
|
||||||
|
if (errorDetail) {
|
||||||
|
setErrorInfo(errorDetail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [errorCode]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="space-y-4"
|
||||||
|
method="POST"
|
||||||
|
action={`${API_BASE_URL}/api/instances/admins/sign-in/`}
|
||||||
|
onSubmit={() => setIsSubmitting(true)}
|
||||||
|
onError={() => setIsSubmitting(false)}
|
||||||
|
>
|
||||||
|
{errorData.type && errorData?.message ? (
|
||||||
|
<Banner type="error" message={errorData?.message} />
|
||||||
|
) : (
|
||||||
|
<>{errorInfo && <AuthBanner bannerData={errorInfo} handleBannerData={(value) => setErrorInfo(value)} />}</>
|
||||||
|
)}
|
||||||
|
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||||
|
|
||||||
|
<div className="w-full space-y-1">
|
||||||
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
||||||
|
Email <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
inputSize="md"
|
||||||
|
placeholder="name@company.com"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={(e) => handleFormChange("email", e.target.value)}
|
||||||
|
autoComplete="on"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full space-y-1">
|
||||||
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
||||||
|
Password <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
inputSize="md"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={(e) => handleFormChange("password", e.target.value)}
|
||||||
|
autoComplete="on"
|
||||||
|
/>
|
||||||
|
{showPassword ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
||||||
|
onClick={() => setShowPassword(false)}
|
||||||
|
>
|
||||||
|
<EyeOff className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
||||||
|
onClick={() => setShowPassword(true)}
|
||||||
|
>
|
||||||
|
<Eye className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="py-2">
|
||||||
|
<Button type="submit" size="lg" className="w-full" disabled={isButtonDisabled}>
|
||||||
|
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Sign in"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
23
apps/admin/app/(all)/instance.provider.tsx
Normal file
23
apps/admin/app/(all)/instance.provider.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { FC, ReactNode } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import useSWR from "swr";
|
||||||
|
// hooks
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
|
type InstanceProviderProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InstanceProvider: FC<InstanceProviderProps> = observer((props) => {
|
||||||
|
const { children } = props;
|
||||||
|
// store hooks
|
||||||
|
const { fetchInstanceInfo } = useInstance();
|
||||||
|
// fetching instance details
|
||||||
|
useSWR("INSTANCE_DETAILS", () => fetchInstanceInfo(), {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateIfStale: false,
|
||||||
|
errorRetryCount: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
});
|
||||||
33
apps/admin/app/(all)/layout.tsx
Normal file
33
apps/admin/app/(all)/layout.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ThemeProvider } from "next-themes";
|
||||||
|
import { SWRConfig } from "swr";
|
||||||
|
// providers
|
||||||
|
import { InstanceProvider } from "./instance.provider";
|
||||||
|
import { StoreProvider } from "./store.provider";
|
||||||
|
import { ToastWithTheme } from "./toast";
|
||||||
|
import { UserProvider } from "./user.provider";
|
||||||
|
|
||||||
|
const DEFAULT_SWR_CONFIG = {
|
||||||
|
refreshWhenHidden: false,
|
||||||
|
revalidateIfStale: false,
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnMount: true,
|
||||||
|
refreshInterval: 600000,
|
||||||
|
errorRetryCount: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function InstanceLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
||||||
|
<ToastWithTheme />
|
||||||
|
<SWRConfig value={DEFAULT_SWR_CONFIG}>
|
||||||
|
<StoreProvider>
|
||||||
|
<InstanceProvider>
|
||||||
|
<UserProvider>{children}</UserProvider>
|
||||||
|
</InstanceProvider>
|
||||||
|
</StoreProvider>
|
||||||
|
</SWRConfig>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
apps/admin/app/(all)/toast.tsx
Normal file
10
apps/admin/app/(all)/toast.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { Toast } from "@plane/ui";
|
||||||
|
import { resolveGeneralTheme } from "@plane/utils";
|
||||||
|
|
||||||
|
export const ToastWithTheme = () => {
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
return <Toast theme={resolveGeneralTheme(resolvedTheme)} />;
|
||||||
|
};
|
||||||
|
|
@ -19,6 +19,7 @@ export const UserProvider: FC<IUserProvider> = observer(({ children }) => {
|
||||||
useSWR("CURRENT_USER", () => fetchCurrentUser(), {
|
useSWR("CURRENT_USER", () => fetchCurrentUser(), {
|
||||||
shouldRetryOnError: false,
|
shouldRetryOnError: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins());
|
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
39
apps/admin/app/layout.tsx
Normal file
39
apps/admin/app/layout.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
// plane imports
|
||||||
|
import { ADMIN_BASE_PATH } from "@plane/constants";
|
||||||
|
// styles
|
||||||
|
import "@/styles/globals.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Plane | Simple, extensible, open-source project management tool.",
|
||||||
|
description:
|
||||||
|
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.",
|
||||||
|
openGraph: {
|
||||||
|
title: "Plane | Simple, extensible, open-source project management tool.",
|
||||||
|
description:
|
||||||
|
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.",
|
||||||
|
url: "https://plane.so/",
|
||||||
|
},
|
||||||
|
keywords:
|
||||||
|
"software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration",
|
||||||
|
twitter: {
|
||||||
|
site: "@planepowers",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||||
|
const ASSET_PREFIX = ADMIN_BASE_PATH;
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href={`${ASSET_PREFIX}/favicon/apple-touch-icon.png`} />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href={`${ASSET_PREFIX}/favicon/favicon-32x32.png`} />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href={`${ASSET_PREFIX}/favicon/favicon-16x16.png`} />
|
||||||
|
<link rel="manifest" href={`${ASSET_PREFIX}/site.webmanifest.json`} />
|
||||||
|
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
|
||||||
|
</head>
|
||||||
|
<body className={`antialiased`}>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
121
apps/admin/ce/components/authentication/authentication-modes.tsx
Normal file
121
apps/admin/ce/components/authentication/authentication-modes.tsx
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { KeyRound, Mails } from "lucide-react";
|
||||||
|
// types
|
||||||
|
import {
|
||||||
|
TGetBaseAuthenticationModeProps,
|
||||||
|
TInstanceAuthenticationMethodKeys,
|
||||||
|
TInstanceAuthenticationModes,
|
||||||
|
} from "@plane/types";
|
||||||
|
import { resolveGeneralTheme } from "@plane/utils";
|
||||||
|
// components
|
||||||
|
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||||
|
import { EmailCodesConfiguration } from "@/components/authentication/email-config-switch";
|
||||||
|
import { GithubConfiguration } from "@/components/authentication/github-config";
|
||||||
|
import { GitlabConfiguration } from "@/components/authentication/gitlab-config";
|
||||||
|
import { GoogleConfiguration } from "@/components/authentication/google-config";
|
||||||
|
import { PasswordLoginConfiguration } from "@/components/authentication/password-config-switch";
|
||||||
|
// plane admin components
|
||||||
|
import { UpgradeButton } from "@/plane-admin/components/common";
|
||||||
|
// assets
|
||||||
|
import githubLightModeImage from "@/public/logos/github-black.png";
|
||||||
|
import githubDarkModeImage from "@/public/logos/github-white.png";
|
||||||
|
import GitlabLogo from "@/public/logos/gitlab-logo.svg";
|
||||||
|
import GoogleLogo from "@/public/logos/google-logo.svg";
|
||||||
|
import OIDCLogo from "@/public/logos/oidc-logo.svg";
|
||||||
|
import SAMLLogo from "@/public/logos/saml-logo.svg";
|
||||||
|
|
||||||
|
export type TAuthenticationModeProps = {
|
||||||
|
disabled: boolean;
|
||||||
|
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Authentication methods
|
||||||
|
export const getAuthenticationModes: (props: TGetBaseAuthenticationModeProps) => TInstanceAuthenticationModes[] = ({
|
||||||
|
disabled,
|
||||||
|
updateConfig,
|
||||||
|
resolvedTheme,
|
||||||
|
}) => [
|
||||||
|
{
|
||||||
|
key: "unique-codes",
|
||||||
|
name: "Unique codes",
|
||||||
|
description:
|
||||||
|
"Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
|
||||||
|
icon: <Mails className="h-6 w-6 p-0.5 text-custom-text-300/80" />,
|
||||||
|
config: <EmailCodesConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "passwords-login",
|
||||||
|
name: "Passwords",
|
||||||
|
description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
|
||||||
|
icon: <KeyRound className="h-6 w-6 p-0.5 text-custom-text-300/80" />,
|
||||||
|
config: <PasswordLoginConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "google",
|
||||||
|
name: "Google",
|
||||||
|
description: "Allow members to log in or sign up for Plane with their Google accounts.",
|
||||||
|
icon: <Image src={GoogleLogo} height={20} width={20} alt="Google Logo" />,
|
||||||
|
config: <GoogleConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "github",
|
||||||
|
name: "GitHub",
|
||||||
|
description: "Allow members to log in or sign up for Plane with their GitHub accounts.",
|
||||||
|
icon: (
|
||||||
|
<Image
|
||||||
|
src={resolveGeneralTheme(resolvedTheme) === "dark" ? githubDarkModeImage : githubLightModeImage}
|
||||||
|
height={20}
|
||||||
|
width={20}
|
||||||
|
alt="GitHub Logo"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
config: <GithubConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "gitlab",
|
||||||
|
name: "GitLab",
|
||||||
|
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
|
||||||
|
icon: <Image src={GitlabLogo} height={20} width={20} alt="GitLab Logo" />,
|
||||||
|
config: <GitlabConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "oidc",
|
||||||
|
name: "OIDC",
|
||||||
|
description: "Authenticate your users via the OpenID Connect protocol.",
|
||||||
|
icon: <Image src={OIDCLogo} height={22} width={22} alt="OIDC Logo" />,
|
||||||
|
config: <UpgradeButton />,
|
||||||
|
unavailable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "saml",
|
||||||
|
name: "SAML",
|
||||||
|
description: "Authenticate your users via the Security Assertion Markup Language protocol.",
|
||||||
|
icon: <Image src={SAMLLogo} height={22} width={22} alt="SAML Logo" className="pl-0.5" />,
|
||||||
|
config: <UpgradeButton />,
|
||||||
|
unavailable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const AuthenticationModes: React.FC<TAuthenticationModeProps> = observer((props) => {
|
||||||
|
const { disabled, updateConfig } = props;
|
||||||
|
// next-themes
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{getAuthenticationModes({ disabled, updateConfig, resolvedTheme }).map((method) => (
|
||||||
|
<AuthenticationMethodCard
|
||||||
|
key={method.key}
|
||||||
|
name={method.name}
|
||||||
|
description={method.description}
|
||||||
|
icon={method.icon}
|
||||||
|
config={method.config}
|
||||||
|
disabled={disabled}
|
||||||
|
unavailable={method.unavailable}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue