mirror of
https://github.com/plankanban/planka.git
synced 2026-05-04 18:00:55 +03:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91eb43f472 | ||
|
|
f47c188331 | ||
|
|
38a45c66e3 | ||
|
|
c342d2edd7 | ||
|
|
95e479cd3a | ||
|
|
d9ae02899d | ||
|
|
5e6195b252 | ||
|
|
61a3ff55cc | ||
|
|
af0cd79535 | ||
|
|
bb907d62e4 | ||
|
|
7604a31a74 | ||
|
|
2685e5d5fc | ||
|
|
5ebe320396 | ||
|
|
66ff3b65c6 | ||
|
|
d5d3f1de44 | ||
|
|
4e9e842e3d | ||
|
|
605dcace54 | ||
|
|
61753f08eb | ||
|
|
52c96c6c8f | ||
|
|
2a1760393f | ||
|
|
7758312e05 | ||
|
|
414418130d | ||
|
|
d83ea4b146 | ||
|
|
addad4378a | ||
|
|
b9967feeea | ||
|
|
dbcdd62bdf | ||
|
|
ff4177f27a | ||
|
|
f68e7d156e |
@@ -16,4 +16,8 @@ server/views/index.ejs
|
||||
server/data/*
|
||||
!server/data/.gitkeep
|
||||
|
||||
server/terms/*
|
||||
!server/terms/_template
|
||||
!server/terms/cloud
|
||||
|
||||
client/dist
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
working-directory: ./client
|
||||
|
||||
- name: Build client
|
||||
run: DISABLE_ESLINT_PLUGIN=true npm run build
|
||||
run: INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build
|
||||
working-directory: ./client
|
||||
|
||||
- name: Include licenses into dist
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: Include built client into dist
|
||||
run: |
|
||||
mv ../../client/dist/* public
|
||||
cp public/index.html views
|
||||
mv public/index.ejs views
|
||||
working-directory: ./server/dist
|
||||
|
||||
- name: Create release package
|
||||
|
||||
@@ -24,13 +24,13 @@ jobs:
|
||||
|
||||
- name: Build client
|
||||
run: |
|
||||
DISABLE_ESLINT_PLUGIN=true npm run build
|
||||
INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build
|
||||
mv dist build
|
||||
working-directory: ./client
|
||||
|
||||
- name: Update Dockerfile to use prebuilt client
|
||||
run: |
|
||||
sed -i '/^FROM node:22 AS client/,/^ && DISABLE_ESLINT_PLUGIN=true npm run build$/c\
|
||||
sed -i '/^FROM node:22 AS client/,/^ && INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build$/c\
|
||||
FROM node:22 AS client\n\
|
||||
WORKDIR /app\n\
|
||||
COPY client/build /app/dist' Dockerfile
|
||||
|
||||
@@ -38,13 +38,13 @@ jobs:
|
||||
|
||||
- name: Build client
|
||||
run: |
|
||||
DISABLE_ESLINT_PLUGIN=true npm run build
|
||||
INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build
|
||||
mv dist build
|
||||
working-directory: ./client
|
||||
|
||||
- name: Update Dockerfile to use prebuilt client
|
||||
run: |
|
||||
sed -i '/^FROM node:22 AS client/,/^ && DISABLE_ESLINT_PLUGIN=true npm run build$/c\
|
||||
sed -i '/^FROM node:22 AS client/,/^ && INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build$/c\
|
||||
FROM node:22 AS client\n\
|
||||
WORKDIR /app\n\
|
||||
COPY client/build /app/dist' Dockerfile
|
||||
|
||||
4
.github/workflows/build-and-test.yml
vendored
4
.github/workflows/build-and-test.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
run: |
|
||||
npm install
|
||||
cd client
|
||||
npm run build
|
||||
INDEX_FORMAT=ejs npm run build
|
||||
|
||||
- name: Set up and start server for testing
|
||||
env:
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Seed database with terms signature
|
||||
run: |
|
||||
TERMS_SIGNATURE=$(sha256sum terms/self-hosted/en-US.md | awk '{print $1}')
|
||||
TERMS_SIGNATURE=$(sha256sum terms/_template/en-US.md | awk '{print $1}')
|
||||
PGPASSWORD=$POSTGRES_PASSWORD psql -h localhost -U $POSTGRES_USERNAME -d $POSTGRES_DATABASE -c "UPDATE user_account SET terms_signature = '$TERMS_SIGNATURE';"
|
||||
working-directory: ./server
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ COPY client .
|
||||
|
||||
RUN npm install npm --global \
|
||||
&& npm install --omit=dev \
|
||||
&& DISABLE_ESLINT_PLUGIN=true npm run build
|
||||
&& INDEX_FORMAT=ejs DISABLE_ESLINT_PLUGIN=true npm run build
|
||||
|
||||
# Stage 3: Final image
|
||||
FROM node:22-alpine
|
||||
@@ -41,12 +41,12 @@ COPY --from=server --chown=node:node /app/node_modules node_modules
|
||||
COPY --from=server --chown=node:node /app/dist .
|
||||
|
||||
COPY --from=client --chown=node:node /app/dist public
|
||||
COPY --from=client --chown=node:node /app/dist/index.html views
|
||||
|
||||
RUN python3 -m venv .venv \
|
||||
&& .venv/bin/pip3 install --upgrade pip \
|
||||
&& .venv/bin/pip3 install -r requirements.txt --no-cache-dir \
|
||||
&& mv .env.sample .env \
|
||||
&& mv public/index.ejs views \
|
||||
&& npm config set update-notifier false
|
||||
|
||||
VOLUME /app/data
|
||||
|
||||
49
README.md
49
README.md
@@ -1,23 +1,27 @@
|
||||
# PLANKA
|
||||
<div align="center">
|
||||
|
||||
**Project mastering driven by fun**
|
||||

|
||||
|
||||
 [](https://github.com/plankanban/planka/pkgs/container/planka) [](https://github.com/plankanban/planka/graphs/contributors) [](https://discord.gg/WqqYNd7Jvt)
|
||||
# PLANKA
|
||||
|
||||

|
||||
_Project mastering driven by fun_
|
||||
|
||||
[**Client demo**](https://plankanban.github.io/planka) (without server features).
|
||||
 [](https://github.com/plankanban/planka/pkgs/container/planka) [](https://github.com/plankanban/planka/graphs/contributors) [](https://discord.gg/WqqYNd7Jvt)
|
||||
|
||||
> ⚠️ The demo GIF and client demo are based on **v1** and will be updated soon.
|
||||
[Install](https://docs.planka.cloud/docs/installation/docker/production-version/) · [Demo](https://planka.app) · [Docs](https://docs.planka.cloud/docs/welcome/) · [API](https://plankanban.github.io/planka/swagger-ui/) · [Cloud](https://planka.app/pricing) · [Pro version](https://planka.app/pro)
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Collaborative Kanban Boards**: Create projects, boards, lists, cards, and manage tasks with an intuitive drag-and-drop interface
|
||||
- **Real-Time Updates**: Instant syncing across all users, no refresh needed
|
||||
- **Rich Markdown Support**: Write beautifully formatted card descriptions with a powerful markdown editor
|
||||
- **Flexible Notifications**: Get alerts through 100+ providers, fully customizable to your workflow
|
||||
- **Seamless Authentication**: Single sign-on with OpenID Connect integration
|
||||
- **Multilingual & Easy to Translate**: Full internationalization support for a global audience
|
||||
- **Collaborative Kanban Boards:** Create projects, boards, lists, cards, and manage tasks with an intuitive drag-and-drop interface
|
||||
- **Real-Time Updates:** Instant syncing across all users, no refresh needed
|
||||
- **Rich Markdown Support:** Write beautifully formatted card descriptions with a powerful markdown editor
|
||||
- **Flexible Notifications:** Get alerts through 100+ providers, fully customizable to your workflow
|
||||
- **Seamless Authentication:** Single sign-on with OpenID Connect integration
|
||||
- **Multilingual & Easy to Translate:** Full internationalization support for a global audience
|
||||
|
||||
## How to Deploy
|
||||
|
||||
@@ -25,20 +29,17 @@ PLANKA is easy to install using multiple methods - learn more in the [installati
|
||||
|
||||
For configuration and environment settings, see the [configuration section](https://docs.planka.cloud/docs/category/configuration/).
|
||||
|
||||
## Notes App Testing
|
||||
Interested in a hosted or [Pro version](https://planka.app/pro) of PLANKA? Check out the pricing on our [website](https://planka.app/pricing).
|
||||
|
||||
The Notes app testing version is available across multiple platforms.
|
||||
## Notes App
|
||||
|
||||
If you have an iOS device, you can join the TestFlight to try the app: [TestFlight](https://testflight.apple.com/join/5eJqTaJW).
|
||||
A testing version of the Notes app is now available on multiple platforms:
|
||||
|
||||
For Windows and Android, you can find the app here: [PLANKA Notes](https://planka-notes.hillerdaniel.de).
|
||||
|
||||
> ⚠️ The Notes app has currently been tested only with PLANKA v2.
|
||||
- **iOS:** Join the [TestFlight](https://testflight.apple.com/join/5eJqTaJW) to try the app
|
||||
- **Windows & Android:** Download the app [here](https://planka-notes.hillerdaniel.de)
|
||||
|
||||
## Contact
|
||||
|
||||
Interested in a hosted version of PLANKA? Email us at [github@planka.group](mailto:github@planka.group).
|
||||
|
||||
For any security issues, please do not create a public issue on GitHub - instead, report it privately by emailing [security@planka.group](mailto:security@planka.group).
|
||||
|
||||
**Note:** We do NOT offer any public support via email, please use GitHub.
|
||||
@@ -49,10 +50,10 @@ For any security issues, please do not create a public issue on GitHub - instead
|
||||
|
||||
PLANKA is [fair-code](https://faircode.io) distributed under the [Fair Use License](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Community%20License%20EN.md) and [PLANKA Pro/Enterprise License](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20Commercial%20License%20EN.md).
|
||||
|
||||
- **Source Available**: The source code is always visible
|
||||
- **Self-Hostable**: Deploy and host it anywhere
|
||||
- **Extensible**: Customize with your own functionality
|
||||
- **Enterprise Licenses**: Available for additional features and support
|
||||
- **Source Available:** The source code is always visible
|
||||
- **Self-Hostable:** Deploy and host it anywhere
|
||||
- **Extensible:** Customize with your own functionality
|
||||
- **Enterprise Licenses:** Available for additional features and support
|
||||
|
||||
For more details, check the [License Guide](https://github.com/plankanban/planka/blob/master/LICENSES/PLANKA%20License%20Guide%20EN.md).
|
||||
|
||||
|
||||
BIN
assets/demo.gif
BIN
assets/demo.gif
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.6 MiB |
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -15,13 +15,13 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 2.0.0
|
||||
version: 2.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "2.0.0"
|
||||
appVersion: "2.1.0"
|
||||
|
||||
dependencies:
|
||||
- alias: postgresql
|
||||
|
||||
@@ -68,7 +68,7 @@ helm install planka . --set secretkey=$SECRETKEY \
|
||||
|
||||
or create a values.yaml file like:
|
||||
|
||||
```yaml
|
||||
````yaml
|
||||
secretkey: "<InsertSecretKey>"
|
||||
# The admin section needs to be present for new instances of PLANKA, after the first start you can remove the lines starting with admin_. If you want the admin user to be unchangeable admin_email: has to stay
|
||||
# After changing the config you have to run ```helm upgrade planka . -f values.yaml```
|
||||
@@ -89,11 +89,11 @@ ingress:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
|
||||
# Needed for HTTPS
|
||||
# Needed for HTTPS
|
||||
tls:
|
||||
- secretName: planka-tls # existing TLS secret in k8s
|
||||
hosts:
|
||||
- planka.example.dev
|
||||
- secretName: planka-tls # existing TLS secret in k8s
|
||||
hosts:
|
||||
- planka.example.dev
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -135,14 +135,14 @@ extraMounts:
|
||||
subPath: ca.crt
|
||||
readOnly: true
|
||||
configMap:
|
||||
name: ca-certificates # Must exist
|
||||
name: ca-certificates # Must exist
|
||||
|
||||
# Mount TLS certificates from existing Secret
|
||||
- name: tls-certs
|
||||
mountPath: /etc/ssl/private
|
||||
readOnly: true
|
||||
secret:
|
||||
secretName: planka-tls-secret # Must exist
|
||||
secretName: planka-tls-secret # Must exist
|
||||
items:
|
||||
- key: tls.crt
|
||||
path: server.crt
|
||||
@@ -178,11 +178,13 @@ extraMounts:
|
||||
A common use case is configuring OIDC with a self-hosted Keycloak instance that uses custom CA certificates.
|
||||
|
||||
First, create the CA certificate ConfigMap:
|
||||
|
||||
```bash
|
||||
kubectl create configmap ca-certificates --from-file=ca.crt=/path/to/your/ca.crt
|
||||
```
|
||||
|
||||
Then configure the chart:
|
||||
|
||||
```yaml
|
||||
# Mount custom CA certificate from existing ConfigMap
|
||||
extraMounts:
|
||||
@@ -225,6 +227,90 @@ extraEnv:
|
||||
key: api-key
|
||||
```
|
||||
|
||||
### Custom Terms of Service
|
||||
|
||||
You can provide your own End User Terms of Service by passing the markdown files directly via `values.yaml` in the `terms` configuration block. This automates the creation of a corresponding ConfigMap and volume mount.
|
||||
|
||||
```yaml
|
||||
terms:
|
||||
enabled: true
|
||||
customFiles:
|
||||
en-US.md: |
|
||||
# End User Terms of Service
|
||||
...
|
||||
[confirmations]::
|
||||
---
|
||||
✔️ **I have read and accept these End User Terms of Service**
|
||||
de-DE.md: |
|
||||
# Nutzungsbedingungen
|
||||
...
|
||||
[confirmations]::
|
||||
---
|
||||
✔️ **Ich habe diese Nutzungsbedingungen gelesen und akzeptiere sie**
|
||||
```
|
||||
|
||||
### Image Digest Pinning
|
||||
|
||||
For enhanced security and reproducibility, you can pin the container image using its SHA256 digest instead of relying solely on tags. This ensures you always deploy the exact same image, preventing tag mutations or accidental updates.
|
||||
|
||||
#### Finding the Image Digest
|
||||
|
||||
You can find the digest of a specific image tag using:
|
||||
|
||||
```bash
|
||||
docker inspect ghcr.io/plankanban/planka:latest --format='{{index .RepoDigests 0}}'
|
||||
# Output: ghcr.io/plankanban/planka@sha256:abc123def456...
|
||||
|
||||
# Or with skopeo
|
||||
skopeo inspect docker://ghcr.io/plankanban/planka:latest
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
You can use digest pinning in several ways:
|
||||
|
||||
**Option 1: Digest with tag (recommended)**
|
||||
|
||||
Includes the tag for reference while using the digest for verification:
|
||||
|
||||
```bash
|
||||
helm install planka . --set secretkey=$SECRETKEY \
|
||||
--set image.tag=latest \
|
||||
--set image.digest=abc123def456... \
|
||||
--set admin_email="demo@demo.demo" \
|
||||
--set admin_password="demo" \
|
||||
--set admin_name="Demo Demo" \
|
||||
--set admin_username="demo"
|
||||
```
|
||||
|
||||
Or in values.yaml:
|
||||
|
||||
```yaml
|
||||
image:
|
||||
repository: ghcr.io/plankanban/planka
|
||||
tag: latest
|
||||
digest: "abc123def456ab89cd12ef34ab56cd78ef90ab12cd34ef56ab78cd90ef12ab34"
|
||||
```
|
||||
|
||||
**Option 2: Digest only**
|
||||
|
||||
If you prefer to pin only by digest without specifying a tag:
|
||||
|
||||
```yaml
|
||||
image:
|
||||
repository: ghcr.io/plankanban/planka
|
||||
tag: "" # Empty - digest alone identifies the image
|
||||
digest: "abc123def456ab89cd12ef34ab56cd78ef90ab12cd34ef56ab78cd90ef12ab34"
|
||||
```
|
||||
|
||||
#### Security Benefits
|
||||
|
||||
- **Immutability**: Ensures you always deploy the exact same image
|
||||
- **Supply Chain Security**: Protects against tag mutations or registry compromise
|
||||
- **Reproducibility**: Makes deployments fully reproducible across environments
|
||||
- **Audit Trail**: Provides clear image identity in deployment manifests
|
||||
|
||||
### Complete Example
|
||||
|
||||
See `values-example.yaml` for a comprehensive example that demonstrates all the advanced features including OIDC configuration with custom CA certificates.
|
||||
````
|
||||
|
||||
13
charts/planka/templates/configmap-terms.yaml
Normal file
13
charts/planka/templates/configmap-terms.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
{{- if .Values.terms.enabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "planka.fullname" . }}-terms
|
||||
labels:
|
||||
{{- include "planka.labels" . | nindent 4 }}
|
||||
data:
|
||||
{{- range $key, $value := .Values.terms.customFiles }}
|
||||
{{ $key }}: |
|
||||
{{- $value | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -39,7 +39,16 @@ spec:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- $imageTag := .Values.image.tag | default .Chart.AppVersion }}
|
||||
{{- if .Values.image.digest }}
|
||||
{{- if $imageTag }}
|
||||
image: "{{ .Values.image.repository }}:{{ $imageTag }}@sha256:{{ .Values.image.digest }}"
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}@sha256:{{ .Values.image.digest }}"
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:{{ $imageTag }}"
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
@@ -61,6 +70,16 @@ spec:
|
||||
- mountPath: /app/logs
|
||||
subPath: app-logs
|
||||
name: emptydir
|
||||
- mountPath: /app/.tmp
|
||||
subPath: app-tmp
|
||||
name: emptydir
|
||||
- mountPath: /tmp
|
||||
subPath: tmp
|
||||
name: emptydir
|
||||
{{- end }}
|
||||
{{- if .Values.terms.enabled }}
|
||||
- mountPath: /app/terms/custom
|
||||
name: planka-terms
|
||||
{{- end }}
|
||||
{{- /* Extra volume mounts */}}
|
||||
{{- range .Values.extraMounts }}
|
||||
@@ -207,6 +226,11 @@ spec:
|
||||
{{- if .Values.securityContext.readOnlyRootFilesystem }}
|
||||
- name: emptydir
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.terms.enabled }}
|
||||
- name: planka-terms
|
||||
configMap:
|
||||
name: {{ include "planka.fullname" . }}-terms
|
||||
{{- end }}
|
||||
{{- /* Extra volumes */}}
|
||||
{{- range .Values.extraMounts }}
|
||||
|
||||
@@ -9,6 +9,10 @@ image:
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
# Optional: specify the image digest for pinning by SHA256
|
||||
# When set, the image reference will include the digest for enhanced security
|
||||
# Example: "abc123def456..." (without sha256: prefix)
|
||||
digest: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
@@ -244,6 +248,22 @@ extraEnv: []
|
||||
## - name: SMTP_FROM
|
||||
## value: "your_email@example.com"
|
||||
|
||||
## End User Terms of Service configuration
|
||||
## Mount custom terms of service markdown files into the Planka deployment
|
||||
##
|
||||
terms:
|
||||
enabled: false
|
||||
# Provide individual language files as key-value pairs
|
||||
# e.g.,
|
||||
# customFiles:
|
||||
# en-US.md: |
|
||||
# # End User Terms of Service
|
||||
# ...
|
||||
# de-DE.md: |
|
||||
# # Nutzungsbedingungen
|
||||
# ...
|
||||
customFiles: {}
|
||||
|
||||
## Extra volume mounts configuration
|
||||
## Mount ConfigMaps, Secrets, and arbitrary volumes to the PLANKA container
|
||||
## This allows mounting any pre-existing ConfigMaps, Secrets, or other volume types
|
||||
|
||||
2563
client/package-lock.json
generated
2563
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -79,9 +79,6 @@
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"@diplodoc/transform": {
|
||||
"lodash": "^4.17.23"
|
||||
},
|
||||
"react-mentions": {
|
||||
"@babel/runtime": "^7.28.6"
|
||||
}
|
||||
@@ -89,12 +86,12 @@
|
||||
"dependencies": {
|
||||
"@ballerina/highlightjs-ballerina": "^1.0.1",
|
||||
"@diplodoc/cut-extension": "^1.1.1",
|
||||
"@diplodoc/transform": "^4.64.1",
|
||||
"@diplodoc/transform": "^4.70.2",
|
||||
"@gravity-ui/components": "^4.18.0",
|
||||
"@gravity-ui/markdown-editor": "^15.31.0",
|
||||
"@gravity-ui/uikit": "^7.31.1",
|
||||
"@gravity-ui/markdown-editor": "^15.35.1",
|
||||
"@gravity-ui/uikit": "^7.34.0",
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@vitejs/plugin-react": "^5.1.3",
|
||||
"@vitejs/plugin-react": "^5.2.0",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"classnames": "^2.5.1",
|
||||
"date-fns": "^4.1.0",
|
||||
@@ -123,10 +120,10 @@
|
||||
"highlightjs-zenscript": "^2.0.0",
|
||||
"hightlightjs-papyrus": "^0.0.4",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^25.8.1",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next": "^25.8.18",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"initials": "^3.1.2",
|
||||
"javascript-time-ago": "^2.6.2",
|
||||
"javascript-time-ago": "^2.6.4",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"linkify-react": "^4.3.2",
|
||||
@@ -134,7 +131,7 @@
|
||||
"lodash": "^4.17.23",
|
||||
"lowlight": "^3.3.0",
|
||||
"markdown-it": "^13.0.2",
|
||||
"nanoid": "^5.1.6",
|
||||
"nanoid": "^5.1.7",
|
||||
"papaparse": "^5.5.3",
|
||||
"patch-package": "^8.0.1",
|
||||
"photoswipe": "^5.4.4",
|
||||
@@ -143,25 +140,25 @@
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-datepicker": "^9.1.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-dropzone": "^14.4.0",
|
||||
"react-dropzone": "^15.0.0",
|
||||
"react-frame-component": "^5.2.7",
|
||||
"react-hot-toast": "^2.6.0",
|
||||
"react-i18next": "^16.5.4",
|
||||
"react-i18next": "^16.5.8",
|
||||
"react-input-mask": "^2.0.4",
|
||||
"react-intersection-observer": "^10.0.2",
|
||||
"react-intersection-observer": "^10.0.3",
|
||||
"react-mentions": "^4.4.10",
|
||||
"react-photoswipe-gallery": "^4.0.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router": "^7.13.0",
|
||||
"react-router": "^7.13.1",
|
||||
"react-textarea-autosize": "^8.5.9",
|
||||
"react-time-ago": "^7.4.1",
|
||||
"react-time-ago": "^7.4.4",
|
||||
"redux": "^5.0.1",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-orm": "^0.16.2",
|
||||
"redux-saga": "^1.4.2",
|
||||
"reselect": "^5.1.1",
|
||||
"sails.io.js": "^1.2.1",
|
||||
"sass-embedded": "^1.97.3",
|
||||
"sass-embedded": "^1.98.0",
|
||||
"semantic-ui-react": "^2.1.5",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"validator": "^13.15.26",
|
||||
@@ -174,10 +171,10 @@
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.28.6",
|
||||
"@babel/preset-env": "^7.29.0",
|
||||
"@cucumber/cucumber": "^12.6.0",
|
||||
"@cucumber/pretty-formatter": "^3.0.0",
|
||||
"@playwright/test": "^1.58.1",
|
||||
"babel-jest": "^30.2.0",
|
||||
"@cucumber/cucumber": "^12.7.0",
|
||||
"@cucumber/pretty-formatter": "^3.2.0",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"babel-jest": "^30.3.0",
|
||||
"babel-preset-airbnb": "^5.0.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
@@ -187,8 +184,8 @@
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"jest": "^30.2.0",
|
||||
"playwright": "^1.58.0",
|
||||
"jest": "^30.3.0",
|
||||
"playwright": "^1.58.2",
|
||||
"prettier": "3.8.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
diff --git a/node_modules/@diplodoc/transform/lib/md.js b/node_modules/@diplodoc/transform/lib/md.js
|
||||
index a2d222e..8d1377e 100644
|
||||
index c9faa96..e4bef9b 100644
|
||||
--- a/node_modules/@diplodoc/transform/lib/md.js
|
||||
+++ b/node_modules/@diplodoc/transform/lib/md.js
|
||||
@@ -101,8 +101,12 @@ function initPlugins(md, options, pluginOptions) {
|
||||
@@ -107,8 +107,12 @@ function initPlugins(md, options, pluginOptions) {
|
||||
}
|
||||
md.use(ol_attr_conversion_1.olAttrConversion);
|
||||
plugins.forEach((plugin) => md.use(plugin, pluginOptions));
|
||||
@@ -1,8 +1,8 @@
|
||||
diff --git a/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js b/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js
|
||||
index dec01f0..a80b857 100644
|
||||
index c0d13c3..4c6e4e9 100644
|
||||
--- a/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js
|
||||
+++ b/node_modules/@gravity-ui/markdown-editor/build/esm/bundle/wysiwyg-preset.js
|
||||
@@ -102,7 +102,6 @@ export const BundlePreset = (builder, opts) => {
|
||||
@@ -107,7 +107,6 @@ export const BundlePreset = (builder, opts) => {
|
||||
enableNewImageSizeCalculation: opts.enableNewImageSizeCalculation,
|
||||
...opts.imgSize,
|
||||
},
|
||||
@@ -10,7 +10,7 @@ index dec01f0..a80b857 100644
|
||||
deflist: {
|
||||
deflistTermPlaceholder: () => i18nPlaceholder('deflist_term'),
|
||||
deflistDescPlaceholder: () => i18nPlaceholder('deflist_desc'),
|
||||
@@ -123,11 +122,6 @@ export const BundlePreset = (builder, opts) => {
|
||||
@@ -128,11 +127,6 @@ export const BundlePreset = (builder, opts) => {
|
||||
...opts.yfmTable,
|
||||
controls: opts.mobile ? false : opts.yfmTable?.controls,
|
||||
},
|
||||
@@ -36,6 +36,23 @@ index 8aefe20..99e59e3 100644
|
||||
}
|
||||
if (options.pmTransformers) {
|
||||
this.#pmTransformers = options.pmTransformers;
|
||||
diff --git a/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/markdown/CodeBlock/CodeBlockHighlight/TooltipPlugin/index.js b/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/markdown/CodeBlock/CodeBlockHighlight/TooltipPlugin/index.js
|
||||
index 5eec9bb..3abd31a 100644
|
||||
--- a/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/markdown/CodeBlock/CodeBlockHighlight/TooltipPlugin/index.js
|
||||
+++ b/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/markdown/CodeBlock/CodeBlockHighlight/TooltipPlugin/index.js
|
||||
@@ -75,12 +75,6 @@ export const codeLangSelectTooltipViewCreator = (view, langItems, mapping = {},
|
||||
dispatch: view.dispatch,
|
||||
}),
|
||||
},
|
||||
- {
|
||||
- id: 'code-block-copy',
|
||||
- type: ToolbarDataType.ReactNodeFn,
|
||||
- width: 28,
|
||||
- content: () => _jsx(ClipboardButton, { text: node.textContent }),
|
||||
- },
|
||||
].filter(isTruthy),
|
||||
[
|
||||
{
|
||||
diff --git a/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/yfm/YfmNote/YfmNoteSpecs/index.js b/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/yfm/YfmNote/YfmNoteSpecs/index.js
|
||||
index 212c583..b709383 100644
|
||||
--- a/node_modules/@gravity-ui/markdown-editor/build/esm/extensions/yfm/YfmNote/YfmNoteSpecs/index.js
|
||||
@@ -18,7 +18,7 @@ const http = {};
|
||||
return result;
|
||||
}, new FormData());
|
||||
|
||||
return fetch(`${Config.SERVER_BASE_URL}/api${url}`, {
|
||||
return fetch(`${Config.BASE_PATH}/api${url}`, {
|
||||
method,
|
||||
headers,
|
||||
body: formData,
|
||||
|
||||
@@ -10,7 +10,7 @@ import Config from '../constants/Config';
|
||||
|
||||
const io = sailsIOClient(socketIOClient);
|
||||
|
||||
io.sails.url = Config.SERVER_BASE_URL;
|
||||
io.sails.path = `${Config.BASE_PATH}/socket.io`;
|
||||
io.sails.autoConnect = false;
|
||||
io.sails.reconnection = true;
|
||||
io.sails.useCORSRouteToGetCookie = false;
|
||||
@@ -30,8 +30,8 @@ socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
|
||||
headers,
|
||||
url: `/api${url}`,
|
||||
},
|
||||
(_, { body, error }) => {
|
||||
if (error) {
|
||||
(body, { error }) => {
|
||||
if (body instanceof Error || error) {
|
||||
reject(body);
|
||||
} else {
|
||||
resolve(body);
|
||||
|
||||
@@ -1,3 +1,34 @@
|
||||
# [2.1.0] - 2026-03-19
|
||||
|
||||
### Added
|
||||
|
||||
* Support running under subpath
|
||||
* Add ability to display card ages
|
||||
* Allow exposing Swagger specification
|
||||
* Configurable HTTP timeout for OIDC
|
||||
|
||||
## [2.0.3] - 2026-03-01
|
||||
|
||||
### Fixed
|
||||
|
||||
* Improve security by ensuring the outgoing proxy is not accessible from outside
|
||||
|
||||
## [2.0.2] - 2026-02-23
|
||||
|
||||
### Fixed
|
||||
|
||||
* Prevent dropzone from overflowing content
|
||||
* Update Gravatar hash algorithm
|
||||
* Improve backup and restore scripts
|
||||
* Improve installation on Windows and containerized environments
|
||||
|
||||
## [2.0.1] - 2026-02-17
|
||||
|
||||
### Fixed
|
||||
|
||||
* Improve connection reliability after the app is idle
|
||||
* Allow loading custom End User Terms of Service
|
||||
|
||||
## [2.0.0] - 2026-02-11
|
||||
|
||||
### Added
|
||||
|
||||
@@ -43,6 +43,14 @@ const Others = React.memo(() => {
|
||||
className={styles.radio}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Radio
|
||||
toggle
|
||||
name="displayCardAges"
|
||||
checked={board.displayCardAges}
|
||||
label={t('common.displayCardAges')}
|
||||
className={styles.radio}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Radio
|
||||
toggle
|
||||
name="expandTaskListsByDefault"
|
||||
|
||||
@@ -17,6 +17,7 @@ import { BoardMembershipRoles, BoardViews } from '../../../constants/Enums';
|
||||
import TaskList from './TaskList';
|
||||
import DueDateChip from '../DueDateChip';
|
||||
import StopwatchChip from '../StopwatchChip';
|
||||
import TimeAgo from '../../common/TimeAgo';
|
||||
import UserAvatar from '../../users/UserAvatar';
|
||||
import LabelChip from '../../labels/LabelChip';
|
||||
import CustomFieldValueChip from '../../custom-field-values/CustomFieldValueChip';
|
||||
@@ -75,12 +76,13 @@ const ProjectContent = React.memo(({ cardId }) => {
|
||||
return attachment && attachment.data.thumbnailUrls.outside360;
|
||||
});
|
||||
|
||||
const { listName, withCreator } = useSelector((state) => {
|
||||
const { listName, withCreator, withAge } = useSelector((state) => {
|
||||
const board = selectors.selectCurrentBoard(state);
|
||||
|
||||
return {
|
||||
listName: list.name && (board.view === BoardViews.KANBAN ? null : list.name),
|
||||
withCreator: board.alwaysDisplayCardCreator,
|
||||
withAge: board.displayCardAges,
|
||||
};
|
||||
}, shallowEqual);
|
||||
|
||||
@@ -115,6 +117,7 @@ const ProjectContent = React.memo(({ cardId }) => {
|
||||
card.dueDate ||
|
||||
card.stopwatch ||
|
||||
card.commentsTotal > 0 ||
|
||||
withAge ||
|
||||
attachmentsTotal > 0 ||
|
||||
notificationsTotal > 0 ||
|
||||
listName;
|
||||
@@ -236,6 +239,14 @@ const ProjectContent = React.memo(({ cardId }) => {
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{withAge && card.createdAt && (
|
||||
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
||||
<span className={styles.attachmentContent}>
|
||||
<Icon name="history" />
|
||||
<TimeAgo date={card.createdAt} />
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{!isCompact && usersNode}
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { Icon } from 'semantic-ui-react';
|
||||
|
||||
import selectors from '../../../selectors';
|
||||
import markdownToText from '../../../utils/markdown-to-text';
|
||||
import { BoardViews } from '../../../constants/Enums';
|
||||
import TimeAgo from '../../common/TimeAgo';
|
||||
import LabelChip from '../../labels/LabelChip';
|
||||
import CustomFieldValueChip from '../../custom-field-values/CustomFieldValueChip';
|
||||
|
||||
@@ -52,19 +53,14 @@ const StoryContent = React.memo(({ cardId }) => {
|
||||
selectNotificationsTotalByCardId(state, cardId),
|
||||
);
|
||||
|
||||
const listName = useSelector((state) => {
|
||||
if (!list.name) {
|
||||
return null;
|
||||
}
|
||||
const { listName, withAge } = useSelector((state) => {
|
||||
const board = selectors.selectCurrentBoard(state);
|
||||
|
||||
const { view } = selectors.selectCurrentBoard(state);
|
||||
|
||||
if (view === BoardViews.KANBAN) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return list.name;
|
||||
});
|
||||
return {
|
||||
listName: list.name && (board.view === BoardViews.KANBAN ? null : list.name),
|
||||
withAge: board.displayCardAges,
|
||||
};
|
||||
}, shallowEqual);
|
||||
|
||||
const coverUrl = useSelector((state) => {
|
||||
const attachment = selectAttachmentById(state, card.coverAttachmentId);
|
||||
@@ -109,7 +105,7 @@ const StoryContent = React.memo(({ cardId }) => {
|
||||
{card.name}
|
||||
</div>
|
||||
{card.description && <div className={styles.descriptionText}>{descriptionText}</div>}
|
||||
{(attachmentsTotal > 0 || notificationsTotal > 0 || listName) && (
|
||||
{(withAge || attachmentsTotal > 0 || notificationsTotal > 0 || listName) && (
|
||||
<span className={styles.attachments}>
|
||||
{notificationsTotal > 0 && (
|
||||
<span
|
||||
@@ -138,6 +134,14 @@ const StoryContent = React.memo(({ cardId }) => {
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{withAge && card.createdAt && (
|
||||
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
||||
<span className={styles.attachmentContent}>
|
||||
<Icon name="history" />
|
||||
<TimeAgo date={card.createdAt} />
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -132,7 +132,11 @@ const AddAttachmentZone = React.memo(({ children }) => {
|
||||
<>
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<div {...getRootProps()}>
|
||||
{isDragActive && <div className={styles.dropzone}>{t('common.dropFileToUpload')}</div>}
|
||||
{isDragActive && (
|
||||
<div className={styles.dropzone}>
|
||||
<div className={styles.dropzoneText}>{t('common.dropFileToUpload')}</div>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<input {...getInputProps()} />
|
||||
|
||||
@@ -8,13 +8,18 @@
|
||||
background: white;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
height: 100%;
|
||||
inset: 0;
|
||||
line-height: 30px;
|
||||
opacity: 0.7;
|
||||
padding: 200px 50px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: 2001;
|
||||
}
|
||||
|
||||
.dropzoneText {
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: min(200px, 50%);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useSelector } from 'react-redux';
|
||||
import history from '../../../history';
|
||||
import selectors from '../../../selectors';
|
||||
import matchPaths from '../../../utils/match-paths';
|
||||
import Config from '../../../constants/Config';
|
||||
import Paths from '../../../constants/Paths';
|
||||
|
||||
const Link = React.memo(({ href, content, stopPropagation, ...props }) => {
|
||||
@@ -23,7 +24,8 @@ const Link = React.memo(({ href, content, stopPropagation, ...props }) => {
|
||||
}
|
||||
}, [href]);
|
||||
|
||||
const isSameSite = !!url && url.origin === window.location.origin;
|
||||
const isSameSite =
|
||||
!!url && url.origin === window.location.origin && url.pathname.startsWith(Config.BASE_PATH);
|
||||
|
||||
const cardsPathMatch = useMemo(() => {
|
||||
if (!isSameSite) {
|
||||
|
||||
@@ -11,15 +11,12 @@ import { Button, Checkbox, Dropdown, Modal, Segment } from 'semantic-ui-react';
|
||||
import selectors from '../../../selectors';
|
||||
import entryActions from '../../../entry-actions';
|
||||
import { localeByLanguage } from '../../../locales';
|
||||
import TERMS_LANGUAGES from '../../../constants/TermsLanguages';
|
||||
import Markdown from '../Markdown';
|
||||
|
||||
import styles from './TermsModal.module.scss';
|
||||
|
||||
const LOCALES = TERMS_LANGUAGES.map((language) => localeByLanguage[language]);
|
||||
|
||||
const splitTermsAndConfirmations = (content) => {
|
||||
const separator = '\n---\n';
|
||||
const separator = '\n[confirmations]::\n---\n';
|
||||
const index = content.lastIndexOf(separator);
|
||||
|
||||
if (index === -1) {
|
||||
@@ -38,6 +35,8 @@ const splitTermsAndConfirmations = (content) => {
|
||||
};
|
||||
|
||||
const TermsModal = React.memo(() => {
|
||||
const { termsLanguages } = useSelector(selectors.selectBootstrap);
|
||||
|
||||
const {
|
||||
termsForm: { payload: terms, isSubmitting, isCancelling, isLanguageUpdating },
|
||||
} = useSelector(selectors.selectAuthenticateForm);
|
||||
@@ -46,6 +45,19 @@ const TermsModal = React.memo(() => {
|
||||
const [t] = useTranslation();
|
||||
const [acceptedConfirmationsSet, setAcceptedConfirmationsSet] = useState(new Set());
|
||||
|
||||
const locales = useMemo(
|
||||
() =>
|
||||
termsLanguages.map(
|
||||
(language) =>
|
||||
localeByLanguage[language] || {
|
||||
language,
|
||||
country: language.split('-')[1]?.toLowerCase(),
|
||||
name: language,
|
||||
},
|
||||
),
|
||||
[termsLanguages],
|
||||
);
|
||||
|
||||
const [content, confirmations] = useMemo(
|
||||
() => splitTermsAndConfirmations(terms.content),
|
||||
[terms.content],
|
||||
@@ -88,7 +100,7 @@ const TermsModal = React.memo(() => {
|
||||
<Dropdown
|
||||
fluid
|
||||
selection
|
||||
options={LOCALES.map((locale) => ({
|
||||
options={locales.map((locale) => ({
|
||||
value: locale.language,
|
||||
flag: locale.country,
|
||||
text: locale.name,
|
||||
|
||||
@@ -68,7 +68,11 @@ const AddImageZone = React.memo(({ children, onCreate }) => {
|
||||
return (
|
||||
/* eslint-disable-next-line react/jsx-props-no-spreading */
|
||||
<div {...getRootProps()}>
|
||||
{isDragActive && <div className={styles.dropzone}>{t('common.dropFileToUpload')}</div>}
|
||||
{isDragActive && (
|
||||
<div className={styles.dropzone}>
|
||||
<div className={styles.dropzoneText}>{t('common.dropFileToUpload')}</div>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<input {...getInputProps()} />
|
||||
|
||||
@@ -8,13 +8,18 @@
|
||||
background: white;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
height: 100%;
|
||||
inset: 0;
|
||||
line-height: 30px;
|
||||
opacity: 0.7;
|
||||
padding-top: 200px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: 2001;
|
||||
}
|
||||
|
||||
.dropzoneText {
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: min(200px, 50%);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import history from '../../history';
|
||||
import Config from '../../constants/Config';
|
||||
|
||||
const SAME_SITE_CLASS = 'same-site';
|
||||
|
||||
@@ -30,7 +31,9 @@ function process(token, nextToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSameSite = url.origin === window.location.origin;
|
||||
const isSameSite =
|
||||
url.origin === window.location.origin && url.pathname.startsWith(Config.BASE_PATH);
|
||||
|
||||
const trimOrigin = isSameSite && nextToken.type === 'text' && nextToken.content === href;
|
||||
|
||||
if (isSameSite) {
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const SERVER_BASE_URL =
|
||||
import.meta.env.VITE_SERVER_BASE_URL || (import.meta.env.DEV ? 'http://localhost:1337' : '');
|
||||
const BASE_PATH = window.BASE_PATH || '';
|
||||
|
||||
const ACCESS_TOKEN_KEY = 'accessToken';
|
||||
const ACCESS_TOKEN_VERSION_KEY = 'accessTokenVersion';
|
||||
@@ -20,7 +19,7 @@ const MAX_SIZE_TO_DISPLAY_CONTENT = 256 * 1024;
|
||||
const IS_MAC = navigator.platform.startsWith('Mac');
|
||||
|
||||
export default {
|
||||
SERVER_BASE_URL,
|
||||
BASE_PATH,
|
||||
ACCESS_TOKEN_KEY,
|
||||
ACCESS_TOKEN_VERSION_KEY,
|
||||
ACCESS_TOKEN_VERSION,
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const ROOT = '/';
|
||||
const LOGIN = '/login';
|
||||
const OIDC_CALLBACK = '/oidc-callback';
|
||||
const PROJECTS = '/projects/:id';
|
||||
const BOARDS = '/boards/:id';
|
||||
const CARDS = '/cards/:id';
|
||||
import Config from './Config';
|
||||
|
||||
const ROOT = `${Config.BASE_PATH}/`;
|
||||
const LOGIN = `${Config.BASE_PATH}/login`;
|
||||
const OIDC_CALLBACK = `${Config.BASE_PATH}/oidc-callback`;
|
||||
const PROJECTS = `${Config.BASE_PATH}/projects/:id`;
|
||||
const BOARDS = `${Config.BASE_PATH}/boards/:id`;
|
||||
const CARDS = `${Config.BASE_PATH}/cards/:id`;
|
||||
|
||||
export default {
|
||||
ROOT,
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
export default ['de-DE', 'en-US'];
|
||||
@@ -166,6 +166,7 @@ export default {
|
||||
deletedUser_title: 'مستخدم محذوف',
|
||||
description: 'الوصف',
|
||||
display: 'عرض',
|
||||
displayCardAges: 'عرض أعمار البطاقات',
|
||||
dropFileToUpload: 'أفلت الملف لرفعه',
|
||||
dueDate_title: 'تاريخ الاستحقاق',
|
||||
dynamicAndUnevenlySpacedLayout: 'تخطيط ديناميكي وغير متساوي المسافات.',
|
||||
|
||||
@@ -178,6 +178,7 @@ export default {
|
||||
deletedUser_title: 'Изтрит потребител',
|
||||
description: 'Описание',
|
||||
display: 'Показване',
|
||||
displayCardAges: 'Показвай възрастта на картите',
|
||||
dropFileToUpload: 'Пуснете файл за качване',
|
||||
dueDate_title: 'Краен срок',
|
||||
dynamicAndUnevenlySpacedLayout: 'Динамично и неравномерно разположение.',
|
||||
|
||||
@@ -177,6 +177,7 @@ export default {
|
||||
deletedUser_title: 'Usuari eliminat',
|
||||
description: 'Descripció',
|
||||
display: 'Mostrar',
|
||||
displayCardAges: "Mostrar l'antiguitat de les targetes",
|
||||
dropFileToUpload: 'Arrossega fitxer per pujar-lo',
|
||||
dueDate_title: 'Data de venciment',
|
||||
dynamicAndUnevenlySpacedLayout: 'Disseny dinàmic i amb espaiat irregular.',
|
||||
|
||||
@@ -169,6 +169,7 @@ export default {
|
||||
deletedUser_title: 'Smazaný uživatel',
|
||||
description: 'Popis',
|
||||
display: 'Zobrazit',
|
||||
displayCardAges: 'Zobrazit stáří karet',
|
||||
dropFileToUpload: 'Přetažením nahrát soubor',
|
||||
dueDate_title: 'Termín',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dynamické a nerovnoměrné rozložení.',
|
||||
|
||||
@@ -173,6 +173,7 @@ export default {
|
||||
deletedUser_title: 'Slettet bruger',
|
||||
description: 'Beskrivelse',
|
||||
display: 'Vis',
|
||||
displayCardAges: 'Vis kortalder',
|
||||
dropFileToUpload: 'Slip fil for at uploade',
|
||||
dueDate_title: 'Frist',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dynamisk og ujævnt fordelt layout.',
|
||||
|
||||
@@ -188,6 +188,7 @@ export default {
|
||||
deletedUser_title: 'Gelöschter Benutzer',
|
||||
description: 'Beschreibung',
|
||||
display: 'Anzeige',
|
||||
displayCardAges: 'Kartenalter anzeigen',
|
||||
dropFileToUpload: 'Datei für Upload ablegen',
|
||||
dueDate_title: 'Fälligkeitsdatum',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dynamisches und ungleichmäßig verteiltes Layout.',
|
||||
|
||||
@@ -186,6 +186,7 @@ export default {
|
||||
deletedUser_title: 'Διαγραμμένος χρήστης',
|
||||
description: 'Περιγραφή',
|
||||
display: 'Εμφάνιση',
|
||||
displayCardAges: 'Εμφάνιση ηλικίας καρτών',
|
||||
dropFileToUpload: 'Σύρετε το αρχείο για μεταφόρτωση',
|
||||
dueDate_title: 'Ημερομηνία λήξης',
|
||||
dynamicAndUnevenlySpacedLayout: 'Δυναμική και άνισα κατανεμημένη διάταξη.',
|
||||
|
||||
@@ -172,6 +172,7 @@ export default {
|
||||
deletedUser_title: 'Deleted User',
|
||||
description: 'Description',
|
||||
display: 'Display',
|
||||
displayCardAges: 'Display card ages',
|
||||
dropFileToUpload: 'Drop file to upload',
|
||||
dueDate_title: 'Due Date',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dynamic and unevenly spaced layout.',
|
||||
|
||||
@@ -167,6 +167,7 @@ export default {
|
||||
deletedUser_title: 'Deleted User',
|
||||
description: 'Description',
|
||||
display: 'Display',
|
||||
displayCardAges: 'Display card ages',
|
||||
dropFileToUpload: 'Drop file to upload',
|
||||
dueDate_title: 'Due Date',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dynamic and unevenly spaced layout.',
|
||||
|
||||
@@ -178,6 +178,7 @@ export default {
|
||||
deletedUser_title: 'Usuario eliminado',
|
||||
description: 'Descripción',
|
||||
display: 'Mostrar',
|
||||
displayCardAges: 'Mostrar antigüedad de las tarjetas',
|
||||
dropFileToUpload: 'Arrastra archivo para subir',
|
||||
dueDate_title: 'Fecha de vencimiento',
|
||||
dynamicAndUnevenlySpacedLayout: 'Diseño dinámico y con espaciado irregular.',
|
||||
|
||||
@@ -173,6 +173,7 @@ export default {
|
||||
deletedUser_title: 'Kustutatud kasutaja',
|
||||
description: 'Kirjeldus',
|
||||
display: 'Kuva',
|
||||
displayCardAges: 'Näita kaartide vanust',
|
||||
dropFileToUpload: 'Lase faili üleslaadida',
|
||||
dueDate_title: 'Tähtaeg',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dünaamiline ja eirastega kujundus.',
|
||||
|
||||
@@ -176,6 +176,7 @@ export default {
|
||||
deletedUser_title: 'کاربر حذف شده',
|
||||
description: 'توضیحات',
|
||||
display: 'نمایش',
|
||||
displayCardAges: 'نمایش سن کارتها',
|
||||
dropFileToUpload: 'فایل را برای آپلود بکشید',
|
||||
dueDate_title: 'تاریخ سررسید',
|
||||
dynamicAndUnevenlySpacedLayout: 'طرحبندی پویا و نامتقارن.',
|
||||
|
||||
@@ -169,6 +169,7 @@ export default {
|
||||
deletedUser_title: 'Poistettu käyttäjä',
|
||||
description: 'Kuvaus',
|
||||
display: 'Näyttö',
|
||||
displayCardAges: 'Näytä korttien ikä',
|
||||
dropFileToUpload: 'Pudota tiedosto ladattavaksi',
|
||||
dueDate_title: 'Määräpäivä',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dynaaminen ja epätasaisesti jaettu asettelu.',
|
||||
|
||||
@@ -177,6 +177,7 @@ export default {
|
||||
deletedUser_title: 'Utilisateur supprimé',
|
||||
description: 'Description',
|
||||
display: 'Affichage',
|
||||
displayCardAges: "Afficher l'âge des cartes",
|
||||
dropFileToUpload: 'Déposer le fichier à télécharger',
|
||||
dueDate_title: "Date d'échéance",
|
||||
dynamicAndUnevenlySpacedLayout: 'Mise en page dynamique et inégalement espacée.',
|
||||
|
||||
@@ -167,6 +167,7 @@ export default {
|
||||
deletedUser_title: 'Törölt felhasználó',
|
||||
description: 'Leírás',
|
||||
display: 'Megjelenítés',
|
||||
displayCardAges: 'Kártyák korának megjelenítése',
|
||||
dropFileToUpload: 'Dobja ide a fájlt a feltöltéshez',
|
||||
dueDate_title: 'Esedékesség dátuma',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dinamikus és asszimetrikus kiosztás.',
|
||||
|
||||
@@ -174,6 +174,7 @@ export default {
|
||||
deletedUser_title: 'Pengguna yang dihapus',
|
||||
description: 'Deskripsi',
|
||||
display: 'Tampilan',
|
||||
displayCardAges: 'Tampilkan usia kartu',
|
||||
dropFileToUpload: 'Tarik berkas untuk menggungah',
|
||||
dueDate_title: 'Tenggat waktu',
|
||||
dynamicAndUnevenlySpacedLayout: 'Tata letak dinamis dan tidak merata.',
|
||||
|
||||
@@ -174,6 +174,7 @@ export default {
|
||||
deletedUser_title: 'Utente eliminato',
|
||||
description: 'Descrizione',
|
||||
display: 'Mostra',
|
||||
displayCardAges: 'Mostra età delle schede',
|
||||
dropFileToUpload: 'Trascina il file da caricare',
|
||||
dueDate_title: 'Data di scadenza',
|
||||
dynamicAndUnevenlySpacedLayout: 'Layout dinamico e irregolarmente distribuito.',
|
||||
|
||||
@@ -170,6 +170,7 @@ export default {
|
||||
deletedUser_title: '削除されたユーザー',
|
||||
description: '説明',
|
||||
display: '表示',
|
||||
displayCardAges: 'カードの経過時間を表示',
|
||||
dropFileToUpload: 'ファイルをドロップしてアップロード',
|
||||
dueDate_title: '期限',
|
||||
dynamicAndUnevenlySpacedLayout: '動的で不均等な間隔のレイアウト。',
|
||||
|
||||
@@ -164,6 +164,7 @@ export default {
|
||||
deletedUser_title: '삭제된 사용자',
|
||||
description: '설명',
|
||||
display: '표시',
|
||||
displayCardAges: '카드 경과 시간 표시',
|
||||
dropFileToUpload: '업로드할 파일을 드롭하세요',
|
||||
dueDate_title: '마감일',
|
||||
dynamicAndUnevenlySpacedLayout: '동적이고 불균등한 간격의 레이아웃.',
|
||||
|
||||
@@ -173,6 +173,7 @@ export default {
|
||||
deletedUser_title: 'Verwijderde gebruiker',
|
||||
description: 'Beschrijving',
|
||||
display: 'Weergave',
|
||||
displayCardAges: 'Kaartleeftijd weergeven',
|
||||
dropFileToUpload: 'Sleep bestand om te uploaden',
|
||||
dueDate_title: 'Vervaldatum',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dynamische en ongelijk verdeelde indeling.',
|
||||
|
||||
@@ -172,6 +172,7 @@ export default {
|
||||
deletedUser_title: 'Usunięty użytkownik',
|
||||
description: 'Opis',
|
||||
display: 'Wyświetlanie',
|
||||
displayCardAges: 'Pokazuj wiek kart',
|
||||
dropFileToUpload: 'Upuść plik aby wgrać',
|
||||
dueDate_title: 'Termin',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dynamiczny i nierówny układ.',
|
||||
|
||||
@@ -175,6 +175,7 @@ export default {
|
||||
deletedUser_title: 'Usuário excluído',
|
||||
description: 'Descrição',
|
||||
display: 'Exibir',
|
||||
displayCardAges: 'Exibir idade dos cartões',
|
||||
dropFileToUpload: 'Solte o arquivo para enviar',
|
||||
dueDate_title: 'Data de vencimento',
|
||||
dynamicAndUnevenlySpacedLayout: 'Layout dinâmico e desigualmente espaçado.',
|
||||
|
||||
@@ -177,6 +177,7 @@ export default {
|
||||
deletedUser_title: 'Utilizador eliminado',
|
||||
description: 'Descrição',
|
||||
display: 'Exibir',
|
||||
displayCardAges: 'Mostrar idade dos cartões',
|
||||
dropFileToUpload: 'Largue o ficheiro para carregar',
|
||||
dueDate_title: 'Data de vencimento',
|
||||
dynamicAndUnevenlySpacedLayout: 'Layout dinâmico e espaçamento irregular.',
|
||||
|
||||
@@ -171,6 +171,7 @@ export default {
|
||||
deletedUser_title: 'Utilizator șters',
|
||||
description: 'Descriere',
|
||||
display: 'Afișare',
|
||||
displayCardAges: 'Afișează vârsta cardurilor',
|
||||
dropFileToUpload: 'Aruncă fișierul pentru a încărca',
|
||||
dueDate_title: 'Data scadentă',
|
||||
dynamicAndUnevenlySpacedLayout: 'Aspect dinamic și spațiat neuniform.',
|
||||
|
||||
@@ -174,6 +174,7 @@ export default {
|
||||
deletedUser_title: 'Удалённый пользователь',
|
||||
description: 'Описание',
|
||||
display: 'Отображение',
|
||||
displayCardAges: 'Отображать возраст карточек',
|
||||
dropFileToUpload: 'Перетяните файл, чтобы загрузить',
|
||||
dueDate_title: 'Срок исполнения',
|
||||
dynamicAndUnevenlySpacedLayout: 'Динамичное и неравномерно распределённое расположение.',
|
||||
|
||||
@@ -168,6 +168,7 @@ export default {
|
||||
deletedUser_title: 'Zmazaný používateľ',
|
||||
description: 'Popis',
|
||||
display: 'Zobraziť',
|
||||
displayCardAges: 'Zobraziť vek kariet',
|
||||
dropFileToUpload: 'Potiahnutím nahraj súbor',
|
||||
dueDate_title: 'Termín do',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dynamické a nerovnomerne rozložené usporiadanie.',
|
||||
|
||||
@@ -171,6 +171,7 @@ export default {
|
||||
deletedUser_title: 'Обрисан корисник',
|
||||
description: 'Опис',
|
||||
display: 'Приказ',
|
||||
displayCardAges: 'Прикажи старост картица',
|
||||
dropFileToUpload: 'Превуци датотеку за слање',
|
||||
dueDate_title: 'Рок',
|
||||
dynamicAndUnevenlySpacedLayout: 'Динамички и неравномерно распоређен изглед.',
|
||||
|
||||
@@ -172,6 +172,7 @@ export default {
|
||||
deletedUser_title: 'Obrisan korisnik',
|
||||
description: 'Opis',
|
||||
display: 'Prikaz',
|
||||
displayCardAges: 'Prikaži starost kartica',
|
||||
dropFileToUpload: 'Prevuci datoteku za slanje',
|
||||
dueDate_title: 'Rok',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dinamički i neravnomerno raspoređen izgled.',
|
||||
|
||||
@@ -178,6 +178,7 @@ export default {
|
||||
deletedUser_title: 'Borttagen användare',
|
||||
description: 'Beskrivning',
|
||||
display: 'Visa',
|
||||
displayCardAges: 'Visa kortålder',
|
||||
dropFileToUpload: 'Släpp en fil för att ladda upp',
|
||||
dueDate_title: 'Förfallodatum',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dynamisk och ojämnt fördelad layout.',
|
||||
|
||||
@@ -174,6 +174,7 @@ export default {
|
||||
deletedUser_title: 'Silinmiş kullanıcı',
|
||||
description: 'açıklama',
|
||||
display: 'Görüntüle',
|
||||
displayCardAges: 'Kart yaşlarını göster',
|
||||
dropFileToUpload: 'Yüklenecek dosyayı buraya bırakın',
|
||||
dueDate_title: 'Termin tarihi',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dinamik ve düzensiz aralıklı düzen.',
|
||||
|
||||
@@ -173,6 +173,7 @@ export default {
|
||||
deletedUser_title: 'Видалений користувач',
|
||||
description: 'Опис',
|
||||
display: 'Дисплей',
|
||||
displayCardAges: 'Відображати вік карток',
|
||||
dropFileToUpload: 'Перетягніть файл для завантаження',
|
||||
dueDate_title: 'Крайній термін',
|
||||
dynamicAndUnevenlySpacedLayout: 'Динамічна та нерівномірна верстка.',
|
||||
|
||||
@@ -169,6 +169,7 @@ export default {
|
||||
deletedUser_title: "O'chirilgan foydalanuvchi",
|
||||
description: 'Tavsif',
|
||||
display: "Ko'rsatish",
|
||||
displayCardAges: "Kartalar yoshini ko'rsatish",
|
||||
dropFileToUpload: 'Faylni yuklash uchun qoldiring',
|
||||
dueDate_title: 'Muddati',
|
||||
dynamicAndUnevenlySpacedLayout: 'Dinamik va notekis joylashtirilgan tartib.',
|
||||
|
||||
@@ -173,6 +173,7 @@ export default {
|
||||
deletedUser_title: 'Người dùng đã bị xóa',
|
||||
description: 'Mô tả',
|
||||
display: 'Hiển thị',
|
||||
displayCardAges: 'Hiển thị tuổi thẻ',
|
||||
dropFileToUpload: 'Thả tệp vào đây để tải lên',
|
||||
dueDate_title: 'Hạn chót',
|
||||
dynamicAndUnevenlySpacedLayout: 'Bố cục động và khoảng cách không đều.',
|
||||
|
||||
@@ -153,6 +153,7 @@ export default {
|
||||
deletedUser_title: '已删除用户',
|
||||
description: '描述',
|
||||
display: '显示',
|
||||
displayCardAges: '显示卡片创建时间',
|
||||
dropFileToUpload: '拖放文件以上传',
|
||||
dueDate_title: '截止日期',
|
||||
dynamicAndUnevenlySpacedLayout: '动态非均匀间隔布局。',
|
||||
|
||||
@@ -153,6 +153,7 @@ export default {
|
||||
deletedUser_title: '已刪除的使用者',
|
||||
description: '描述',
|
||||
display: '顯示',
|
||||
displayCardAges: '顯示卡片建立時間',
|
||||
dropFileToUpload: '拖放文件以上傳',
|
||||
dueDate_title: '截止日期',
|
||||
dynamicAndUnevenlySpacedLayout: '動態不均勻間距佈局。',
|
||||
|
||||
@@ -31,6 +31,7 @@ export default class extends BaseModel {
|
||||
defaultCardType: attr(),
|
||||
limitCardTypesToDefaultOne: attr(),
|
||||
alwaysDisplayCardCreator: attr(),
|
||||
displayCardAges: attr(),
|
||||
expandTaskListsByDefault: attr(),
|
||||
context: attr(),
|
||||
view: attr(),
|
||||
|
||||
@@ -8,24 +8,28 @@ import { jwtDecode } from 'jwt-decode';
|
||||
|
||||
import Config from '../constants/Config';
|
||||
|
||||
const PATH = Config.BASE_PATH || '/';
|
||||
|
||||
export const setAccessToken = (accessToken) => {
|
||||
const { exp } = jwtDecode(accessToken);
|
||||
const expires = new Date(exp * 1000);
|
||||
|
||||
Cookies.set(Config.ACCESS_TOKEN_KEY, accessToken, {
|
||||
expires,
|
||||
path: PATH,
|
||||
secure: window.location.protocol === 'https:',
|
||||
sameSite: 'strict',
|
||||
});
|
||||
|
||||
Cookies.set(Config.ACCESS_TOKEN_VERSION_KEY, Config.ACCESS_TOKEN_VERSION, {
|
||||
expires,
|
||||
path: PATH,
|
||||
});
|
||||
};
|
||||
|
||||
export const removeAccessToken = () => {
|
||||
Cookies.remove(Config.ACCESS_TOKEN_KEY);
|
||||
Cookies.remove(Config.ACCESS_TOKEN_VERSION_KEY);
|
||||
Cookies.remove(Config.ACCESS_TOKEN_KEY, { path: PATH });
|
||||
Cookies.remove(Config.ACCESS_TOKEN_VERSION_KEY, { path: PATH });
|
||||
};
|
||||
|
||||
export const getAccessToken = () => {
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default '2.0.0';
|
||||
export default '2.1.0';
|
||||
|
||||
@@ -17,6 +17,6 @@ ln -s ${CLIENT_PATH}/logo512.png ${SERVER_PUBLIC_PATH}/logo512.png && echo "Link
|
||||
ln -s ${CLIENT_PATH}/manifest.json ${SERVER_PUBLIC_PATH}/manifest.json && echo "Linked manifest.json successfully"
|
||||
ln -s ${CLIENT_PATH}/robots.txt ${SERVER_PUBLIC_PATH}/robots.txt && echo "Linked robots.txt successfully"
|
||||
ln -s ${CLIENT_PATH}/assets ${SERVER_PUBLIC_PATH}/assets && echo "Linked assets folder successfully"
|
||||
ln -s ${CLIENT_PATH}/index.html ${SERVER_VIEWS_PATH}/index.html && echo "Linked index.html successfully"
|
||||
ln -s ${CLIENT_PATH}/index.ejs ${SERVER_VIEWS_PATH}/index.ejs && echo "Linked index.ejs successfully"
|
||||
|
||||
echo "Setup symbolic links completed successfully."
|
||||
|
||||
@@ -1,13 +1,42 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { defineConfig } from 'vite';
|
||||
import commonjs from 'vite-plugin-commonjs';
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import react from '@vitejs/plugin-react';
|
||||
import svgr from 'vite-plugin-svgr';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import browserslistToEsbuild from 'browserslist-to-esbuild';
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const createEjsTemplate = () => ({
|
||||
name: 'create-ejs-template',
|
||||
closeBundle() {
|
||||
if (process.env.INDEX_FORMAT !== 'ejs') return;
|
||||
|
||||
const distPath = path.resolve(__dirname, 'dist');
|
||||
const htmlPath = path.join(distPath, 'index.html');
|
||||
|
||||
if (!fs.existsSync(htmlPath)) return;
|
||||
|
||||
const html = fs.readFileSync(htmlPath, 'utf8');
|
||||
|
||||
const ejs = html
|
||||
.replace(/(href|src)="\.\/([^"]+)"/g, '$1="<%- basePath %>/$2"')
|
||||
.replace('</head>', " <script>window.BASE_PATH = '<%- basePath %>';</script>\n </head>");
|
||||
|
||||
fs.writeFileSync(path.join(distPath, 'index.ejs'), ejs);
|
||||
fs.unlinkSync(htmlPath);
|
||||
},
|
||||
});
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [
|
||||
commonjs(),
|
||||
nodePolyfills({
|
||||
@@ -15,6 +44,7 @@ export default defineConfig({
|
||||
}),
|
||||
react(),
|
||||
svgr(),
|
||||
createEjsTemplate(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -24,6 +54,10 @@ export default defineConfig({
|
||||
server: {
|
||||
port: 3000,
|
||||
open: true,
|
||||
proxy: {
|
||||
'/api': 'http://localhost:1337',
|
||||
'/socket.io': { target: 'http://localhost:1337', ws: true },
|
||||
},
|
||||
},
|
||||
build: {
|
||||
target: browserslistToEsbuild(['>0.2%', 'not dead', 'not op_mini all']),
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Stop on Error
|
||||
# Stop on error
|
||||
set -e
|
||||
|
||||
# Configure those to match your PLANKA Docker container names
|
||||
PLANKA_DOCKER_CONTAINER_POSTGRES="planka-postgres-1"
|
||||
PLANKA_DOCKER_CONTAINER_PLANKA="planka-planka-1"
|
||||
# Configure those to match your Docker container names
|
||||
DOCKER_CONTAINER_POSTGRES="planka-postgres-1"
|
||||
DOCKER_CONTAINER_PLANKA="planka-planka-1"
|
||||
|
||||
# Use provided directory or default to current directory
|
||||
BACKUP_DIR="${1:-$(pwd)}"
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "No backup directory specified, backing up to current directory: $BACKUP_DIR"
|
||||
else
|
||||
echo "Backing up to: $BACKUP_DIR"
|
||||
fi
|
||||
echo
|
||||
|
||||
# Create Temporary folder
|
||||
if date --version >/dev/null 2>&1; then
|
||||
# GNU date (Linux)
|
||||
BACKUP_DATETIME=$(date --utc +%FT%H-%M-%SZ)
|
||||
@@ -15,28 +24,30 @@ else
|
||||
# BSD date (macOS)
|
||||
BACKUP_DATETIME=$(date -u +%FT%H-%M-%SZ)
|
||||
fi
|
||||
mkdir -p "$BACKUP_DATETIME-backup"
|
||||
|
||||
# Dump DB into SQL File
|
||||
BACKUP_TEMP="$BACKUP_DIR/$BACKUP_DATETIME-backup"
|
||||
|
||||
# Create temporary directory
|
||||
mkdir -p "$BACKUP_TEMP"
|
||||
|
||||
echo -n "Exporting postgres database ... "
|
||||
docker exec -t "$PLANKA_DOCKER_CONTAINER_POSTGRES" pg_dumpall -c -U postgres > "$BACKUP_DATETIME-backup/postgres.sql"
|
||||
docker exec -t "$DOCKER_CONTAINER_POSTGRES" pg_dumpall -c -U postgres > "$BACKUP_TEMP/postgres.sql"
|
||||
echo "Success!"
|
||||
echo
|
||||
|
||||
# Export Docker Volume
|
||||
echo -n "Exporting data volume ... "
|
||||
docker run --rm --volumes-from "$PLANKA_DOCKER_CONTAINER_PLANKA" -v "$(pwd)/$BACKUP_DATETIME-backup:/backup" ubuntu cp -r /app/data /backup/data
|
||||
docker run --rm --volumes-from "$DOCKER_CONTAINER_PLANKA" -v "$BACKUP_TEMP:/backup" node:22-alpine cp -r /app/data /backup/data
|
||||
echo "Success!"
|
||||
echo
|
||||
|
||||
# Create tgz
|
||||
echo -n "Creating final tarball $BACKUP_DATETIME-backup.tgz ... "
|
||||
tar -czf "$BACKUP_DATETIME-backup.tgz" \
|
||||
"$BACKUP_DATETIME-backup/postgres.sql" \
|
||||
"$BACKUP_DATETIME-backup/data"
|
||||
tar -C "$BACKUP_DIR" -czf "$BACKUP_TEMP.tgz" "$BACKUP_DATETIME-backup"
|
||||
echo "Success!"
|
||||
echo
|
||||
|
||||
# Remove source files
|
||||
echo -n "Cleaning up temporary files and folders ... "
|
||||
rm -rf "$BACKUP_DATETIME-backup"
|
||||
echo -n "Cleaning up temporary files and directories ... "
|
||||
rm -rf "$BACKUP_TEMP"
|
||||
echo "Success!"
|
||||
echo
|
||||
|
||||
echo "Backup Complete!"
|
||||
|
||||
@@ -51,6 +51,9 @@ services:
|
||||
# will be sent through this proxy if set.
|
||||
# - OUTGOING_PROXY=http://proxy:3128
|
||||
|
||||
# Set to true to expose the Swagger specification at /swagger.json
|
||||
# - SWAGGER_EXPOSED=false
|
||||
|
||||
# - S3_ENDPOINT=
|
||||
# - S3_REGION=
|
||||
# - S3_ACCESS_KEY_ID=
|
||||
@@ -78,6 +81,7 @@ services:
|
||||
# - OIDC_IGNORE_USERNAME=true
|
||||
# - OIDC_IGNORE_ROLES=true
|
||||
# - OIDC_ENFORCED=true
|
||||
# - OIDC_TIMEOUT=3500
|
||||
# - OIDC_DEBUG=true
|
||||
|
||||
# Email Notifications (https://nodemailer.com/smtp/)
|
||||
|
||||
@@ -4,6 +4,7 @@ services:
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- data:/app/data
|
||||
# - ./terms:/app/terms/custom
|
||||
# Optionally override this to your user/group
|
||||
# user: 1000:1000
|
||||
# tmpfs:
|
||||
@@ -64,6 +65,9 @@ services:
|
||||
# which you can control via OUTGOING_BLOCKED_* and OUTGOING_ALLOWED_* below.
|
||||
# - OUTGOING_PROXY=http://proxy:3128
|
||||
|
||||
# Set to true to expose the Swagger specification at /swagger.json
|
||||
# - SWAGGER_EXPOSED=false
|
||||
|
||||
# - S3_ENDPOINT=
|
||||
# - S3_REGION=
|
||||
# - S3_ACCESS_KEY_ID=
|
||||
@@ -95,6 +99,7 @@ services:
|
||||
# - OIDC_IGNORE_USERNAME=true
|
||||
# - OIDC_IGNORE_ROLES=true
|
||||
# - OIDC_ENFORCED=true
|
||||
# - OIDC_TIMEOUT=3500
|
||||
# - OIDC_DEBUG=true
|
||||
|
||||
# Email Notifications (https://nodemailer.com/smtp/)
|
||||
|
||||
@@ -1,31 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Stop on Error
|
||||
# Stop on error
|
||||
set -e
|
||||
|
||||
# Configure those to match your PLANKA Docker container names
|
||||
PLANKA_DOCKER_CONTAINER_POSTGRES="planka-postgres-1"
|
||||
PLANKA_DOCKER_CONTAINER_PLANKA="planka-planka-1"
|
||||
# Configure those to match your Docker container names
|
||||
DOCKER_CONTAINER_POSTGRES="planka-postgres-1"
|
||||
DOCKER_CONTAINER_PLANKA="planka-planka-1"
|
||||
|
||||
# Extract tgz archive
|
||||
PLANKA_BACKUP_ARCHIVE_TGZ=$1
|
||||
PLANKA_BACKUP_ARCHIVE=$(basename "$PLANKA_BACKUP_ARCHIVE_TGZ" .tgz)
|
||||
echo -n "Extracting tarball $PLANKA_BACKUP_ARCHIVE_TGZ ... "
|
||||
tar -xzf "$PLANKA_BACKUP_ARCHIVE_TGZ"
|
||||
# Use provided archive
|
||||
BACKUP_ARCHIVE="$1"
|
||||
|
||||
if [ -z "$BACKUP_ARCHIVE" ]; then
|
||||
echo "Usage: $0 <backup-archive.tgz>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKUP_DIR=$(dirname "$BACKUP_ARCHIVE")
|
||||
BACKUP_TEMP="$BACKUP_DIR/$(basename "$BACKUP_ARCHIVE" .tgz)"
|
||||
|
||||
echo -n "Extracting tarball $BACKUP_ARCHIVE ... "
|
||||
tar -C "$BACKUP_DIR" -xzf "$BACKUP_ARCHIVE"
|
||||
echo "Success!"
|
||||
echo
|
||||
|
||||
# Import Database
|
||||
echo -n "Importing postgres database ... "
|
||||
cat "$PLANKA_BACKUP_ARCHIVE/postgres.sql" | docker exec -i "$PLANKA_DOCKER_CONTAINER_POSTGRES" psql -U postgres
|
||||
cat "$BACKUP_TEMP/postgres.sql" | docker exec -i "$DOCKER_CONTAINER_POSTGRES" psql -U postgres
|
||||
echo "Success!"
|
||||
echo
|
||||
|
||||
# Restore Docker Volume
|
||||
echo -n "Importing data volume ... "
|
||||
docker run --rm --volumes-from "$PLANKA_DOCKER_CONTAINER_PLANKA" -v "$(pwd)/$PLANKA_BACKUP_ARCHIVE:/backup" ubuntu cp -rf /backup/data/. /app/public/data
|
||||
docker run --rm --user root --volumes-from "$DOCKER_CONTAINER_PLANKA" -v "$BACKUP_TEMP:/backup" node:22-alpine sh -c "cp -rf /backup/data/. /app/data && chown -R node:node /app/data/*"
|
||||
echo "Success!"
|
||||
echo
|
||||
|
||||
echo -n "Cleaning up temporary files and folders ... "
|
||||
rm -r "$PLANKA_BACKUP_ARCHIVE"
|
||||
echo -n "Cleaning up temporary files and directories ... "
|
||||
rm -r "$BACKUP_TEMP"
|
||||
echo "Success!"
|
||||
echo
|
||||
|
||||
echo "Restore complete!"
|
||||
|
||||
192
package-lock.json
generated
192
package-lock.json
generated
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"name": "planka",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "planka",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"concurrently": "^9.2.1",
|
||||
"genversion": "^3.2.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.2.7"
|
||||
"lint-staged": "^16.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
|
||||
"integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
|
||||
"integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"environment": "^1.0.0"
|
||||
@@ -78,18 +78,6 @@
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -134,13 +122,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
|
||||
"integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz",
|
||||
"integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"slice-ansi": "^7.1.0",
|
||||
"string-width": "^8.0.0"
|
||||
"slice-ansi": "^8.0.0",
|
||||
"string-width": "^8.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
@@ -330,26 +318,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
|
||||
"integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"minimatch": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-package": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-package/-/find-package-1.0.0.tgz",
|
||||
@@ -386,9 +362,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
|
||||
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz",
|
||||
"integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -436,15 +412,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jake": {
|
||||
"version": "10.9.4",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
|
||||
@@ -463,18 +430,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged": {
|
||||
"version": "16.2.7",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz",
|
||||
"integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==",
|
||||
"version": "16.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz",
|
||||
"integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^14.0.2",
|
||||
"commander": "^14.0.3",
|
||||
"listr2": "^9.0.5",
|
||||
"micromatch": "^4.0.8",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"pidtree": "^0.6.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"string-argv": "^0.3.2",
|
||||
"yaml": "^2.8.1"
|
||||
"tinyexec": "^1.0.4",
|
||||
"yaml": "^2.8.2"
|
||||
},
|
||||
"bin": {
|
||||
"lint-staged": "bin/lint-staged.js"
|
||||
@@ -531,17 +497,32 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"node_modules/log-update/node_modules/ansi-styles": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/slice-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
"ansi-styles": "^6.2.1",
|
||||
"is-fullwidth-code-point": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-function": {
|
||||
@@ -557,9 +538,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"version": "5.1.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
|
||||
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
@@ -568,18 +549,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/nano-spawn": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz",
|
||||
"integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/nano-spawn?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
|
||||
@@ -620,29 +589,17 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pidtree": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
|
||||
"integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"pidtree": "bin/pidtree.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -708,16 +665,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz",
|
||||
"integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"is-fullwidth-code-point": "^5.0.0"
|
||||
"ansi-styles": "^6.2.3",
|
||||
"is-fullwidth-code-point": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
@@ -745,13 +702,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz",
|
||||
"integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==",
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz",
|
||||
"integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.3.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
"get-east-asian-width": "^1.5.0",
|
||||
"strip-ansi": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
@@ -761,12 +718,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
"ansi-regex": "^6.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -790,16 +747,13 @@
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz",
|
||||
"integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-kill": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planka",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"client:build": "npm run build --prefix client",
|
||||
@@ -39,6 +39,6 @@
|
||||
"concurrently": "^9.2.1",
|
||||
"genversion": "^3.2.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.2.7"
|
||||
"lint-staged": "^16.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,3 +23,7 @@ test
|
||||
views/index.ejs
|
||||
|
||||
data/*
|
||||
|
||||
terms/*
|
||||
!terms/_template
|
||||
!terms/cloud
|
||||
|
||||
@@ -42,6 +42,9 @@ SECRET_KEY=notsecretkey
|
||||
# will be sent through this proxy if set.
|
||||
# OUTGOING_PROXY=http://proxy:3128
|
||||
|
||||
# Set to true to expose the Swagger specification at /swagger.json
|
||||
# SWAGGER_EXPOSED=false
|
||||
|
||||
# S3_ENDPOINT=
|
||||
# S3_REGION=
|
||||
# S3_ACCESS_KEY_ID=
|
||||
@@ -69,6 +72,7 @@ SECRET_KEY=notsecretkey
|
||||
# OIDC_IGNORE_USERNAME=true
|
||||
# OIDC_IGNORE_ROLES=true
|
||||
# OIDC_ENFORCED=true
|
||||
# OIDC_TIMEOUT=3500
|
||||
# OIDC_DEBUG=true
|
||||
|
||||
# Email Notifications (https://nodemailer.com/smtp/)
|
||||
|
||||
4
server/.gitignore
vendored
4
server/.gitignore
vendored
@@ -138,3 +138,7 @@ views/index.ejs
|
||||
|
||||
data/*
|
||||
!data/.gitkeep
|
||||
|
||||
terms/*
|
||||
!terms/_template
|
||||
!terms/cloud
|
||||
|
||||
@@ -55,6 +55,10 @@
|
||||
* type: boolean
|
||||
* description: Whether to always display card creators
|
||||
* example: false
|
||||
* displayCardAges:
|
||||
* type: boolean
|
||||
* description: Whether to display card ages
|
||||
* example: false
|
||||
* expandTaskListsByDefault:
|
||||
* type: boolean
|
||||
* description: Whether to expand task lists by default
|
||||
@@ -120,6 +124,9 @@ module.exports = {
|
||||
alwaysDisplayCardCreator: {
|
||||
type: 'boolean',
|
||||
},
|
||||
displayCardAges: {
|
||||
type: 'boolean',
|
||||
},
|
||||
expandTaskListsByDefault: {
|
||||
type: 'boolean',
|
||||
},
|
||||
@@ -160,6 +167,7 @@ module.exports = {
|
||||
'defaultCardType',
|
||||
'limitCardTypesToDefaultOne',
|
||||
'alwaysDisplayCardCreator',
|
||||
'displayCardAges',
|
||||
'expandTaskListsByDefault',
|
||||
);
|
||||
}
|
||||
@@ -178,6 +186,7 @@ module.exports = {
|
||||
'defaultCardType',
|
||||
'limitCardTypesToDefaultOne',
|
||||
'alwaysDisplayCardCreator',
|
||||
'displayCardAges',
|
||||
'expandTaskListsByDefault',
|
||||
'isSubscribed',
|
||||
]);
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
* format: uri
|
||||
* description: URL to the customer management panel (conditionally added for admins if configured)
|
||||
* example: https://panel.example.com
|
||||
* termsLanguages:
|
||||
* type: array
|
||||
* description: List of available language codes for terms localization
|
||||
* items:
|
||||
* type: string
|
||||
* example: [de-DE, en-US]
|
||||
* version:
|
||||
* type: string
|
||||
* description: Current version of the PLANKA application
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /cards/{cardId}/custom-field-values/customFieldGroupId:{customFieldGroupId}:customFieldId:${customFieldId}:
|
||||
* /cards/{cardId}/custom-field-values/customFieldGroupId:{customFieldGroupId}:customFieldId:{customFieldId}:
|
||||
* patch:
|
||||
* summary: Create or update custom field value
|
||||
* description: Creates or updates a custom field value for a card. Requires board editor permissions.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /cards/{cardId}/custom-field-value/customFieldGroupId:{customFieldGroupId}:customFieldId:${customFieldId}:
|
||||
* /cards/{cardId}/custom-field-value/customFieldGroupId:{customFieldGroupId}:customFieldId:{customFieldId}:
|
||||
* delete:
|
||||
* summary: Delete custom field value
|
||||
* description: Deletes a custom field value for a specific card. Requires board editor permissions.
|
||||
|
||||
19
server/api/controllers/index.js
Normal file
19
server/api/controllers/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* Copyright (c) 2025 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
exits: {
|
||||
success: {
|
||||
responseType: 'view',
|
||||
viewTemplatePath: 'index',
|
||||
},
|
||||
},
|
||||
|
||||
fn() {
|
||||
return {
|
||||
basePath: sails.config.custom.baseUrlPath,
|
||||
};
|
||||
},
|
||||
};
|
||||
28
server/api/controllers/swagger/show.js
Normal file
28
server/api/controllers/swagger/show.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SWAGGER_PATH = path.join(sails.config.appPath, 'swagger.json');
|
||||
|
||||
module.exports = {
|
||||
async fn() {
|
||||
if (!sails.config.custom.swaggerExposed) {
|
||||
return this.res.notFound();
|
||||
}
|
||||
|
||||
let specification;
|
||||
try {
|
||||
const content = fs.readFileSync(SWAGGER_PATH, 'utf8');
|
||||
specification = JSON.parse(content);
|
||||
} catch (error) {
|
||||
sails.log.warn('swagger.json not found, run "npm run swagger:generate" to create it');
|
||||
return this.res.notFound();
|
||||
}
|
||||
|
||||
return specification;
|
||||
},
|
||||
};
|
||||
@@ -19,7 +19,6 @@
|
||||
* description: Language code for terms localization
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [de-DE, en-US]
|
||||
* example: en-US
|
||||
* responses:
|
||||
* 200:
|
||||
@@ -41,7 +40,6 @@
|
||||
* properties:
|
||||
* language:
|
||||
* type: string
|
||||
* enum: [de-DE, en-US]
|
||||
* description: Language code used
|
||||
* example: en-US
|
||||
* content:
|
||||
@@ -65,7 +63,6 @@ module.exports = {
|
||||
inputs: {
|
||||
language: {
|
||||
type: 'string',
|
||||
isIn: User.LANGUAGES,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ module.exports = {
|
||||
fn(inputs) {
|
||||
const data = {
|
||||
oidc: inputs.oidc,
|
||||
termsLanguages: sails.hooks.terms.getLanguages(),
|
||||
version: sails.config.custom.version,
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ module.exports = {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hash = crypto.createHash('md5').update(inputs.record.email).digest('hex');
|
||||
const hash = crypto.createHash('sha256').update(inputs.record.email).digest('hex');
|
||||
return `${sails.config.custom.gravatarBaseUrl}${hash}?s=180&d=initials`;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
|
||||
fn(inputs) {
|
||||
inputs.response.clearCookie('httpOnlyToken', {
|
||||
path: sails.config.custom.baseUrlPath,
|
||||
path: sails.config.custom.baseUrlPath || '/',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
*/
|
||||
|
||||
const { execFile } = require('child_process');
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
|
||||
const PYTHON_PATH =
|
||||
process.platform === 'win32'
|
||||
? path.join(sails.config.appPath, '.venv', 'Scripts', 'python.exe')
|
||||
: path.join(sails.config.appPath, '.venv', 'bin', 'python');
|
||||
|
||||
const promisifyExecFile = util.promisify(execFile);
|
||||
|
||||
module.exports = {
|
||||
@@ -27,9 +33,9 @@ module.exports = {
|
||||
async fn(inputs) {
|
||||
try {
|
||||
await promisifyExecFile(
|
||||
`${sails.config.appPath}/.venv/bin/python3`,
|
||||
PYTHON_PATH,
|
||||
[
|
||||
`${sails.config.appPath}/utils/send_notifications.py`,
|
||||
path.join(sails.config.appPath, 'utils', 'send_notifications.py'),
|
||||
JSON.stringify(inputs.services),
|
||||
inputs.title,
|
||||
JSON.stringify(inputs.bodyByFormat),
|
||||
|
||||
@@ -24,7 +24,7 @@ module.exports = {
|
||||
fn(inputs) {
|
||||
inputs.response.cookie('httpOnlyToken', inputs.value, {
|
||||
expires: new Date(inputs.accessTokenPayload.exp * 1000),
|
||||
path: sails.config.custom.baseUrlPath,
|
||||
path: sails.config.custom.baseUrlPath || '/',
|
||||
secure: sails.config.custom.baseUrlSecure,
|
||||
httpOnly: true,
|
||||
sameSite: 'strict',
|
||||
|
||||
@@ -45,6 +45,12 @@ module.exports = function defineOidcHook(sails) {
|
||||
clientInitPromise = (async () => {
|
||||
sails.log.info('Initializing OIDC client');
|
||||
|
||||
if (sails.config.custom.oidcTimeout !== null) {
|
||||
openidClient.custom.setHttpOptionsDefaults({
|
||||
timeout: sails.config.custom.oidcTimeout,
|
||||
});
|
||||
}
|
||||
|
||||
let issuer;
|
||||
try {
|
||||
issuer = await openidClient.Issuer.discover(sails.config.custom.oidcIssuer);
|
||||
|
||||
@@ -12,25 +12,37 @@
|
||||
*/
|
||||
|
||||
const fsPromises = require('fs').promises;
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const LANGUAGES = ['de-DE', 'en-US'];
|
||||
const DEFAULT_LANGUAGE = 'en-US';
|
||||
|
||||
const getContent = (language = DEFAULT_LANGUAGE) =>
|
||||
fsPromises.readFile(
|
||||
`${sails.config.appPath}/terms/${sails.config.custom.termsType}/${language}.md`,
|
||||
'utf8',
|
||||
);
|
||||
const PATH = path.join(sails.config.appPath, 'terms');
|
||||
const TEMPLATE_TYPE = '_template';
|
||||
|
||||
const hashContent = (content) => crypto.createHash('sha256').update(content).digest('hex');
|
||||
|
||||
module.exports = function defineTermsHook(sails) {
|
||||
let type;
|
||||
let languages;
|
||||
let defaultLanguage;
|
||||
let signature;
|
||||
|
||||
return {
|
||||
LANGUAGES,
|
||||
const getLanguages = async () => {
|
||||
const entries = await fsPromises.readdir(path.join(PATH, type), {
|
||||
withFileTypes: true,
|
||||
});
|
||||
|
||||
return entries
|
||||
.filter(
|
||||
(entry) => (entry.isFile() || entry.isSymbolicLink()) && path.extname(entry.name) === '.md',
|
||||
)
|
||||
.map((entry) => path.basename(entry.name, '.md'))
|
||||
.sort();
|
||||
};
|
||||
|
||||
const getContent = (language) =>
|
||||
fsPromises.readFile(path.join(PATH, type, `${language}.md`), 'utf8');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Runs when this Sails app loads/lifts.
|
||||
*/
|
||||
@@ -38,13 +50,32 @@ module.exports = function defineTermsHook(sails) {
|
||||
async initialize() {
|
||||
sails.log.info('Initializing custom hook (`terms`)');
|
||||
|
||||
const content = await getContent();
|
||||
type = sails.config.custom.termsType;
|
||||
|
||||
try {
|
||||
languages = await getLanguages();
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
if (!languages || languages.length === 0) {
|
||||
sails.log.warn('Custom terms not found, falling back to template');
|
||||
|
||||
type = TEMPLATE_TYPE;
|
||||
languages = await getLanguages();
|
||||
}
|
||||
|
||||
defaultLanguage = languages.includes(sails.config.i18n.defaultLocale)
|
||||
? sails.config.i18n.defaultLocale
|
||||
: languages[0];
|
||||
|
||||
const content = await getContent(defaultLanguage);
|
||||
signature = hashContent(content);
|
||||
},
|
||||
|
||||
async getPayload(language = DEFAULT_LANGUAGE) {
|
||||
if (!LANGUAGES.includes(language)) {
|
||||
language = DEFAULT_LANGUAGE; // eslint-disable-line no-param-reassign
|
||||
async getPayload(language) {
|
||||
if (!language || !languages.includes(language)) {
|
||||
language = defaultLanguage; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
const content = await getContent(language);
|
||||
@@ -56,6 +87,10 @@ module.exports = function defineTermsHook(sails) {
|
||||
};
|
||||
},
|
||||
|
||||
getLanguages() {
|
||||
return languages;
|
||||
},
|
||||
|
||||
isSignatureValid(value) {
|
||||
return value === signature;
|
||||
},
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
* - defaultCardType
|
||||
* - limitCardTypesToDefaultOne
|
||||
* - alwaysDisplayCardCreator
|
||||
* - displayCardAges
|
||||
* - expandTaskListsByDefault
|
||||
* - createdAt
|
||||
* - updatedAt
|
||||
@@ -67,6 +68,11 @@
|
||||
* default: false
|
||||
* description: Whether to always display the card creator
|
||||
* example: false
|
||||
* displayCardAges:
|
||||
* type: boolean
|
||||
* default: false
|
||||
* description: Whether to display card ages
|
||||
* example: false
|
||||
* expandTaskListsByDefault:
|
||||
* type: boolean
|
||||
* default: false
|
||||
@@ -137,6 +143,11 @@ module.exports = {
|
||||
defaultsTo: false,
|
||||
columnName: 'always_display_card_creator',
|
||||
},
|
||||
displayCardAges: {
|
||||
type: 'boolean',
|
||||
defaultsTo: false,
|
||||
columnName: 'display_card_ages',
|
||||
},
|
||||
expandTaskListsByDefault: {
|
||||
type: 'boolean',
|
||||
defaultsTo: false,
|
||||
|
||||
@@ -2,6 +2,10 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const ignore = require('ignore');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const swaggerJsdoc = require('swagger-jsdoc');
|
||||
|
||||
const swaggerConfig = require('./config/swagger');
|
||||
|
||||
const OUT_DIR = 'dist';
|
||||
|
||||
@@ -40,3 +44,6 @@ const build = (src, dest) => {
|
||||
};
|
||||
|
||||
build('./', OUT_DIR);
|
||||
|
||||
const specification = swaggerJsdoc(swaggerConfig);
|
||||
fs.writeFileSync(path.join(OUT_DIR, 'swagger.json'), JSON.stringify(specification, null, 2));
|
||||
|
||||
@@ -41,7 +41,7 @@ module.exports.custom = {
|
||||
version,
|
||||
|
||||
baseUrl,
|
||||
baseUrlPath: parsedBasedUrl.pathname,
|
||||
baseUrlPath: parsedBasedUrl.pathname.replace(/\/$/, ''), // Remove trailing slash
|
||||
baseUrlSecure: parsedBasedUrl.protocol === 'https:',
|
||||
|
||||
maxUploadFileSize: envToBytes(process.env.MAX_UPLOAD_FILE_SIZE),
|
||||
@@ -64,6 +64,7 @@ module.exports.custom = {
|
||||
|
||||
showDetailedAuthErrors: process.env.SHOW_DETAILED_AUTH_ERRORS === 'true',
|
||||
outgoingProxy: process.env.OUTGOING_PROXY,
|
||||
swaggerExposed: process.env.SWAGGER_EXPOSED === 'true',
|
||||
|
||||
s3Endpoint: process.env.S3_ENDPOINT,
|
||||
s3Region: process.env.S3_REGION,
|
||||
@@ -92,6 +93,7 @@ module.exports.custom = {
|
||||
oidcIgnoreUsername: process.env.OIDC_IGNORE_USERNAME === 'true',
|
||||
oidcIgnoreRoles: process.env.OIDC_IGNORE_ROLES === 'true',
|
||||
oidcEnforced: process.env.OIDC_ENFORCED === 'true',
|
||||
oidcTimeout: envToNumber(process.env.OIDC_TIMEOUT),
|
||||
oidcDebug: process.env.OIDC_DEBUG === 'true',
|
||||
|
||||
// TODO: move client base url to environment variable?
|
||||
@@ -113,7 +115,7 @@ module.exports.custom = {
|
||||
/* Internal */
|
||||
|
||||
internalAccessToken: process.env.INTERNAL_ACCESS_TOKEN,
|
||||
termsType: process.env.TERMS_TYPE || 'self-hosted',
|
||||
termsType: process.env.TERMS_TYPE || 'custom',
|
||||
customerPanelUrl: process.env.CUSTOMER_PANEL_URL,
|
||||
demoMode: process.env.DEMO_MODE === 'true',
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user