Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f2723623a | ||
|
|
fee6841d0a | ||
|
|
ca6fb3b962 | ||
|
|
28c3f28e01 | ||
|
|
80d12aaeeb | ||
|
|
89af209a2e | ||
|
|
f1fed3e533 | ||
|
|
eddeb96b16 | ||
|
|
9e8ff3b579 | ||
|
|
fc1278011c | ||
|
|
24163463fc | ||
|
|
fdef87e3b9 | ||
|
|
964498dbaa | ||
|
|
5b519cb62f | ||
|
|
11fc3b4485 | ||
|
|
37c0b59f82 | ||
|
|
239611ad51 | ||
|
|
0575e665fe | ||
|
|
019766dced | ||
|
|
6dc9e4ed99 | ||
|
|
d4b64b90fc | ||
|
|
1a49826b85 | ||
|
|
815643eb2c | ||
|
|
bcc4c0d923 | ||
|
|
cf7810b067 | ||
|
|
f1c72df8b6 | ||
|
|
5056d38848 | ||
|
|
3ef5b3ead8 | ||
|
|
ded58dc8b0 | ||
|
|
d8ee1ecdd1 | ||
|
|
fc2f4cd5d3 | ||
|
|
7ef457157e | ||
|
|
23ee815200 | ||
|
|
40c04c35ff | ||
|
|
743f2956c8 | ||
|
|
5bf5db27d8 | ||
|
|
667c5daf25 | ||
|
|
6662b0a717 | ||
|
|
348bf4d6c4 | ||
|
|
3ac2d289d0 | ||
|
|
3bc73c43ea | ||
|
|
8e0c60f5be | ||
|
|
aae69cb5e4 | ||
|
|
782c0356c8 | ||
|
|
757cdb14a4 | ||
|
|
8a4bdbd1fe | ||
|
|
5bf42be786 | ||
|
|
d896c3183b | ||
|
|
13ee0c3466 | ||
|
|
211c841379 | ||
|
|
90a30961ff | ||
|
|
7cb9774e05 | ||
|
|
875895b331 | ||
|
|
91bc889fed | ||
|
|
a34d8e9753 | ||
|
|
107cb85ba2 | ||
|
|
9333ef562e | ||
|
|
7d28abc7e6 | ||
|
|
5317950b5c | ||
|
|
7501619b7e | ||
|
|
080e29aa03 | ||
|
|
48c532b2c2 | ||
|
|
a62815f825 | ||
|
|
6938232dc4 | ||
|
|
4c4cfa9c66 | ||
|
|
ba89d41b19 | ||
|
|
5c435414f7 | ||
|
|
2d7812a679 | ||
|
|
d386c82973 | ||
|
|
6c4db967c2 | ||
|
|
932d8225ed | ||
|
|
0d481703da | ||
|
|
23e2b4a622 | ||
|
|
f4cc51e1c0 |
@@ -0,0 +1,57 @@
|
||||
name: Build and publish release package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
|
||||
jobs:
|
||||
build-and-publish-release-package:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Workflow install pnpm
|
||||
run: npm install pnpm -g
|
||||
|
||||
- name: Client install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Server install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Server include into dist
|
||||
run: mv server/ dist/
|
||||
|
||||
- name: Client build production
|
||||
run: |
|
||||
npm run build
|
||||
working-directory: ./client
|
||||
|
||||
- name: Client include into dist
|
||||
run: |
|
||||
mv build/index.html ../dist/views/index.ejs
|
||||
mv build/* ../dist/public/
|
||||
working-directory: ./client
|
||||
|
||||
- name: Dist include README.md SECURITY.md LICENSE start.sh
|
||||
run: mv README.md SECURITY.md LICENSE start.sh dist/
|
||||
|
||||
- name: Dist Remove node modules
|
||||
run: rm -R dist/node_modules
|
||||
|
||||
- name: Dist create .zip file
|
||||
run: |
|
||||
mv dist/ planka/
|
||||
zip -r planka-prebuild-${{ github.event.release.tag_name }}.zip planka
|
||||
|
||||
- name: Dist upload assets
|
||||
run: |
|
||||
gh release upload ${{ github.event.release.tag_name }} planka-prebuild-${{ github.event.release.tag_name }}.zip
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -13,26 +13,26 @@ env:
|
||||
|
||||
jobs:
|
||||
build-and-push-docker-base-image:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Build and push Docker DEV image
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- 'charts/**'
|
||||
- 'docker-*.sh'
|
||||
- '*.md'
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build-and-push-docker-image-dev:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/plankanban/planka:dev
|
||||
@@ -6,19 +6,19 @@ on:
|
||||
|
||||
jobs:
|
||||
build-and-push-docker-image:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
script: return context.payload.release.tag_name.replace('v', '')
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Release Charts
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "charts/**"
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
release:
|
||||
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
|
||||
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "$GITHUB_ACTOR"
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
|
||||
- name: Add repositories
|
||||
run: |
|
||||
for dir in $(ls -d charts/*/); do
|
||||
helm dependency list $dir 2> /dev/null | tail +2 | head -n -1 | awk '{ print "helm repo add " $1 " " $3 }' | while read cmd; do $cmd; done
|
||||
done
|
||||
|
||||
- name: Run chart-releaser for stable
|
||||
uses: helm/chart-releaser-action@v1.5.0
|
||||
with:
|
||||
charts_dir: charts
|
||||
mark_as_latest: false
|
||||
env:
|
||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -10,3 +10,6 @@ docker-compose.override.yml
|
||||
# `npm i --package-lock-only`
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
|
||||
# Chart dependencies
|
||||
**/charts/*.tgz
|
||||
|
||||
+15
-5
@@ -1,11 +1,17 @@
|
||||
FROM ghcr.io/plankanban/planka:base-latest as server-dependencies
|
||||
FROM node:18-alpine as server-dependencies
|
||||
|
||||
RUN apk -U upgrade \
|
||||
&& apk add build-base python3 \
|
||||
--no-cache
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY server/package.json server/package-lock.json .
|
||||
|
||||
RUN npm install npm@latest --global \
|
||||
&& npm clean-install --omit=dev
|
||||
&& npm install pnpm --global \
|
||||
&& pnpm import \
|
||||
&& pnpm install --prod
|
||||
|
||||
FROM node:lts AS client
|
||||
|
||||
@@ -14,14 +20,18 @@ WORKDIR /app
|
||||
COPY client/package.json client/package-lock.json .
|
||||
|
||||
RUN npm install npm@latest --global \
|
||||
&& npm clean-install --omit=dev
|
||||
&& npm install pnpm --global \
|
||||
&& pnpm import \
|
||||
&& pnpm install --prod
|
||||
|
||||
COPY client .
|
||||
RUN DISABLE_ESLINT_PLUGIN=true npm run build
|
||||
|
||||
FROM ghcr.io/plankanban/planka:base-latest
|
||||
FROM node:18-alpine
|
||||
|
||||
RUN apk del vips-dependencies --purge
|
||||
RUN apk -U upgrade \
|
||||
&& apk add bash \
|
||||
--no-cache
|
||||
|
||||
USER node
|
||||
WORKDIR /app
|
||||
|
||||
+12
-17
@@ -1,27 +1,22 @@
|
||||
FROM node:lts-alpine
|
||||
FROM node:18-alpine
|
||||
|
||||
ARG ALPINE_VERSION=3.16
|
||||
ARG VIPS_VERSION=8.13.3
|
||||
ARG VIPS_VERSION=8.14.5
|
||||
|
||||
RUN apk -U upgrade \
|
||||
&& apk add \
|
||||
bash giflib glib lcms2 libexif \
|
||||
libgsf libjpeg-turbo libpng librsvg libwebp \
|
||||
orc pango tiff \
|
||||
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/community/ \
|
||||
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/main/ \
|
||||
bash pkgconf \
|
||||
libjpeg-turbo libexif librsvg cgif tiff libspng libimagequant \
|
||||
--no-cache \
|
||||
&& apk add \
|
||||
build-base giflib-dev glib-dev lcms2-dev libexif-dev \
|
||||
libgsf-dev libjpeg-turbo-dev libpng-dev librsvg-dev libwebp-dev \
|
||||
orc-dev pango-dev tiff-dev \
|
||||
build-base gobject-introspection-dev meson \
|
||||
libjpeg-turbo-dev libexif-dev librsvg-dev cgif-dev tiff-dev libspng-dev libimagequant-dev \
|
||||
--virtual vips-dependencies \
|
||||
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/community/ \
|
||||
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/main/ \
|
||||
--no-cache \
|
||||
&& wget -O- https://github.com/libvips/libvips/releases/download/v${VIPS_VERSION}/vips-${VIPS_VERSION}.tar.gz | tar xzC /tmp \
|
||||
&& wget -O- https://github.com/libvips/libvips/releases/download/v${VIPS_VERSION}/vips-${VIPS_VERSION}.tar.xz | tar xJC /tmp \
|
||||
&& cd /tmp/vips-${VIPS_VERSION} \
|
||||
&& ./configure \
|
||||
&& make \
|
||||
&& make install-strip \
|
||||
&& meson setup build-dir \
|
||||
&& cd build-dir \
|
||||
&& ninja \
|
||||
&& ninja test \
|
||||
&& ninja install \
|
||||
&& rm -rf /tmp/vips-${VIPS_VERSION}
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
## How to deploy Planka
|
||||
|
||||
There are 2 types of installation:
|
||||
- [Without Docker](https://docs.planka.cloud/docs/installl-planka/Debian%20&%20Ubuntu) ([for Windows](https://docs.planka.cloud/docs/installl-planka/Windows))
|
||||
- [Dockerized](https://docs.planka.cloud/docs/installl-planka/Docker%20Compose)
|
||||
There are many ways to install Planka
|
||||
|
||||
[Check them out](https://docs.planka.cloud/docs/intro)
|
||||
|
||||
For configuration, please see the [configuration section](https://docs.planka.cloud/docs/category/configuration).
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
@@ -0,0 +1,6 @@
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
version: 12.5.1
|
||||
digest: sha256:01dfb2d07ab6800b4a5a6c81f20f3377a758124b2b96b891d0cd6b4f64cf783b
|
||||
generated: "2023-05-15T00:54:48.1308917+01:00"
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: v2
|
||||
name: planka
|
||||
description: A Helm chart to deploy Planka and it's dependencies.
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
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: 0.1.12
|
||||
|
||||
# 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: "1.15.1"
|
||||
|
||||
dependencies:
|
||||
- alias: postgresql
|
||||
condition: postgresql.enabled
|
||||
name: postgresql
|
||||
repository: &bitnami-repo https://charts.bitnami.com/bitnami
|
||||
version: 12.5.1
|
||||
@@ -0,0 +1,106 @@
|
||||
# Planka Helm Chart
|
||||
|
||||
[Planka](https://github.com/plankanban/planka) is an OSS alternative to Trello that you can host yourself, and this is a Helm Chart to make it easier to deploy to K8s.
|
||||
|
||||
Shoutout to [this issue](https://github.com/plankanban/planka/issues/192) who have been asking for this Helm Chart.
|
||||
|
||||
## Issues
|
||||
|
||||
By using the Bitnami chart for PostgreSQL, there is an issue where once deployed, if trying to use a different password then it will be ignored as the Persistant Volume (PV) will already exist with the previous password. See warning from Bitnami below:
|
||||
|
||||
> **Warning!** Setting a password will be ignored on new installation in the case when previous Posgresql release was deleted through the helm command. In that case, old PVC will have an old password, and setting it through helm won't take effect. Deleting persistent volumes (PVs) will solve the issue. Refer to [issue 2061](https://github.com/bitnami/charts/issues/2061) for more details
|
||||
|
||||
If you want to fully uninstall this chart including the data, follow [these steps](https://github.com/bitnami/charts/blob/main/bitnami/postgresql/README.md#uninstalling-the-chart) from the Bitnami Chart's docs.
|
||||
|
||||
## Usage
|
||||
|
||||
The basic usage of the chart can be found below:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Chris-Greaves/planka-helm-chart.git
|
||||
cd planka-helm-chart
|
||||
helm dependency build
|
||||
export SECRETKEY=$(openssl rand -hex 64)
|
||||
helm install planka . --set secretkey=$SECRETKEY \
|
||||
--set admin_email="demo@demo.demo" \
|
||||
--set admin_password="demo" \
|
||||
--set admin_name="Demo Demo" \
|
||||
--set admin_username="demo"
|
||||
```
|
||||
|
||||
> **NOTE:** The command `openssl rand -hex 64` is needed to create a random hexadecimal key for planka. On Windows you can use Git Bash to run that command.
|
||||
|
||||
To access Planka you can port forward using the following command:
|
||||
|
||||
```bash
|
||||
kubectl port-forward $POD_NAME 3000:1337
|
||||
```
|
||||
|
||||
### Accessing Externally
|
||||
|
||||
To access Planka externally you can use the following configuration
|
||||
|
||||
```bash
|
||||
# HTTP only
|
||||
helm install planka . --set secretkey=$SECRETKEY \
|
||||
--set admin_email="demo@demo.demo" \
|
||||
--set admin_password="demo" \
|
||||
--set admin_name="Demo Demo" \
|
||||
--set admin_username="demo"
|
||||
--set ingress.enabled=true \
|
||||
--set ingress.hosts[0].host=planka.example.dev \
|
||||
|
||||
# HTTPS
|
||||
helm install planka . --set secretkey=$SECRETKEY \
|
||||
--set admin_email="demo@demo.demo" \
|
||||
--set admin_password="demo" \
|
||||
--set admin_name="Demo Demo" \
|
||||
--set admin_username="demo"
|
||||
--set ingress.enabled=true \
|
||||
--set ingress.hosts[0].host=planka.example.dev \
|
||||
--set ingress.tls[0].secretName=planka-tls \
|
||||
--set ingress.tls[0].hosts[0]=planka.example.dev \
|
||||
```
|
||||
|
||||
or create a values.yaml file like:
|
||||
|
||||
```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```
|
||||
|
||||
# Admin user
|
||||
admin_email: "demo@demo.demo" # Do not remove if you want to prevent this user from being edited/deleted
|
||||
admin_password: "demo"
|
||||
admin_name: "Demo Demo"
|
||||
admin_username: "demo"
|
||||
# Admin user
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
hosts:
|
||||
- host: planka.example.dev
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
|
||||
# Needed for HTTPS
|
||||
tls:
|
||||
- secretName: planka-tls # existing TLS secret in k8s
|
||||
hosts:
|
||||
- planka.example.dev
|
||||
```
|
||||
|
||||
```bash
|
||||
helm install planka . -f values.yaml
|
||||
```
|
||||
|
||||
### Things to consider if production hosting
|
||||
|
||||
If you want to host Planka for more than just playing around with, you might want to do the following things:
|
||||
|
||||
- Create a `values.yaml` with your config, as this will make applying upgrades much easier in the future.
|
||||
- Create your `secretkey` once and store it either in a secure vault, or in your `values.yaml` file so it will be the same for upgrading in the future.
|
||||
- Specify a password for `postgresql.auth.password` as there have been issues with the postgresql chart generating new passwords locking you out of the data you've already stored. (see [this issue](https://github.com/bitnami/charts/issues/2061))
|
||||
|
||||
Any questions or concerns, [raise an issue](https://github.com/Chris-Greaves/planka-helm-chart/issues/new).
|
||||
@@ -0,0 +1,22 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "planka.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "planka.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "planka.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "planka.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://localhost:3000 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 3000:$CONTAINER_PORT
|
||||
{{- end }}
|
||||
@@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "planka.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "planka.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "planka.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "planka.labels" -}}
|
||||
helm.sh/chart: {{ include "planka.chart" . }}
|
||||
{{ include "planka.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "planka.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "planka.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "planka.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "planka.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,136 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "planka.fullname" . }}
|
||||
labels:
|
||||
{{- include "planka.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "planka.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "planka.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "planka.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.containerPort | default 1337 }}
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
volumeMounts:
|
||||
- mountPath: /app/public/user-avatars
|
||||
subPath: user-avatars
|
||||
name: planka
|
||||
- mountPath: /app/public/project-background-images
|
||||
subPath: project-background-images
|
||||
name: planka
|
||||
- mountPath: /app/private/attachments
|
||||
subPath: attachments
|
||||
name: planka
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: planka-postgresql-svcbind-custom-user
|
||||
key: uri
|
||||
- name: BASE_URL
|
||||
{{- if .Values.baseUrl }}
|
||||
value: {{ .Values.baseUrl }}
|
||||
{{- else if .Values.ingress.enabled }}
|
||||
value: {{ printf "https://%s" (first .Values.ingress.hosts).host }}
|
||||
{{- else }}
|
||||
value: http://localhost:3000
|
||||
{{- end }}
|
||||
- name: SECRET_KEY
|
||||
value: {{ required "A secret key needs to be generated using 'openssl rand -hex 64' and assigned to secretkey." .Values.secretkey }}
|
||||
- name: TRUST_PROXY
|
||||
value: "0"
|
||||
- name: DEFAULT_ADMIN_EMAIL
|
||||
value: {{ .Values.admin_email }}
|
||||
- name: DEFAULT_ADMIN_PASSWORD
|
||||
value: {{ .Values.admin_password }}
|
||||
- name: DEFAULT_ADMIN_NAME
|
||||
value: {{ .Values.admin_name }}
|
||||
- name: DEFAULT_ADMIN_USERNAME
|
||||
value: {{ .Values.admin_username }}
|
||||
{{ range $k, $v := .Values.env }}
|
||||
- name: {{ $k | quote }}
|
||||
value: {{ $v | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.oidc.enabled }}
|
||||
{{- $secretName := default (printf "%s-oidc" (include "planka.fullname" .)) .Values.oidc.existingSecret }}
|
||||
- name: OIDC_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: clientId
|
||||
name: {{ $secretName }}
|
||||
- name: OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: clientSecret
|
||||
name: {{ $secretName }}
|
||||
- name: OIDC_ISSUER
|
||||
value: {{ required "issuerUrl is required when configuring OIDC" .Values.oidc.issuerUrl | quote }}
|
||||
- name: OIDC_SCOPES
|
||||
value: {{ join " " .Values.oidc.scopes | default "openid profile email" | quote }}
|
||||
{{- if .Values.oidc.admin.roles }}
|
||||
- name: OIDC_ADMIN_ROLES
|
||||
value: {{ join "," .Values.oidc.admin.roles | quote }}
|
||||
{{- end }}
|
||||
- name: OIDC_ROLES_ATTRIBUTE
|
||||
value: {{ .Values.oidc.admin.rolesAttribute | default "groups" | quote }}
|
||||
{{- if .Values.oidc.admin.ignoreRoles }}
|
||||
- name: OIDC_IGNORE_ROLES
|
||||
value: {{ .Values.oidc.admin.ignoreRoles | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: planka
|
||||
{{- if .Values.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .Values.persistence.existingClaim | default (include "planka.fullname" .) }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,28 @@
|
||||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2beta1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "planka.fullname" . }}
|
||||
labels:
|
||||
{{- include "planka.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "planka.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,61 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "planka.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "planka.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||
pathType: {{ .pathType }}
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- else }}
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,25 @@
|
||||
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
|
||||
---
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ include "planka.fullname" . }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "planka.name" . }}
|
||||
helm.sh/chart: {{ include "planka.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size | quote }}
|
||||
{{- if .Values.persistence.storageClass }}
|
||||
{{- if (eq "-" .Values.persistence.storageClass) }}
|
||||
storageClassName: ""
|
||||
{{- else }}
|
||||
storageClassName: "{{ .Values.persistence.storageClass }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,17 @@
|
||||
{{- if .Values.oidc.enabled }}
|
||||
{{- if eq (and (not (empty .Values.oidc.clientId)) (not (empty .Values.oidc.clientSecret))) (not (empty .Values.oidc.existingSecret)) -}}
|
||||
{{- fail "Either specify inline `clientId` and `clientSecret` or refer to them via `existingSecret`" -}}
|
||||
{{- end }}
|
||||
{{- if (and (and (not (empty .Values.oidc.clientId)) (not (empty .Values.oidc.clientSecret))) (empty .Values.oidc.existingSecret)) -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "planka.fullname" . }}-oidc
|
||||
labels:
|
||||
{{- include "planka.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
data:
|
||||
clientId: {{ .Values.oidc.clientId | b64enc | quote }}
|
||||
clientSecret: {{ .Values.oidc.clientSecret | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "planka.fullname" . }}
|
||||
labels:
|
||||
{{- include "planka.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "planka.selectorLabels" . | nindent 4 }}
|
||||
@@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "planka.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "planka.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "planka.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "planka.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "planka.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,180 @@
|
||||
# Default values for planka.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: ghcr.io/plankanban/planka
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
# Generate a secret using openssl rand -base64 45
|
||||
secretkey: ""
|
||||
|
||||
# Base url for Planka. Will override `ingress.hosts[0].host`
|
||||
# Defaults to `http://localhost:3000` if ingress is disabled.
|
||||
baseUrl: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 1337
|
||||
## @param service.containerPort Planka HTTP container port
|
||||
## If empty will default to 1337
|
||||
##
|
||||
containerPort: 1337
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
# Used to set planka BASE_URL if no `baseurl` is provided.
|
||||
- host: planka.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: planka-tls
|
||||
# hosts:
|
||||
# - planka.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
postgresql:
|
||||
enabled: true
|
||||
auth:
|
||||
database: planka
|
||||
username: planka
|
||||
password: ""
|
||||
postgresPassword: ""
|
||||
replicationPassword: ""
|
||||
# existingSecret: planka-postgresql
|
||||
serviceBindings:
|
||||
enabled: true
|
||||
|
||||
## PVC-based data storage configuration
|
||||
persistence:
|
||||
enabled: false
|
||||
# existingClaim: netbox-data
|
||||
# storageClass: "-"
|
||||
|
||||
accessMode: ReadWriteOnce
|
||||
size: 10Gi
|
||||
|
||||
## OpenID Identity Management configuration
|
||||
##
|
||||
## Example:
|
||||
## ---------------
|
||||
## oidc:
|
||||
## enabled: true
|
||||
## clientId: sxxaAIAxVXlCxTmc1YLHBbQr8NL8MqLI2DUbt42d
|
||||
## clientSecret: om4RTMRVHRszU7bqxB7RZNkHIzA8e4sGYWxeCwIMYQXPwEBWe4SY5a0wwCe9ltB3zrq5f0dnFnp34cEHD7QSMHsKvV9AiV5Z7eqDraMnv0I8IFivmuV5wovAECAYreSI
|
||||
## issuerUrl: https://auth.local/application/o/planka/
|
||||
## admin:
|
||||
## roles:
|
||||
## - planka-admin
|
||||
##
|
||||
## ---------------
|
||||
## NOTE: A minimal configuration requires setting `clientId`, `clientSecret` and `issuerUrl`. (plus `admin.roles` for administrators)
|
||||
## ref: https://docs.planka.cloud/docs/Configuration/OIDC
|
||||
##
|
||||
oidc:
|
||||
## @param oidc.enabled Enable single sign-on (SSO) with OpenID Connect (OIDC)
|
||||
##
|
||||
enabled: false
|
||||
|
||||
## OIDC credentials
|
||||
## @param oidc.clientId A string unique to the provider that identifies your app.
|
||||
## @param oidc.clientSecret A secret string that the provider uses to confirm ownership of a client ID.
|
||||
##
|
||||
## NOTE: Either specify inline `clientId` and `clientSecret` or refer to them via `existingSecret`
|
||||
##
|
||||
clientId: ""
|
||||
clientSecret: ""
|
||||
|
||||
## @param oidc.existingSecret Name of an existing secret containing OIDC credentials
|
||||
## NOTE: Must contain key `clientId` and `clientSecret`
|
||||
## NOTE: When it's set, the `clientId` and `clientSecret` parameters are ignored
|
||||
##
|
||||
existingSecret: ""
|
||||
|
||||
## @param oidc.issuerUrl The OpenID connect metadata document endpoint
|
||||
##
|
||||
issuerUrl: ""
|
||||
|
||||
## @param oidc.scopes A list of scopes required for OIDC client.
|
||||
## If empty will default to `openid`, `profile` and `email`
|
||||
## NOTE: Planka needs the email and name claims
|
||||
##
|
||||
scopes: []
|
||||
|
||||
## Admin permissions configuration
|
||||
admin:
|
||||
## @param oidc.admin.ignoreRoles If set to true, the admin roles will be ignored.
|
||||
## It is useful if you want to use OIDC for authentication but not for authorization.
|
||||
## If empty will default to `false`
|
||||
##
|
||||
ignoreRoles: false
|
||||
|
||||
## @param oidc.admin.rolesAttribute The name of a custom group claim that you have configured in your OIDC provider
|
||||
## If empty will default to `groups`
|
||||
##
|
||||
rolesAttribute: groups
|
||||
|
||||
## @param oidc.admin.roles The names of the admin groups
|
||||
##
|
||||
roles: []
|
||||
# - planka-admin
|
||||
+1
-1
@@ -1 +1 @@
|
||||
REACT_APP_VERSION=1.10.3
|
||||
REACT_APP_VERSION=1.15.1
|
||||
|
||||
Generated
+3379
-21947
File diff suppressed because it is too large
Load Diff
+30
-29
@@ -55,59 +55,60 @@
|
||||
"dependencies": {
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"classnames": "^2.3.2",
|
||||
"date-fns": "^2.29.3",
|
||||
"date-fns": "^2.30.0",
|
||||
"dequal": "^2.0.3",
|
||||
"easymde": "^2.18.0",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^22.0.6",
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
"i18next": "^23.7.6",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"initials": "^3.1.2",
|
||||
"js-cookie": "^3.0.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"node-sass": "^8.0.0",
|
||||
"photoswipe": "^5.3.3",
|
||||
"nanoid": "^5.0.3",
|
||||
"node-sass": "^9.0.0",
|
||||
"photoswipe": "^5.4.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-datepicker": "^4.8.0",
|
||||
"react-datepicker": "^4.21.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-i18next": "^12.0.0",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-input-mask": "^2.0.4",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-photoswipe-gallery": "^2.2.2",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.4.3",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-photoswipe-gallery": "^2.2.7",
|
||||
"react-redux": "^8.1.3",
|
||||
"react-router-dom": "^6.19.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-simplemde-editor": "^5.2.0",
|
||||
"react-textarea-autosize": "^8.4.0",
|
||||
"redux": "^4.2.0",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
"redux": "^4.2.1",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-orm": "^0.16.2",
|
||||
"redux-saga": "^1.2.1",
|
||||
"remark-breaks": "^3.0.2",
|
||||
"redux-saga": "^1.2.3",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"reselect": "^4.1.7",
|
||||
"reselect": "^4.1.8",
|
||||
"sails.io.js": "^1.2.1",
|
||||
"semantic-ui-react": "^2.1.3",
|
||||
"semantic-ui-react": "^2.1.4",
|
||||
"socket.io-client": "^2.5.0",
|
||||
"validator": "^13.7.0",
|
||||
"whatwg-fetch": "^3.6.2",
|
||||
"validator": "^13.11.0",
|
||||
"whatwg-fetch": "^3.6.19",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@testing-library/jest-dom": "^6.1.4",
|
||||
"@testing-library/react": "^14.1.0",
|
||||
"@testing-library/user-event": "^14.5.1",
|
||||
"babel-preset-airbnb": "^5.0.0",
|
||||
"chai": "^4.3.7",
|
||||
"eslint": "^8.28.0",
|
||||
"chai": "^4.3.10",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"react-test-renderer": "^18.2.0"
|
||||
}
|
||||
|
||||
@@ -39,6 +39,14 @@ const initializeCore = (
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: with success?
|
||||
initializeCore.fetchConfig = (config) => ({
|
||||
type: ActionTypes.CORE_INITIALIZE__CONFIG_FETCH,
|
||||
payload: {
|
||||
config,
|
||||
},
|
||||
});
|
||||
|
||||
const logout = () => ({
|
||||
type: ActionTypes.LOGOUT,
|
||||
payload: {},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const initializeLogin = (config) => ({
|
||||
type: ActionTypes.LOGIN_INITIALIZE,
|
||||
payload: {
|
||||
config,
|
||||
},
|
||||
});
|
||||
|
||||
const authenticate = (data) => ({
|
||||
type: ActionTypes.AUTHENTICATE,
|
||||
payload: {
|
||||
@@ -21,12 +28,33 @@ authenticate.failure = (error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const authenticateUsingOidc = () => ({
|
||||
type: ActionTypes.USING_OIDC_AUTHENTICATE,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
authenticateUsingOidc.success = (accessToken) => ({
|
||||
type: ActionTypes.USING_OIDC_AUTHENTICATE__SUCCESS,
|
||||
payload: {
|
||||
accessToken,
|
||||
},
|
||||
});
|
||||
|
||||
authenticateUsingOidc.failure = (error) => ({
|
||||
type: ActionTypes.USING_OIDC_AUTHENTICATE__FAILURE,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const clearAuthenticateError = () => ({
|
||||
type: ActionTypes.AUTHENTICATE_ERROR_CLEAR,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
export default {
|
||||
initializeLogin,
|
||||
authenticate,
|
||||
authenticateUsingOidc,
|
||||
clearAuthenticateError,
|
||||
};
|
||||
|
||||
@@ -5,10 +5,14 @@ import socket from './socket';
|
||||
|
||||
const createAccessToken = (data, headers) => http.post('/access-tokens', data, headers);
|
||||
|
||||
const exchangeForAccessTokenUsingOidc = (data, headers) =>
|
||||
http.post('/access-tokens/exchange-using-oidc', data, headers);
|
||||
|
||||
const deleteCurrentAccessToken = (headers) =>
|
||||
socket.delete('/access-tokens/me', undefined, headers);
|
||||
|
||||
export default {
|
||||
createAccessToken,
|
||||
exchangeForAccessTokenUsingOidc,
|
||||
deleteCurrentAccessToken,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import socket from './socket';
|
||||
import { transformUser } from './users';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
@@ -13,6 +14,10 @@ const getActivities = (cardId, data, headers) =>
|
||||
socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({
|
||||
...body,
|
||||
items: body.items.map(transformActivity),
|
||||
included: {
|
||||
...body.included,
|
||||
users: body.included.users.map(transformUser),
|
||||
},
|
||||
}));
|
||||
|
||||
/* Event handlers */
|
||||
|
||||
@@ -1,18 +1,50 @@
|
||||
import socket from './socket';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
export const transformBoardMembership = (boardMembership) => ({
|
||||
...boardMembership,
|
||||
createdAt: new Date(boardMembership.createdAt),
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createBoardMembership = (boardId, data, headers) =>
|
||||
socket.post(`/boards/${boardId}/memberships`, data, headers);
|
||||
socket.post(`/boards/${boardId}/memberships`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformBoardMembership(body.item),
|
||||
}));
|
||||
|
||||
const updateBoardMembership = (id, data, headers) =>
|
||||
socket.patch(`/board-memberships/${id}`, data, headers);
|
||||
socket.patch(`/board-memberships/${id}`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformBoardMembership(body.item),
|
||||
}));
|
||||
|
||||
const deleteBoardMembership = (id, headers) =>
|
||||
socket.delete(`/board-memberships/${id}`, undefined, headers);
|
||||
socket.delete(`/board-memberships/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformBoardMembership(body.item),
|
||||
}));
|
||||
|
||||
/* Event handlers */
|
||||
|
||||
const makeHandleBoardMembershipCreate = (next) => (body) => {
|
||||
next({
|
||||
...body,
|
||||
item: transformBoardMembership(body.item),
|
||||
});
|
||||
};
|
||||
|
||||
const makeHandleBoardMembershipUpdate = makeHandleBoardMembershipCreate;
|
||||
|
||||
const makeHandleBoardMembershipDelete = makeHandleBoardMembershipCreate;
|
||||
|
||||
export default {
|
||||
createBoardMembership,
|
||||
updateBoardMembership,
|
||||
deleteBoardMembership,
|
||||
makeHandleBoardMembershipCreate,
|
||||
makeHandleBoardMembershipUpdate,
|
||||
makeHandleBoardMembershipDelete,
|
||||
};
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import socket from './socket';
|
||||
import http from './http';
|
||||
import { transformUser } from './users';
|
||||
import { transformBoardMembership } from './board-memberships';
|
||||
import { transformCard } from './cards';
|
||||
import { transformAttachment } from './attachments';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createBoard = (projectId, data, headers) =>
|
||||
socket.post(`/projects/${projectId}/boards`, data, headers);
|
||||
socket.post(`/projects/${projectId}/boards`, data, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||
},
|
||||
}));
|
||||
|
||||
const createBoardWithImport = (projectId, data, requestId, headers) =>
|
||||
http.post(`/projects/${projectId}/boards?requestId=${requestId}`, data, headers);
|
||||
@@ -18,6 +26,8 @@ const getBoard = (id, subscribe, headers) =>
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
users: body.included.users.map(transformUser),
|
||||
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||
cards: body.included.cards.map(transformCard),
|
||||
attachments: body.included.attachments.map(transformAttachment),
|
||||
},
|
||||
|
||||
@@ -5,9 +5,11 @@ import Config from '../constants/Config';
|
||||
const http = {};
|
||||
|
||||
// TODO: add all methods
|
||||
['POST'].forEach((method) => {
|
||||
['GET', 'POST'].forEach((method) => {
|
||||
http[method.toLowerCase()] = (url, data, headers) => {
|
||||
const formData = Object.keys(data).reduce((result, key) => {
|
||||
const formData =
|
||||
data &&
|
||||
Object.keys(data).reduce((result, key) => {
|
||||
result.append(key, data[key]);
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import http from './http';
|
||||
import socket from './socket';
|
||||
import root from './root';
|
||||
import accessTokens from './access-tokens';
|
||||
import users from './users';
|
||||
import projects from './projects';
|
||||
@@ -20,6 +21,7 @@ import notifications from './notifications';
|
||||
export { http, socket };
|
||||
|
||||
export default {
|
||||
...root,
|
||||
...accessTokens,
|
||||
...users,
|
||||
...projects,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
import socket from './socket';
|
||||
import { transformUser } from './users';
|
||||
import { transformCard } from './cards';
|
||||
import { transformActivity } from './activities';
|
||||
|
||||
@@ -19,6 +20,7 @@ const getNotifications = (headers) =>
|
||||
items: body.items.map(transformNotification),
|
||||
included: {
|
||||
...omit(body.included, 'actions'),
|
||||
users: body.included.users.map(transformUser),
|
||||
cards: body.included.cards.map(transformCard),
|
||||
activities: body.included.actions.map(transformActivity),
|
||||
},
|
||||
@@ -30,6 +32,7 @@ const getNotification = (id, headers) =>
|
||||
item: transformNotification(body.item),
|
||||
included: {
|
||||
...omit(body.included, 'actions'),
|
||||
users: body.included.users.map(transformUser),
|
||||
cards: body.included.cards.map(transformCard),
|
||||
activities: body.included.actions.map(transformActivity),
|
||||
},
|
||||
|
||||
@@ -1,14 +1,40 @@
|
||||
import socket from './socket';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
export const transformProjectManager = (projectManager) => ({
|
||||
...projectManager,
|
||||
createdAt: new Date(projectManager.createdAt),
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createProjectManager = (projectId, data, headers) =>
|
||||
socket.post(`/projects/${projectId}/managers`, data, headers);
|
||||
socket.post(`/projects/${projectId}/managers`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformProjectManager(body.item),
|
||||
}));
|
||||
|
||||
const deleteProjectManager = (id, headers) =>
|
||||
socket.delete(`/project-managers/${id}`, undefined, headers);
|
||||
socket.delete(`/project-managers/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformProjectManager(body.item),
|
||||
}));
|
||||
|
||||
/* Event handlers */
|
||||
|
||||
const makeHandleProjectManagerCreate = (next) => (body) => {
|
||||
next({
|
||||
...body,
|
||||
item: transformProjectManager(body.item),
|
||||
});
|
||||
};
|
||||
|
||||
const makeHandleProjectManagerDelete = makeHandleProjectManagerCreate;
|
||||
|
||||
export default {
|
||||
createProjectManager,
|
||||
deleteProjectManager,
|
||||
makeHandleProjectManagerCreate,
|
||||
makeHandleProjectManagerDelete,
|
||||
};
|
||||
|
||||
@@ -1,13 +1,41 @@
|
||||
import http from './http';
|
||||
import socket from './socket';
|
||||
import { transformUser } from './users';
|
||||
import { transformProjectManager } from './project-managers';
|
||||
import { transformBoardMembership } from './board-memberships';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getProjects = (headers) => socket.get('/projects', undefined, headers);
|
||||
const getProjects = (headers) =>
|
||||
socket.get('/projects', undefined, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
users: body.included.users.map(transformUser),
|
||||
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||
},
|
||||
}));
|
||||
|
||||
const createProject = (data, headers) => socket.post('/projects', data, headers);
|
||||
const createProject = (data, headers) =>
|
||||
socket.post('/projects', data, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||
},
|
||||
}));
|
||||
|
||||
const getProject = (id, headers) => socket.get(`/projects/${id}`, undefined, headers);
|
||||
const getProject = (id, headers) =>
|
||||
socket.get(`/projects/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
users: body.included.users.map(transformUser),
|
||||
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||
},
|
||||
}));
|
||||
|
||||
const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers);
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import http from './http';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getConfig = (headers) => http.get('/config', undefined, headers);
|
||||
|
||||
export default {
|
||||
getConfig,
|
||||
};
|
||||
+70
-10
@@ -1,30 +1,87 @@
|
||||
import http from './http';
|
||||
import socket from './socket';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
export const transformUser = (user) => ({
|
||||
...user,
|
||||
createdAt: new Date(user.createdAt),
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getUsers = (headers) => socket.get('/users', undefined, headers);
|
||||
const getUsers = (headers) =>
|
||||
socket.get('/users', undefined, headers).then((body) => ({
|
||||
...body,
|
||||
items: body.items.map(transformUser),
|
||||
}));
|
||||
|
||||
const createUser = (data, headers) => socket.post('/users', data, headers);
|
||||
const createUser = (data, headers) =>
|
||||
socket.post('/users', data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
|
||||
const getUser = (id, headers) => socket.get(`/users/${id}`, undefined, headers);
|
||||
const getUser = (id, headers) =>
|
||||
socket.get(`/users/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
|
||||
const getCurrentUser = (subscribe, headers) =>
|
||||
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers);
|
||||
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
|
||||
const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers);
|
||||
const updateUser = (id, data, headers) =>
|
||||
socket.patch(`/users/${id}`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
|
||||
const updateUserEmail = (id, data, headers) => socket.patch(`/users/${id}/email`, data, headers);
|
||||
const updateUserEmail = (id, data, headers) =>
|
||||
socket.patch(`/users/${id}/email`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
|
||||
const updateUserPassword = (id, data, headers) =>
|
||||
socket.patch(`/users/${id}/password`, data, headers);
|
||||
socket.patch(`/users/${id}/password`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
|
||||
const updateUserUsername = (id, data, headers) =>
|
||||
socket.patch(`/users/${id}/username`, data, headers);
|
||||
socket.patch(`/users/${id}/username`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
|
||||
const updateUserAvatar = (id, data, headers) => http.post(`/users/${id}/avatar`, data, headers);
|
||||
const updateUserAvatar = (id, data, headers) =>
|
||||
http.post(`/users/${id}/avatar`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
|
||||
const deleteUser = (id, headers) => socket.delete(`/users/${id}`, undefined, headers);
|
||||
const deleteUser = (id, headers) =>
|
||||
socket.delete(`/users/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
|
||||
/* Event handlers */
|
||||
|
||||
const makeHandleUserCreate = (next) => (body) => {
|
||||
next({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
});
|
||||
};
|
||||
|
||||
const makeHandleUserUpdate = makeHandleUserCreate;
|
||||
|
||||
const makeHandleUserDelete = makeHandleUserCreate;
|
||||
|
||||
export default {
|
||||
getUsers,
|
||||
@@ -37,4 +94,7 @@ export default {
|
||||
updateUserUsername,
|
||||
updateUserAvatar,
|
||||
deleteUser,
|
||||
makeHandleUserCreate,
|
||||
makeHandleUserUpdate,
|
||||
makeHandleUserDelete,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
|
||||
import { useClosableForm, useForm } from '../../../hooks';
|
||||
import { useForm } from '../../../hooks';
|
||||
|
||||
import styles from './CommentEdit.module.scss';
|
||||
|
||||
@@ -35,12 +35,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
||||
text: data.text.trim(),
|
||||
};
|
||||
|
||||
if (!cleanData.text) {
|
||||
textField.current.ref.current.select();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dequal(cleanData, defaultData)) {
|
||||
if (cleanData.text && !dequal(cleanData, defaultData)) {
|
||||
onUpdate(cleanData);
|
||||
}
|
||||
|
||||
@@ -65,10 +60,9 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
||||
[submit],
|
||||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
||||
close,
|
||||
isOpened,
|
||||
);
|
||||
const handleFieldBlur = useCallback(() => {
|
||||
submit();
|
||||
}, [submit]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
submit();
|
||||
@@ -99,13 +93,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
||||
onBlur={handleFieldBlur}
|
||||
/>
|
||||
<div className={styles.controls}>
|
||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
||||
<Button
|
||||
positive
|
||||
content={t('action.save')}
|
||||
onMouseOver={handleControlMouseOver}
|
||||
onMouseOut={handleControlMouseOut}
|
||||
/>
|
||||
<Button positive content={t('action.save')} />
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -217,6 +217,7 @@ const CardModal = React.memo(
|
||||
onUserSelect={onUserAdd}
|
||||
onUserDeselect={onUserRemove}
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(styles.attachment, styles.dueDate)}
|
||||
@@ -266,6 +267,7 @@ const CardModal = React.memo(
|
||||
onMove={onLabelMove}
|
||||
onDelete={onLabelDelete}
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(styles.attachment, styles.dueDate)}
|
||||
@@ -314,10 +316,11 @@ const CardModal = React.memo(
|
||||
)}
|
||||
</span>
|
||||
{canEdit && (
|
||||
// eslint-disable-next-line jsx-a11y/control-has-associated-label
|
||||
<button
|
||||
onClick={handleToggleStopwatchClick}
|
||||
type="button"
|
||||
className={classNames(styles.attachment, styles.dueDate)}
|
||||
onClick={handleToggleStopwatchClick}
|
||||
>
|
||||
<Icon
|
||||
name={stopwatch.startedAt ? 'pause' : 'play'}
|
||||
|
||||
@@ -100,7 +100,6 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
|
||||
onChange={setValue}
|
||||
/>
|
||||
<div className={styles.controls}>
|
||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
||||
<Button positive content={t('action.save')} />
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
|
||||
import { useClosableForm, useField } from '../../../hooks';
|
||||
import { useField } from '../../../hooks';
|
||||
|
||||
import styles from './NameEdit.module.scss';
|
||||
|
||||
@@ -28,12 +28,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
||||
const submit = useCallback(() => {
|
||||
const cleanValue = value.trim();
|
||||
|
||||
if (!cleanValue) {
|
||||
field.current.ref.current.select();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cleanValue !== defaultValue) {
|
||||
if (cleanValue && cleanValue !== defaultValue) {
|
||||
onUpdate(cleanValue);
|
||||
}
|
||||
|
||||
@@ -60,10 +55,9 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
||||
[submit],
|
||||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
||||
close,
|
||||
isOpened,
|
||||
);
|
||||
const handleFieldBlur = useCallback(() => {
|
||||
submit();
|
||||
}, [submit]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
submit();
|
||||
@@ -93,13 +87,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
||||
onBlur={handleFieldBlur}
|
||||
/>
|
||||
<div className={styles.controls}>
|
||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
||||
<Button
|
||||
positive
|
||||
content={t('action.save')}
|
||||
onMouseOver={handleControlMouseOver}
|
||||
onMouseOut={handleControlMouseOut}
|
||||
/>
|
||||
<Button positive content={t('action.save')} />
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -48,6 +48,8 @@ const Tasks = React.memo(({ items, canEdit, onCreate, onUpdate, onMove, onDelete
|
||||
return (
|
||||
<>
|
||||
{items.length > 0 && (
|
||||
<>
|
||||
<span className={styles.progressWrapper}>
|
||||
<Progress
|
||||
autoSuccess
|
||||
value={completedItems.length}
|
||||
@@ -56,6 +58,11 @@ const Tasks = React.memo(({ items, canEdit, onCreate, onUpdate, onMove, onDelete
|
||||
size="tiny"
|
||||
className={styles.progress}
|
||||
/>
|
||||
</span>
|
||||
<span className={styles.count}>
|
||||
{completedItems.length}/{items.length}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||
<Droppable droppableId="tasks" type={DroppableTypes.TASK}>
|
||||
|
||||
@@ -3,6 +3,23 @@
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.progressWrapper {
|
||||
display: inline-block;
|
||||
padding: 3px 0;
|
||||
vertical-align: top;
|
||||
width: calc(100% - 50px);
|
||||
}
|
||||
|
||||
.count {
|
||||
color: #8c8c8c;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.taskButton {
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Icon, Menu } from 'semantic-ui-react';
|
||||
import { Button, Icon, Menu } from 'semantic-ui-react';
|
||||
import { usePopup } from '../../lib/popup';
|
||||
|
||||
import Paths from '../../constants/Paths';
|
||||
@@ -55,15 +55,16 @@ const Header = React.memo(
|
||||
>
|
||||
<Icon fitted name="arrow left" />
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
className={classNames(
|
||||
styles.item,
|
||||
canEditProject && styles.itemHoverable,
|
||||
styles.title,
|
||||
)}
|
||||
<Menu.Item className={classNames(styles.item, styles.title)}>
|
||||
{project.name}
|
||||
{canEditProject && (
|
||||
<Button
|
||||
className={classNames(styles.editButton, styles.target)}
|
||||
onClick={handleProjectSettingsClick}
|
||||
>
|
||||
{project.name}
|
||||
<Icon fitted name="pencil" size="small" />
|
||||
</Button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</Menu.Menu>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
:global(#app) {
|
||||
.editButton {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
line-height: 34px;
|
||||
margin-left: 8px;
|
||||
opacity: 0;
|
||||
padding: 0;
|
||||
width: 34px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
cursor: auto;
|
||||
user-select: auto;
|
||||
@@ -11,6 +27,10 @@
|
||||
&:hover {
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
|
||||
.target {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Grid, Header, Message } from 'semantic-ui-react';
|
||||
import { Button, Form, Grid, Header, Message } from 'semantic-ui-react';
|
||||
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
|
||||
import { Input } from '../../lib/custom-ui';
|
||||
|
||||
@@ -28,6 +28,21 @@ const createMessage = (error) => {
|
||||
type: 'error',
|
||||
content: 'common.invalidPassword',
|
||||
};
|
||||
case 'Use single sign-on':
|
||||
return {
|
||||
type: 'error',
|
||||
content: 'common.useSingleSignOn',
|
||||
};
|
||||
case 'Email already in use':
|
||||
return {
|
||||
type: 'error',
|
||||
content: 'common.emailAlreadyInUse',
|
||||
};
|
||||
case 'Username already in use':
|
||||
return {
|
||||
type: 'error',
|
||||
content: 'common.usernameAlreadyInUse',
|
||||
};
|
||||
case 'Failed to fetch':
|
||||
return {
|
||||
type: 'warning',
|
||||
@@ -47,7 +62,16 @@ const createMessage = (error) => {
|
||||
};
|
||||
|
||||
const Login = React.memo(
|
||||
({ defaultData, isSubmitting, error, onAuthenticate, onMessageDismiss }) => {
|
||||
({
|
||||
defaultData,
|
||||
isSubmitting,
|
||||
isSubmittingUsingOidc,
|
||||
error,
|
||||
withOidc,
|
||||
onAuthenticate,
|
||||
onAuthenticateUsingOidc,
|
||||
onMessageDismiss,
|
||||
}) => {
|
||||
const [t] = useTranslation();
|
||||
const wasSubmitting = usePrevious(isSubmitting);
|
||||
|
||||
@@ -168,9 +192,19 @@ const Login = React.memo(
|
||||
content={t('action.logIn')}
|
||||
floated="right"
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
disabled={isSubmitting || isSubmittingUsingOidc}
|
||||
/>
|
||||
</Form>
|
||||
{withOidc && (
|
||||
<Button
|
||||
type="button"
|
||||
loading={isSubmittingUsingOidc}
|
||||
disabled={isSubmitting || isSubmittingUsingOidc}
|
||||
onClick={onAuthenticateUsingOidc}
|
||||
>
|
||||
{t('action.logInWithSSO')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Grid.Column>
|
||||
@@ -201,10 +235,15 @@ const Login = React.memo(
|
||||
);
|
||||
|
||||
Login.propTypes = {
|
||||
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
defaultData: PropTypes.object.isRequired,
|
||||
/* eslint-enable react/forbid-prop-types */
|
||||
isSubmitting: PropTypes.bool.isRequired,
|
||||
isSubmittingUsingOidc: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
withOidc: PropTypes.bool.isRequired,
|
||||
onAuthenticate: PropTypes.func.isRequired,
|
||||
onAuthenticateUsingOidc: PropTypes.func.isRequired,
|
||||
onMessageDismiss: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Loader } from 'semantic-ui-react';
|
||||
|
||||
import LoginContainer from '../containers/LoginContainer';
|
||||
|
||||
const LoginWrapper = React.memo(({ isInitializing }) => {
|
||||
if (isInitializing) {
|
||||
return <Loader active size="massive" />;
|
||||
}
|
||||
|
||||
return <LoginContainer />;
|
||||
});
|
||||
|
||||
LoginWrapper.propTypes = {
|
||||
isInitializing: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default LoginWrapper;
|
||||
@@ -5,7 +5,7 @@ import { Route, Routes } from 'react-router-dom';
|
||||
import { ReduxRouter } from '../lib/redux-router';
|
||||
|
||||
import Paths from '../constants/Paths';
|
||||
import LoginContainer from '../containers/LoginContainer';
|
||||
import LoginWrapperContainer from '../containers/LoginWrapperContainer';
|
||||
import CoreContainer from '../containers/CoreContainer';
|
||||
import NotFound from './NotFound';
|
||||
|
||||
@@ -13,7 +13,6 @@ import 'react-datepicker/dist/react-datepicker.css';
|
||||
import 'photoswipe/dist/photoswipe.css';
|
||||
import 'easymde/dist/easymde.min.css';
|
||||
import '../lib/custom-ui/styles.css';
|
||||
|
||||
import '../styles.module.scss';
|
||||
|
||||
function Root({ store, history }) {
|
||||
@@ -21,7 +20,8 @@ function Root({ store, history }) {
|
||||
<Provider store={store}>
|
||||
<ReduxRouter history={history}>
|
||||
<Routes>
|
||||
<Route path={Paths.LOGIN} element={<LoginContainer />} />
|
||||
<Route path={Paths.LOGIN} element={<LoginWrapperContainer />} />
|
||||
<Route path={Paths.OIDC_CALLBACK} element={<LoginWrapperContainer />} />
|
||||
<Route path={Paths.ROOT} element={<CoreContainer />} />
|
||||
<Route path={Paths.PROJECTS} element={<CoreContainer />} />
|
||||
<Route path={Paths.BOARDS} element={<CoreContainer />} />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { dequal } from 'dequal';
|
||||
import omit from 'lodash/omit';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -9,7 +10,7 @@ import { useForm } from '../../hooks';
|
||||
|
||||
import styles from './UserInformationEdit.module.scss';
|
||||
|
||||
const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
||||
const UserInformationEdit = React.memo(({ defaultData, isNameEditable, onUpdate }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const [data, handleFieldChange] = useForm(() => ({
|
||||
@@ -32,13 +33,17 @@ const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
||||
const nameField = useRef(null);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
if (isNameEditable) {
|
||||
if (!cleanData.name) {
|
||||
nameField.current.select();
|
||||
return;
|
||||
}
|
||||
|
||||
onUpdate(cleanData);
|
||||
}, [onUpdate, cleanData]);
|
||||
} else {
|
||||
onUpdate(omit(cleanData, 'name'));
|
||||
}
|
||||
}, [isNameEditable, onUpdate, cleanData]);
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
@@ -48,6 +53,7 @@ const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
||||
ref={nameField}
|
||||
name="name"
|
||||
value={data.name}
|
||||
disabled={!isNameEditable}
|
||||
className={styles.field}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
@@ -74,6 +80,7 @@ const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
||||
|
||||
UserInformationEdit.propTypes = {
|
||||
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
isNameEditable: PropTypes.bool.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import { Popup } from '../lib/custom-ui';
|
||||
|
||||
import UserInformationEdit from './UserInformationEdit';
|
||||
|
||||
const UserInformationEditStep = React.memo(({ defaultData, onUpdate, onBack, onClose }) => {
|
||||
const UserInformationEditStep = React.memo(
|
||||
({ defaultData, isNameEditable, onUpdate, onBack, onClose }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const handleUpdate = useCallback(
|
||||
@@ -24,14 +25,20 @@ const UserInformationEditStep = React.memo(({ defaultData, onUpdate, onBack, onC
|
||||
})}
|
||||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<UserInformationEdit defaultData={defaultData} onUpdate={handleUpdate} />
|
||||
<UserInformationEdit
|
||||
defaultData={defaultData}
|
||||
isNameEditable={isNameEditable}
|
||||
onUpdate={handleUpdate}
|
||||
/>
|
||||
</Popup.Content>
|
||||
</>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
UserInformationEditStep.propTypes = {
|
||||
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
isNameEditable: PropTypes.bool.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onBack: PropTypes.func,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
|
||||
@@ -23,6 +23,7 @@ const AccountPane = React.memo(
|
||||
phone,
|
||||
organization,
|
||||
language,
|
||||
isLocked,
|
||||
isAvatarUpdating,
|
||||
usernameUpdateForm,
|
||||
emailUpdateForm,
|
||||
@@ -74,6 +75,7 @@ const AccountPane = React.memo(
|
||||
phone,
|
||||
organization,
|
||||
}}
|
||||
isNameEditable={!isLocked}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
<Divider horizontal section>
|
||||
@@ -102,6 +104,8 @@ const AccountPane = React.memo(
|
||||
value={language || 'auto'}
|
||||
onChange={handleLanguageChange}
|
||||
/>
|
||||
{!isLocked && (
|
||||
<>
|
||||
<Divider horizontal section>
|
||||
<Header as="h4">
|
||||
{t('common.authentication', {
|
||||
@@ -159,6 +163,8 @@ const AccountPane = React.memo(
|
||||
</Button>
|
||||
</UserPasswordEditPopup>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Tab.Pane>
|
||||
);
|
||||
},
|
||||
@@ -172,6 +178,7 @@ AccountPane.propTypes = {
|
||||
phone: PropTypes.string,
|
||||
organization: PropTypes.string,
|
||||
language: PropTypes.string,
|
||||
isLocked: PropTypes.bool.isRequired,
|
||||
isAvatarUpdating: PropTypes.bool.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
usernameUpdateForm: PropTypes.object.isRequired,
|
||||
|
||||
@@ -17,6 +17,7 @@ const UserSettingsModal = React.memo(
|
||||
organization,
|
||||
language,
|
||||
subscribeToOwnCards,
|
||||
isLocked,
|
||||
isAvatarUpdating,
|
||||
usernameUpdateForm,
|
||||
emailUpdateForm,
|
||||
@@ -48,6 +49,7 @@ const UserSettingsModal = React.memo(
|
||||
phone={phone}
|
||||
organization={organization}
|
||||
language={language}
|
||||
isLocked={isLocked}
|
||||
isAvatarUpdating={isAvatarUpdating}
|
||||
usernameUpdateForm={usernameUpdateForm}
|
||||
emailUpdateForm={emailUpdateForm}
|
||||
@@ -105,6 +107,7 @@ UserSettingsModal.propTypes = {
|
||||
organization: PropTypes.string,
|
||||
language: PropTypes.string,
|
||||
subscribeToOwnCards: PropTypes.bool.isRequired,
|
||||
isLocked: PropTypes.bool.isRequired,
|
||||
isAvatarUpdating: PropTypes.bool.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
usernameUpdateForm: PropTypes.object.isRequired,
|
||||
|
||||
@@ -64,6 +64,7 @@ const ActionsStep = React.memo(
|
||||
return (
|
||||
<UserInformationEditStep
|
||||
defaultData={pick(user, ['name', 'phone', 'organization'])}
|
||||
isNameEditable={!user.isLocked}
|
||||
onUpdate={onUpdate}
|
||||
onBack={handleBack}
|
||||
onClose={onClose}
|
||||
@@ -135,6 +136,8 @@ const ActionsStep = React.memo(
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
{!user.isLocked && (
|
||||
<>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
|
||||
{t('action.editUsername', {
|
||||
context: 'title',
|
||||
@@ -150,11 +153,15 @@ const ActionsStep = React.memo(
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
{!user.isDeletionLocked && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
|
||||
{t('action.deleteUser', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu>
|
||||
</Popup.Content>
|
||||
</>
|
||||
|
||||
@@ -17,6 +17,9 @@ const Item = React.memo(
|
||||
organization,
|
||||
phone,
|
||||
isAdmin,
|
||||
isLocked,
|
||||
isRoleLocked,
|
||||
isDeletionLocked,
|
||||
emailUpdateForm,
|
||||
passwordUpdateForm,
|
||||
usernameUpdateForm,
|
||||
@@ -46,7 +49,7 @@ const Item = React.memo(
|
||||
<Table.Cell>{username || '-'}</Table.Cell>
|
||||
<Table.Cell>{email}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Radio toggle checked={isAdmin} onChange={handleIsAdminChange} />
|
||||
<Radio toggle checked={isAdmin} disabled={isRoleLocked} onChange={handleIsAdminChange} />
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign="right">
|
||||
<ActionsPopup
|
||||
@@ -57,6 +60,8 @@ const Item = React.memo(
|
||||
organization,
|
||||
phone,
|
||||
isAdmin,
|
||||
isLocked,
|
||||
isDeletionLocked,
|
||||
emailUpdateForm,
|
||||
passwordUpdateForm,
|
||||
usernameUpdateForm,
|
||||
@@ -88,6 +93,9 @@ Item.propTypes = {
|
||||
organization: PropTypes.string,
|
||||
phone: PropTypes.string,
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
isLocked: PropTypes.bool.isRequired,
|
||||
isRoleLocked: PropTypes.bool.isRequired,
|
||||
isDeletionLocked: PropTypes.bool.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
emailUpdateForm: PropTypes.object.isRequired,
|
||||
passwordUpdateForm: PropTypes.object.isRequired,
|
||||
|
||||
@@ -110,6 +110,9 @@ const UsersModal = React.memo(
|
||||
organization={item.organization}
|
||||
phone={item.phone}
|
||||
isAdmin={item.isAdmin}
|
||||
isLocked={item.isLocked}
|
||||
isRoleLocked={item.isRoleLocked}
|
||||
isDeletionLocked={item.isDeletionLocked}
|
||||
emailUpdateForm={item.emailUpdateForm}
|
||||
passwordUpdateForm={item.passwordUpdateForm}
|
||||
usernameUpdateForm={item.usernameUpdateForm}
|
||||
|
||||
@@ -12,14 +12,19 @@ export default {
|
||||
|
||||
/* Login */
|
||||
|
||||
LOGIN_INITIALIZE: 'LOGIN_INITIALIZE',
|
||||
AUTHENTICATE: 'AUTHENTICATE',
|
||||
AUTHENTICATE__SUCCESS: 'AUTHENTICATE__SUCCESS',
|
||||
AUTHENTICATE__FAILURE: 'AUTHENTICATE__FAILURE',
|
||||
USING_OIDC_AUTHENTICATE: 'USING_OIDC_AUTHENTICATE',
|
||||
USING_OIDC_AUTHENTICATE__SUCCESS: 'USING_OIDC_AUTHENTICATE__SUCCESS',
|
||||
USING_OIDC_AUTHENTICATE__FAILURE: 'USING_OIDC_AUTHENTICATE__FAILURE',
|
||||
AUTHENTICATE_ERROR_CLEAR: 'AUTHENTICATE_ERROR_CLEAR',
|
||||
|
||||
/* Core */
|
||||
|
||||
CORE_INITIALIZE: 'CORE_INITIALIZE',
|
||||
CORE_INITIALIZE__CONFIG_FETCH: 'CORE_INITIALIZE__CONFIG_FETCH',
|
||||
LOGOUT: 'LOGOUT',
|
||||
LOGOUT__ACCESS_TOKEN_INVALIDATE: 'LOGOUT__ACCESS_TOKEN_INVALIDATE',
|
||||
|
||||
|
||||
@@ -11,11 +11,11 @@ export default {
|
||||
/* Login */
|
||||
|
||||
AUTHENTICATE: `${PREFIX}/AUTHENTICATE`,
|
||||
USING_OIDC_AUTHENTICATE: `${PREFIX}/USING_OIDC_AUTHENTICATE`,
|
||||
AUTHENTICATE_ERROR_CLEAR: `${PREFIX}/AUTHENTICATE_ERROR_CLEAR`,
|
||||
|
||||
/* Core */
|
||||
|
||||
CORE_INITIALIZE: `${PREFIX}/CORE_INITIALIZE`,
|
||||
LOGOUT: `${PREFIX}/LOGOUT`,
|
||||
|
||||
/* Modals */
|
||||
|
||||
@@ -2,6 +2,7 @@ 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`;
|
||||
@@ -9,6 +10,7 @@ const CARDS = `${Config.BASE_PATH}/cards/:id`;
|
||||
export default {
|
||||
ROOT,
|
||||
LOGIN,
|
||||
OIDC_CALLBACK,
|
||||
PROJECTS,
|
||||
BOARDS,
|
||||
CARDS,
|
||||
|
||||
@@ -4,18 +4,18 @@ import selectors from '../selectors';
|
||||
import Core from '../components/Core';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const isCoreInitializing = selectors.selectIsCoreInitializing(state);
|
||||
const isInitializing = selectors.selectIsInitializing(state);
|
||||
const isSocketDisconnected = selectors.selectIsSocketDisconnected(state);
|
||||
const currentModal = selectors.selectCurrentModal(state);
|
||||
const currentProject = selectors.selectCurrentProject(state);
|
||||
const currentBoard = selectors.selectCurrentBoard(state);
|
||||
|
||||
return {
|
||||
isInitializing,
|
||||
isSocketDisconnected,
|
||||
currentModal,
|
||||
currentProject,
|
||||
currentBoard,
|
||||
isInitializing: isCoreInitializing,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import selectors from '../selectors';
|
||||
import entryActions from '../entry-actions';
|
||||
import Login from '../components/Login';
|
||||
|
||||
const mapStateToProps = ({
|
||||
const mapStateToProps = (state) => {
|
||||
const oidcConfig = selectors.selectOidcConfig(state);
|
||||
|
||||
const {
|
||||
ui: {
|
||||
authenticateForm: { data: defaultData, isSubmitting, error },
|
||||
authenticateForm: { data: defaultData, isSubmitting, isSubmittingUsingOidc, error },
|
||||
},
|
||||
}) => ({
|
||||
} = state;
|
||||
|
||||
return {
|
||||
defaultData,
|
||||
isSubmitting,
|
||||
isSubmittingUsingOidc,
|
||||
error,
|
||||
});
|
||||
withOidc: !!oidcConfig,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators(
|
||||
{
|
||||
onAuthenticate: entryActions.authenticate,
|
||||
onAuthenticateUsingOidc: entryActions.authenticateUsingOidc,
|
||||
onMessageDismiss: entryActions.clearAuthenticateError,
|
||||
},
|
||||
dispatch,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import selectors from '../selectors';
|
||||
import LoginWrapper from '../components/LoginWrapper';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const isInitializing = selectors.selectIsInitializing(state);
|
||||
|
||||
return {
|
||||
isInitializing,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(LoginWrapper);
|
||||
@@ -15,6 +15,7 @@ const mapStateToProps = (state) => {
|
||||
organization,
|
||||
language,
|
||||
subscribeToOwnCards,
|
||||
isLocked,
|
||||
isAvatarUpdating,
|
||||
emailUpdateForm,
|
||||
passwordUpdateForm,
|
||||
@@ -30,6 +31,7 @@ const mapStateToProps = (state) => {
|
||||
organization,
|
||||
language,
|
||||
subscribeToOwnCards,
|
||||
isLocked,
|
||||
isAvatarUpdating,
|
||||
emailUpdateForm,
|
||||
passwordUpdateForm,
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import EntryActionTypes from '../constants/EntryActionTypes';
|
||||
|
||||
const initializeCore = () => ({
|
||||
type: EntryActionTypes.CORE_INITIALIZE,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
const logout = () => ({
|
||||
type: EntryActionTypes.LOGOUT,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
export default {
|
||||
initializeCore,
|
||||
logout,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,11 @@ const authenticate = (data) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const authenticateUsingOidc = () => ({
|
||||
type: EntryActionTypes.USING_OIDC_AUTHENTICATE,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
const clearAuthenticateError = () => ({
|
||||
type: EntryActionTypes.AUTHENTICATE_ERROR_CLEAR,
|
||||
payload: {},
|
||||
@@ -14,5 +19,6 @@ const clearAuthenticateError = () => ({
|
||||
|
||||
export default {
|
||||
authenticate,
|
||||
authenticateUsingOidc,
|
||||
clearAuthenticateError,
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ export default {
|
||||
|
||||
translation: {
|
||||
common: {
|
||||
aboutPlanka: 'O aplikaci Planka',
|
||||
account: 'Účet',
|
||||
actions: 'Akce',
|
||||
addAttachment_title: 'Přidat přílohu',
|
||||
@@ -45,6 +46,9 @@ export default {
|
||||
background: 'Pozadí',
|
||||
board: 'Tabule',
|
||||
boardNotFound_title: 'Tabule nenalezena',
|
||||
canComment: 'Může komentovat',
|
||||
canEditContentOfBoard: 'Může upravovat obsah tabule',
|
||||
canOnlyViewBoard: 'Může zobrazit pouze tabuli',
|
||||
cardActions_title: 'Akce na kartě',
|
||||
cardNotFound_title: 'Karta nenalezena',
|
||||
cardOrActionAreDeleted: 'Karta nebo akce je smazána',
|
||||
@@ -68,14 +72,18 @@ export default {
|
||||
deleteTask_title: 'Smazat úkol',
|
||||
deleteUser_title: 'Smazat uživatele',
|
||||
description: 'Popis',
|
||||
detectAutomatically: 'Automatická detekce',
|
||||
dropFileToUpload: 'Přetáhnutím nahraj soubor',
|
||||
editor: 'Editor',
|
||||
editAttachment_title: 'Upravit přílohu',
|
||||
editAvatar_title: 'Upravit avatar',
|
||||
editBoard_title: 'Upravit tabuli',
|
||||
editDueDate_title: 'Upravit Termín do',
|
||||
editEmail_title: 'Upravit e-mail',
|
||||
editInformation_title: 'Upravit informace',
|
||||
editLabel_title: 'Upravit štítek',
|
||||
editPassword_title: 'Upravit heslo',
|
||||
editPermissions_title: 'Upravit oprávnění',
|
||||
editStopwatch_title: 'Upravit časovač',
|
||||
editUsername_title: 'Upravit uživatelské jméno',
|
||||
email: 'E-mail',
|
||||
@@ -89,10 +97,13 @@ export default {
|
||||
filterByLabels_title: 'Filtruj podle štítku',
|
||||
filterByMembers_title: 'Filtruj podle člena',
|
||||
fromComputer_title: 'Z počítače',
|
||||
fromTrello: 'Z Trella',
|
||||
general: 'Obecné',
|
||||
hours: 'Hodiny',
|
||||
importBoard_title: 'Importovat tabuli',
|
||||
invalidCurrentPassword: 'Neplatné aktuální heslo',
|
||||
labels: 'Štítky',
|
||||
language: 'Jazyk',
|
||||
leaveBoard_title: 'Opustit tabuli',
|
||||
leaveProject_title: 'Opustit projekt',
|
||||
list: 'Seznam',
|
||||
@@ -122,15 +133,20 @@ export default {
|
||||
projectNotFound_title: 'Projekt nenalezen',
|
||||
removeManager_title: 'Odstranit vedoucího',
|
||||
removeMember_title: 'Odstranit člena',
|
||||
searchLabels: 'Hledat štítky...',
|
||||
searchMembers: 'Hledat členy...',
|
||||
searchUsers: 'Hledat uživatele...',
|
||||
seconds: 'Vteřin',
|
||||
selectBoard: 'Vybrat tabuli',
|
||||
selectList: 'Vybrat seznam',
|
||||
selectPermissions_title: 'Vybrat oprávnění',
|
||||
selectProject: 'Vybrat projekt',
|
||||
settings: 'Nastavení',
|
||||
stopwatch: 'Časovač',
|
||||
subscribeToMyOwnCardsByDefault: 'Ve výchozím nastavení odebírat vlastní karty',
|
||||
taskActions_title: 'Akce na úkolu',
|
||||
tasks: 'Úkoly',
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Pro tuto přílohu není k dispozici žádný náhled',
|
||||
time: 'Čas',
|
||||
title: 'Titulek',
|
||||
userActions_title: 'Akce na uživateli',
|
||||
@@ -142,6 +158,8 @@ export default {
|
||||
username: 'Uživatelské jméno',
|
||||
usernameAlreadyInUse: 'Uživatelské jméno se již používá',
|
||||
users: 'Uživatelé',
|
||||
version: 'Verze',
|
||||
viewer: 'Prohlížeč',
|
||||
writeComment: 'Napsat komentář...',
|
||||
},
|
||||
|
||||
@@ -153,6 +171,7 @@ export default {
|
||||
addCard_title: 'Přidat kartu',
|
||||
addComment: 'Přidat komentář',
|
||||
addList: 'Přidat seznam',
|
||||
addMember: 'Přidat člena',
|
||||
addMoreDetailedDescription: 'Přidat další detailní popis',
|
||||
addTask: 'Přidat úkol',
|
||||
addToCard: 'Přidat na kartu',
|
||||
@@ -182,10 +201,14 @@ export default {
|
||||
editDueDate_title: 'Upravit termín do',
|
||||
editDescription_title: 'Upravit popis',
|
||||
editEmail_title: 'Upravit e-mail',
|
||||
editInformation_title: 'Upravit informace',
|
||||
editPassword_title: 'Upravit heslo',
|
||||
editPermissions: 'Upravit oprávnění',
|
||||
editStopwatch_title: 'Upravit časovač',
|
||||
editTitle_title: 'Upravit titulek',
|
||||
editUsername_title: 'Upravit uživatelské jméno',
|
||||
hideDetails: 'Skrýt detaily',
|
||||
import: 'Import',
|
||||
leaveBoard: 'Opustit tabuli',
|
||||
leaveProject: 'Opustit projekt',
|
||||
logOut_title: 'Odhlásit se',
|
||||
@@ -202,6 +225,7 @@ export default {
|
||||
save: 'Uložit',
|
||||
showAllAttachments: 'Zozbrazit všechny přílohy ({{hidden}} skryté)',
|
||||
showFewerAttachments: 'Zobrazit méně příloh',
|
||||
showDetails: 'Zobrazit detaily',
|
||||
start: 'Start',
|
||||
stop: 'Stop',
|
||||
subscribe: 'Odebírat',
|
||||
|
||||
@@ -11,10 +11,12 @@ export default {
|
||||
projectManagement: 'Project management',
|
||||
serverConnectionFailed: 'Server connection failed',
|
||||
unknownError: 'Unknown error, try again later',
|
||||
useSingleSignOn: 'Use single sign-on',
|
||||
},
|
||||
|
||||
action: {
|
||||
logIn: 'Log in',
|
||||
logInWithSSO: 'Log in with SSO',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,13 +8,15 @@ import it from './it';
|
||||
import ja from './ja';
|
||||
import ko from './ko';
|
||||
import pl from './pl';
|
||||
import ro from './ro';
|
||||
import ru from './ru';
|
||||
import sk from './sk';
|
||||
import sv from './sv';
|
||||
import tr from './tr';
|
||||
import uz from './uz';
|
||||
import zh from './zh';
|
||||
|
||||
const locales = [cs, da, de, en, es, fr, it, ja, ko, pl, ru, sk, sv, uz, zh];
|
||||
const locales = [cs, da, de, en, es, fr, it, ja, ko, pl, ro, ru, sk, sv, tr, uz, zh];
|
||||
|
||||
export default locales;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export default {
|
||||
time: 'HH:mm',
|
||||
dateTime: '$t(format:date) $t(format:time)',
|
||||
longDate: 'MMMMd日',
|
||||
longDateTime: "MMMMd'日 ' HH:MM",
|
||||
longDateTime: "MMMMd'日 ' HH:mm",
|
||||
},
|
||||
|
||||
translation: {
|
||||
|
||||
+143
-125
@@ -1,7 +1,5 @@
|
||||
import dateFns from 'date-fns/locale/ko';
|
||||
|
||||
/* Translated by Park Seok Ho */
|
||||
|
||||
export default {
|
||||
dateFns,
|
||||
|
||||
@@ -15,205 +13,225 @@ export default {
|
||||
|
||||
translation: {
|
||||
common: {
|
||||
aboutPlanka: 'Planka에 대해서',
|
||||
account: '계정',
|
||||
actions: '활동',
|
||||
addAttachment_title: '첨부파일 추가',
|
||||
addAttachment_title: '첨부 파일 추가',
|
||||
addComment: '댓글',
|
||||
addManager_title: '관리자 추가',
|
||||
addMember_title: '회원 추가',
|
||||
addManager_title: '매니저 추가',
|
||||
addMember_title: '멤버 추가',
|
||||
addUser_title: '사용자 추가',
|
||||
administrator: '시스템 관리자',
|
||||
all: 'All',
|
||||
administrator: '관리자',
|
||||
all: '모두',
|
||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||
'모든 변경 사항은 연결이 복원된 후 자동으로 저장됩니다',
|
||||
areYouSureYouWantToDeleteThisAttachment: '첨부 파일을 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisBoard: '보드를 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisCard: '항목카드를 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisComment: '댓글을 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisLabel: '라벨을 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisList: '리스트를 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisProject: '프로젝트를 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisTask: '태스트를 삭제하겠습니까?',
|
||||
areYouSureYouWantToDeleteThisUser: '이 유저를 삭제하겠습니까?',
|
||||
'연결이 복원되면 모든 변경 사항이 자동으로 저장됩니다.',
|
||||
areYouSureYouWantToDeleteThisAttachment: '해당하는 첨부 파일을 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisBoard: '해당하는 보드를 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisCard: '해당하는 항목카드를 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisComment: '해당하는 댓글을 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisLabel: '해당하는 레이블을 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisList: '해당하는 리스트를 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisProject: '해당하는 프로젝트를 삭제하시겠습니까?',
|
||||
areYouSureYouWantToDeleteThisTask: '해당하는 태스크를 삭제하겠습니까?',
|
||||
areYouSureYouWantToDeleteThisUser: '해당하는 사용자를 삭제하겠습니까?',
|
||||
areYouSureYouWantToLeaveBoard: '이 보드에서 나가시겠습니까?',
|
||||
areYouSureYouWantToLeaveProject: '이 프로젝트에서 나가시겠습니까?',
|
||||
areYouSureYouWantToRemoveThisManagerFromProject: '프로젝트에서 관리자를 제거하시겠습니까?',
|
||||
areYouSureYouWantToRemoveThisMemberFromBoard: '이 회원을 보드에서 탈퇴시키겠습니까?',
|
||||
attachment: '첨부파일',
|
||||
attachments: '첨부파일',
|
||||
authentication: '계정 관리',
|
||||
background: 'Background',
|
||||
areYouSureYouWantToRemoveThisManagerFromProject:
|
||||
'해당하는 프로젝트에서 매니저를 제거하시겠습니까?',
|
||||
areYouSureYouWantToRemoveThisMemberFromBoard: '해당하는 멤버를 보드에서 삭제하시겠습니까?',
|
||||
attachment: '첨부',
|
||||
attachments: '첨부 파일',
|
||||
authentication: '인증',
|
||||
background: '배경',
|
||||
board: '보드',
|
||||
boardNotFound_title: '보드를 찾을 수 없습니다.',
|
||||
cardActions_title: 'Card Actions',
|
||||
cardNotFound_title: '항목카드를 찾을 수 없습니다.',
|
||||
cardOrActionAreDeleted: '항목카드 혹은 활동내역이 삭제되었습니다.',
|
||||
color: 'Color',
|
||||
createBoard_title: '보드명칭을 입력하세요.',
|
||||
createLabel_title: '라벨 생성',
|
||||
createNewOneOrSelectExistingOne: '새 항목을 만들거나<br />기존 항목을 선택하세요.',
|
||||
boardNotFound_title: '보드를 찾을 수 없음',
|
||||
canComment: '댓글 달기 가능',
|
||||
canEditContentOfBoard: '보드 콘텐츠 수정 가능',
|
||||
canOnlyViewBoard: '보드를 보는 것만 가능합니다.',
|
||||
cardActions_title: '카드 액션',
|
||||
cardNotFound_title: '카드를 찾을 수 없음',
|
||||
cardOrActionAreDeleted: '카드 또는 액션이 삭제됨',
|
||||
color: '색상',
|
||||
createBoard_title: '보드 생성',
|
||||
createLabel_title: '레이블 생성',
|
||||
createNewOneOrSelectExistingOne: '새로 생성하거나<br />기존 항목을 선택하세요.',
|
||||
createProject_title: '프로젝트 생성',
|
||||
createTextFile_title: 'Text File 생성',
|
||||
createTextFile_title: '텍스트 파일 생성',
|
||||
currentPassword: '현재 비밀번호',
|
||||
dangerZone_title: 'Danger Zone',
|
||||
dangerZone_title: '위험 구역',
|
||||
date: '날짜',
|
||||
dueDate_title: '완료일',
|
||||
deleteAttachment_title: '첨부파일 삭제',
|
||||
dueDate_title: '마감일',
|
||||
deleteAttachment_title: '첨부 파일 삭제',
|
||||
deleteBoard_title: '보드 삭제',
|
||||
deleteCard_title: '항목 카드 삭제',
|
||||
deleteCard_title: '카드 삭제',
|
||||
deleteComment_title: '댓글 삭제',
|
||||
deleteLabel_title: '라벨 삭제',
|
||||
deleteLabel_title: '레이블 삭제',
|
||||
deleteList_title: '리스트 삭제',
|
||||
deleteProject_title: '프로젝트 삭제',
|
||||
deleteTask_title: '태스크 삭제',
|
||||
deleteUser_title: '사용자 삭제',
|
||||
description: '설명',
|
||||
detectAutomatically: '자동으로 감지',
|
||||
dropFileToUpload: '업로드 하기 위해서 파일을 Drop 하세요.',
|
||||
dropFileToUpload: '업로드할 파일 놓기',
|
||||
editor: '편집기',
|
||||
editAttachment_title: '첨부 수정',
|
||||
editAvatar_title: '프로필사진 수정',
|
||||
editAvatar_title: '아바타 수정',
|
||||
editBoard_title: '보드 수정',
|
||||
editDueDate_title: '완료일 수정',
|
||||
editEmail_title: '메일 수정',
|
||||
editDueDate_title: '마감일 수정',
|
||||
editEmail_title: '이메일 수정',
|
||||
editInformation_title: '정보 수정',
|
||||
editLabel_title: '라벨 수정',
|
||||
editLabel_title: '레이블 수정',
|
||||
editPassword_title: '비밀번호 수정',
|
||||
editStopwatch_title: '타이머 수정',
|
||||
editUsername_title: '사용자이름 수정',
|
||||
email: '메일',
|
||||
emailAlreadyInUse: '기 가입된 이메일이 존재합니다.',
|
||||
enterCardTitle: '항목카드명을 입력하세요...',
|
||||
enterDescription: '자세한 설명을 작성하세요...',
|
||||
enterFilename: '파일이름을 입력하세요...',
|
||||
enterListTitle: '리스트명을 입력 (예,todo,완료,이슈등...)',
|
||||
enterProjectTitle: '프로젝트명칭을 입력하세요.',
|
||||
enterTaskDescription: '태스크 설명을 입력하세요...',
|
||||
filterByLabels_title: '라벨 검색',
|
||||
filterByMembers_title: '회원으로 검색',
|
||||
fromComputer_title: 'PC로 부터',
|
||||
general: 'General',
|
||||
editPermissions_title: '권한 수정',
|
||||
editStopwatch_title: '스톱워치 수정',
|
||||
editUsername_title: '사용자 이름 수정',
|
||||
email: '이메일',
|
||||
emailAlreadyInUse: '이미 사용 중인 이메일입니다.',
|
||||
enterCardTitle: '카드 제목 입력... [Ctrl+Enter]로 자동으로 열기',
|
||||
enterDescription: '설명 입력...',
|
||||
enterFilename: '파일 이름 입력...',
|
||||
enterListTitle: '리스트 제목 입력...',
|
||||
enterProjectTitle: '프로젝트 제목 입력',
|
||||
enterTaskDescription: '태스크 설명 입력...',
|
||||
filterByLabels_title: '레이블로 필터링',
|
||||
filterByMembers_title: '멤버로 필터링',
|
||||
fromComputer_title: 'PC에서',
|
||||
fromTrello: '트렐로에서',
|
||||
general: '일반',
|
||||
hours: '시',
|
||||
invalidCurrentPassword: '현재 패스워드는 유효하지 않습니다.',
|
||||
labels: '라벨',
|
||||
language: '언어 선택',
|
||||
leaveBoard_title: '보드 탈퇴',
|
||||
leaveProject_title: '프로젝트 탈퇴',
|
||||
list: '내역',
|
||||
listActions_title: '활용 내역',
|
||||
managers: '관리자',
|
||||
members: '팀원',
|
||||
importBoard_title: '보드 가져오기',
|
||||
invalidCurrentPassword: '현재 비밀번호가 일치하지 않습니다.',
|
||||
labels: '레이블',
|
||||
language: '언어',
|
||||
leaveBoard_title: '보드 떠나기',
|
||||
leaveProject_title: '프로젝트 떠나기',
|
||||
list: '리스트',
|
||||
listActions_title: '리스트 액션',
|
||||
managers: '매니저',
|
||||
members: '멤버',
|
||||
minutes: '분',
|
||||
moveCard_title: '항목카드 이동',
|
||||
moveCard_title: '카드 옮기기',
|
||||
name: '이름',
|
||||
newEmail: '새로운 메일',
|
||||
newPassword: '새로운 패스워드',
|
||||
newEmail: '새로운 이메일',
|
||||
newPassword: '새로운 비밀번호',
|
||||
newUsername: '새로운 사용자 이름',
|
||||
noConnectionToServer: '서버에 연결할수 없습니다.',
|
||||
noBoards: '보드가 존재하지 않습니다.',
|
||||
noLists: '리스트가 존재하지 않습니다.',
|
||||
noProjects: '프로젝트가 존재하지 않습니다.',
|
||||
noConnectionToServer: '서버에 연결되지 않음',
|
||||
noBoards: '보드가 존재하지 않음',
|
||||
noLists: '리스트가 존재하지 않음',
|
||||
noProjects: '프로젝트가 존재하지 않음',
|
||||
notifications: '알림',
|
||||
noUnreadNotifications: '알림을 모두 확인했습니다.',
|
||||
openBoard_title: '보드열기',
|
||||
noUnreadNotifications: '읽지 않은 알림 없음',
|
||||
openBoard_title: '보드 열기',
|
||||
optional_inline: '옵션',
|
||||
organization: '조직',
|
||||
phone: '전화번호',
|
||||
preferences: '환경설정',
|
||||
preferences: '기본설정',
|
||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||
'Tip: 클립보드에서 첨부 파일을 추가하려면 Ctrl-V(Mac의 경우 Cmd-V) 입니다..',
|
||||
'팁: 클립보드에서 첨부 파일을 추가하려면 Ctrl-V(Mac의 경우 Cmd-V)를 누릅니다.',
|
||||
project: '프로젝트',
|
||||
projectNotFound_title: '프로젝트가 존재하지 않습니다.',
|
||||
removeManager_title: '관리자 삭제',
|
||||
removeMember_title: '맴버 삭제',
|
||||
projectNotFound_title: '프로젝트를 찾을 수 없음',
|
||||
removeManager_title: '매니저 삭제',
|
||||
removeMember_title: '멤버 삭제',
|
||||
searchLabels: '레이블 검색하기...',
|
||||
searchMembers: '멤버 검색하기...',
|
||||
searchUsers: '사용자 검색하기...',
|
||||
seconds: '초',
|
||||
selectBoard: '보드 선택',
|
||||
selectList: '리스트 선택',
|
||||
selectPermissions_title: '권한 선택',
|
||||
selectProject: '프로젝트 선택',
|
||||
settings: '설정',
|
||||
stopwatch: '활동시간',
|
||||
subscribeToMyOwnCardsByDefault: '내 항목카드는 기본으로 구독',
|
||||
taskActions_title: '태스크 활동',
|
||||
stopwatch: '스톱워치',
|
||||
subscribeToMyOwnCardsByDefault: '기본적으로 내 카드 구독하기',
|
||||
taskActions_title: '태스크 액션',
|
||||
tasks: '태스크',
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'이 첨부 파일에 사용할 수 있는 미리보기가 없습니다.',
|
||||
thereIsNoPreviewAvailableForThisAttachment: '이 첨부 파일은 미리 보기를 사용할 수 없습니다.',
|
||||
time: '시간',
|
||||
title: '제목',
|
||||
userActions_title: '',
|
||||
userAddedThisCardToList: '<0>{{user}}</0><1>이 카드를 {{list}}</1>에 추가했습니다.',
|
||||
userActions_title: '사용자 액션',
|
||||
userAddedThisCardToList: '<0>{{user}}</0><1>님이 {{list}}</1>에 카드를 추가했습니다.',
|
||||
userLeftNewCommentToCard:
|
||||
'{{user}}이(가) <2>{{card}}</2>에 «{{comment}}»라는 댓글을 남겼습니다',
|
||||
userMovedCardFromListToList: '{{user}} moved <2>{{card}}</2> from {{fromList}} to {{toList}}',
|
||||
'{{user}}님이 <2>{{card}}</2>에 «{{comment}}»라는 댓글을 남겼습니다.',
|
||||
userMovedCardFromListToList:
|
||||
'{{user}}님이 {{fromList}}에서 {{toList}}로 <2>{{card}}</2>를 옮겼습니다.',
|
||||
userMovedThisCardFromListToList:
|
||||
'<0>{{user}}</0><1> 이 항목카드를 {{fromList}} 에서 {{toList}} 으로 이동했습니다. </1>',
|
||||
username: '아이디',
|
||||
usernameAlreadyInUse: '이미 사용중인 사용자입니다.',
|
||||
'<0>{{user}}</0><1> 님이 {{fromList}}에서 {{toList}}로 이 카드를 옮겼습니다.</1>',
|
||||
username: '사용자 이름',
|
||||
usernameAlreadyInUse: '이미 사용중인 사용자 이름입니다.',
|
||||
users: '사용자',
|
||||
writeComment: '댓글 작성...',
|
||||
version: '버전',
|
||||
viewer: '열람자',
|
||||
writeComment: '댓글 쓰기...',
|
||||
},
|
||||
|
||||
action: {
|
||||
addAnotherCard: '다른 항목카드 추가',
|
||||
addAnotherCard: '다른 카드 추가',
|
||||
addAnotherList: '다른 리스트 추가',
|
||||
addAnotherTask: '다른 TASK 추가',
|
||||
addCard: '항목카드 추가',
|
||||
addCard_title: '항목카드 추가',
|
||||
addAnotherTask: '다른 태스크 추가',
|
||||
addCard: '카드 추가',
|
||||
addCard_title: '카드 추가',
|
||||
addComment: '댓글 추가',
|
||||
addList: '리스트 추가',
|
||||
addMember: '멤버 추가',
|
||||
addMoreDetailedDescription: '자세한 설명 추가',
|
||||
addTask: '태스트 추가',
|
||||
addToCard: '항목카드 추가',
|
||||
addTask: '태스크 추가',
|
||||
addToCard: '카드에 추가',
|
||||
addUser: '사용자 추가',
|
||||
createBoard: '보드 생성',
|
||||
createFile: '파일 생성',
|
||||
createLabel: '라벨 생성',
|
||||
createNewLabel: '새로운 라벨 생성',
|
||||
createLabel: '레이블 생성',
|
||||
createNewLabel: '새로운 레이블 생성',
|
||||
createProject: '프로젝트 생성',
|
||||
delete: '삭제',
|
||||
deleteAttachment: '첨부삭제',
|
||||
deleteAvatar: '프로필 사진 삭제',
|
||||
deleteAttachment: '첨부 삭제',
|
||||
deleteAvatar: '아바타 삭제',
|
||||
deleteBoard: '보드 삭제',
|
||||
deleteCard: '항목카드 삭제',
|
||||
deleteCard_title: '항목카드 삭제',
|
||||
deleteCard: '카드 삭제',
|
||||
deleteCard_title: '카드 삭제',
|
||||
deleteComment: '댓글 삭제',
|
||||
deleteImage: '이미지 삭제',
|
||||
deleteLabel: '라벨 삭제',
|
||||
deleteLabel: '레이블 삭제',
|
||||
deleteList: '리스트 삭제',
|
||||
deleteList_title: '리스트 삭제',
|
||||
deleteProject: '프로젝트 삭제',
|
||||
deleteProject_title: '프로젝트 삭제',
|
||||
deleteTask: '태스트 삭제',
|
||||
deleteTask_title: '태스트 삭제',
|
||||
deleteTask: '태스크 삭제',
|
||||
deleteTask_title: '태스크 삭제',
|
||||
deleteUser: '사용자 삭제',
|
||||
edit: '수정',
|
||||
editDueDate_title: '완료일 수정',
|
||||
editDueDate_title: '마감일 수정',
|
||||
editDescription_title: '설명 수정',
|
||||
editEmail_title: '메일 수정',
|
||||
editEmail_title: '이메일 수정',
|
||||
editInformation_title: '정보 수정',
|
||||
editPassword_title: '패스워드 수정',
|
||||
editStopwatch_title: '시간 수정',
|
||||
editPassword_title: '비밀번호 수정',
|
||||
editPermissions: '권한 수정',
|
||||
editStopwatch_title: '스톱워치 수정',
|
||||
editTitle_title: '제목 수정',
|
||||
editUsername_title: '이름 수정',
|
||||
leaveBoard: '보드에서 나가기',
|
||||
leaveProject: '프로젝트에서 나가기',
|
||||
editUsername_title: '사용자 이름 수정',
|
||||
hideDetails: '세부 사항 숨기기',
|
||||
import: '가져오기',
|
||||
leaveBoard: '보드 나가기',
|
||||
leaveProject: '프로젝트 나가기',
|
||||
logOut_title: '로그아웃',
|
||||
makeCover_title: '커버 만들기',
|
||||
move: '이동',
|
||||
moveCard_title: '항목카드 이동',
|
||||
moveCard_title: '카드 이동',
|
||||
remove: '제거',
|
||||
removeBackground: '백그라운드 제거',
|
||||
removeBackground: '배경 제거',
|
||||
removeCover_title: '커버 제거',
|
||||
removeFromBoard: '보드에서 제거',
|
||||
removeFromProject: '프로젝트에서 제거',
|
||||
removeManager: '관리자 삭제',
|
||||
removeMember: '멤버 삭제',
|
||||
removeFromBoard: '보드 제거',
|
||||
removeFromProject: '프로젝트 제거',
|
||||
removeManager: '매니저 제거',
|
||||
removeMember: '멤버 제거',
|
||||
save: '저장',
|
||||
showAllAttachments: '모든 첨부파일 표시 ({{hidden}} hidden)',
|
||||
showFewerAttachments: '일부 첨부파일만 표시',
|
||||
showAllAttachments: '모든 첨부 파일 표시 ({{hidden}} 숨겨짐)',
|
||||
showDetails: '세부 사항 보기',
|
||||
showFewerAttachments: '일부 첨부 파일만 표시',
|
||||
start: '시작',
|
||||
stop: '끝',
|
||||
stop: '종료',
|
||||
subscribe: '구독하기',
|
||||
unsubscribe: '구독 취소',
|
||||
uploadNewAvatar: '새로운 프로필 사진 업로드',
|
||||
unsubscribe: '구독취소',
|
||||
uploadNewAvatar: '새로운 아바타 업로드',
|
||||
uploadNewImage: '새로운 이미지 업로드',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
/* Translated by Park Seok Ho */
|
||||
|
||||
export default {
|
||||
translation: {
|
||||
common: {
|
||||
emailOrUsername: '이메일 혹은 아이디',
|
||||
invalidEmailOrUsername: '이메일이나 사용자 이름이 유효하지 않습니다.',
|
||||
invalidPassword: '잘못된 비밀번호',
|
||||
emailOrUsername: '이메일 혹은 사용자 이름',
|
||||
invalidEmailOrUsername: '이메일 혹은 사용자 이름이 유효하지 않습니다.',
|
||||
invalidPassword: '유효하지 않은 비밀번호',
|
||||
logInToPlanka: 'Planka에 로그인',
|
||||
noInternetConnection: '인터넷에 연결되지 않음',
|
||||
pageNotFound_title: '페이지를 찾을수 없습니다.',
|
||||
password: '패스워드',
|
||||
noInternetConnection: '인터넷 연결이 없음',
|
||||
pageNotFound_title: '페이지를 찾을 수 없습니다.',
|
||||
password: '비밀번호',
|
||||
projectManagement: '프로젝트 관리',
|
||||
serverConnectionFailed: '서버 연결 실패',
|
||||
unknownError: '알 수 없는 오류가 발생하였습니다, 나중에 다시 시도하세요.',
|
||||
serverConnectionFailed: '서버 연결에 실패함',
|
||||
unknownError: '알 수 없는 오류, 나중에 다시 시도하십시오.',
|
||||
useSingleSignOn: 'single sign-on(SSO) 사용',
|
||||
},
|
||||
|
||||
action: {
|
||||
logIn: '로그인',
|
||||
logInWithSSO: 'single sign-on(SSO)으로 로그인',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
import dateFns from 'date-fns/locale/ro';
|
||||
|
||||
export default {
|
||||
dateFns,
|
||||
|
||||
format: {
|
||||
date: 'P',
|
||||
time: 'HH:mm',
|
||||
dateTime: '$t(format:date) $t(format:time)',
|
||||
longDate: 'd MMM',
|
||||
longDateTime: "d MMMM 'в' p",
|
||||
},
|
||||
|
||||
translation: {
|
||||
common: {
|
||||
aboutPlanka: 'Despre Planka',
|
||||
account: 'Cont',
|
||||
actions: 'Acțiuni',
|
||||
addAttachment_title: 'Adauga atasament',
|
||||
addComment: 'Adaugă comentariu',
|
||||
addManager_title: 'Adaugă Manager',
|
||||
addMember_title: 'Adaugă Membru',
|
||||
addUser_title: 'Adaugă Utilizator',
|
||||
administrator: 'Administrator',
|
||||
all: 'Toate',
|
||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||
'Toate modificarile vor fi salvate automat<br />dupa restabilirea conexiunii',
|
||||
areYouSureYouWantToDeleteThisAttachment: 'Sigur doriți să ștergeți acest atașament?',
|
||||
areYouSureYouWantToDeleteThisBoard: 'Sigur doriți să ștergeți acest panou?',
|
||||
areYouSureYouWantToDeleteThisCard: 'Sigur doriți să ștergeți acest card?',
|
||||
areYouSureYouWantToDeleteThisComment: 'Sigur doriți să ștergeți acest comentariu?',
|
||||
areYouSureYouWantToDeleteThisLabel: 'Sigur doriți să ștergeți această etichetă?',
|
||||
areYouSureYouWantToDeleteThisList: 'Sigur doriți să ștergeți această listă?',
|
||||
areYouSureYouWantToDeleteThisProject: 'Sigur doriți să ștergeți acest proiect?',
|
||||
areYouSureYouWantToDeleteThisTask: 'Sigur doriți să ștergeți această sarcină?',
|
||||
areYouSureYouWantToDeleteThisUser: 'Sigur doriți să ștergeți acest utilizator?',
|
||||
areYouSureYouWantToLeaveBoard: 'Ești sigur că vrei să părăsești tabla?',
|
||||
areYouSureYouWantToLeaveProject: 'Ești sigur că vrei să părăsești proiectul?',
|
||||
areYouSureYouWantToRemoveThisManagerFromProject:
|
||||
'Sigur doriți să eliminați acest manager din proiect?',
|
||||
areYouSureYouWantToRemoveThisMemberFromBoard:
|
||||
'Sigur doriți să eliminați acest membru din consiliu?',
|
||||
attachment: 'Atașament',
|
||||
attachments: 'Atasamente',
|
||||
authentication: 'Autentificare',
|
||||
background: 'Fundal',
|
||||
board: 'Tabla',
|
||||
boardNotFound_title: 'Tabla nu a fost găsită',
|
||||
canComment: 'Poate comenta',
|
||||
canEditContentOfBoard: 'Poate edita conținutul tablei',
|
||||
canOnlyViewBoard: 'Poate doar vizualiza tabla',
|
||||
cardActions_title: 'Acțiuni cu carduri',
|
||||
cardNotFound_title: 'Cardul nu a fost găsit',
|
||||
cardOrActionAreDeleted: 'Cardul sau acțiunea sunt șterse',
|
||||
color: 'Culoarea',
|
||||
createBoard_title: 'Creare Tabla',
|
||||
createLabel_title: 'Creați etichetă',
|
||||
createNewOneOrSelectExistingOne: 'Crează unu nou sau selectează<br />unu deja existent',
|
||||
createProject_title: 'Crează Proiect',
|
||||
createTextFile_title: 'Crează un Fișier Text',
|
||||
currentPassword: 'Parola Curentă',
|
||||
dangerZone_title: 'Zona periculoasă',
|
||||
date: 'Data',
|
||||
dueDate_title: 'Data scadentă',
|
||||
deleteAttachment_title: 'Ștergeți atașamentul',
|
||||
deleteBoard_title: 'Ștergeți Tabla',
|
||||
deleteCard_title: 'Ștergeți cardul',
|
||||
deleteComment_title: 'Șterge comentariul',
|
||||
deleteLabel_title: 'Ștergeți eticheta',
|
||||
deleteList_title: 'Șterge lista',
|
||||
deleteProject_title: 'Șterge proiectul',
|
||||
deleteTask_title: 'Ștergeți sarcina',
|
||||
deleteUser_title: 'Șterge utilizator',
|
||||
description: 'Descriere',
|
||||
detectAutomatically: 'Detectează automat',
|
||||
dropFileToUpload: 'Aruncă fișierul pentru a încărca',
|
||||
editor: 'Editor',
|
||||
editAttachment_title: 'Editați atașamentul',
|
||||
editAvatar_title: 'Editați avatarul',
|
||||
editBoard_title: 'Editați tabla',
|
||||
editDueDate_title: 'Editați data scadentă',
|
||||
editEmail_title: 'Editați E-mail',
|
||||
editInformation_title: 'Editați informații',
|
||||
editLabel_title: 'Editați eticheta',
|
||||
editPassword_title: 'Editați parola',
|
||||
editPermissions_title: 'Editați permisiunile',
|
||||
editStopwatch_title: 'Editați cronometrul',
|
||||
editUsername_title: 'Editați numele de utilizator',
|
||||
email: 'E-mail',
|
||||
emailAlreadyInUse: 'E-mail deja utilizat',
|
||||
enterCardTitle: 'Introduceți titlul cardului... [Ctrl+Enter] pentru a se deschide automat',
|
||||
enterDescription: 'Introduceți descrierea...',
|
||||
enterFilename: 'Introduceți numele fișierului',
|
||||
enterListTitle: 'Introduceți titlul listei...',
|
||||
enterProjectTitle: 'Introduceți titlul proiectului',
|
||||
enterTaskDescription: 'Introduceți descrierea sarcinii...',
|
||||
filterByLabels_title: 'Filtrați după etichete',
|
||||
filterByMembers_title: 'Filtrați după membri',
|
||||
fromComputer_title: 'De pe computer',
|
||||
fromTrello: 'De pe Trello',
|
||||
general: 'General',
|
||||
hours: 'Ore',
|
||||
importBoard_title: 'Import Tabla',
|
||||
invalidCurrentPassword: 'Parolă actuală nevalidă',
|
||||
labels: 'Etichete',
|
||||
language: 'Limba',
|
||||
leaveBoard_title: 'Părăsiți Tabla',
|
||||
leaveProject_title: 'Părăsiți proiectul',
|
||||
list: 'Lista',
|
||||
listActions_title: 'Listează acțiuni',
|
||||
managers: 'Managerii',
|
||||
members: 'Membri',
|
||||
minutes: 'Minute',
|
||||
moveCard_title: 'Mutați cardul',
|
||||
name: 'Nume',
|
||||
newEmail: 'Email nou',
|
||||
newPassword: 'Parolă Nouă',
|
||||
newUsername: 'Nume de utilizator nou',
|
||||
noConnectionToServer: 'Nicio conexiune la server',
|
||||
noBoards: 'Fără Table',
|
||||
noLists: 'Fără Liste',
|
||||
noProjects: 'Fără proiecte',
|
||||
notifications: 'Notificări',
|
||||
noUnreadNotifications: 'Fără notificări necitite',
|
||||
openBoard_title: 'Tablă deschisă',
|
||||
optional_inline: 'optional',
|
||||
organization: 'Organizatia',
|
||||
phone: 'Telefon',
|
||||
preferences: 'Preferințe',
|
||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||
'Sfat: apăsați Ctrl-V (Cmd-V pe Mac) pentru a adăuga un atașament din clipboard',
|
||||
project: 'Proiect',
|
||||
projectNotFound_title: 'Proiectul nu a fost găsit',
|
||||
removeManager_title: 'Eliminați Manager',
|
||||
removeMember_title: 'Eliminați membru',
|
||||
searchLabels: 'Căutați etichete...',
|
||||
searchMembers: 'Căutați membri...',
|
||||
searchUsers: 'Căutați utilizatori...',
|
||||
seconds: 'Secunde',
|
||||
selectBoard: 'Selectați tabla',
|
||||
selectList: 'Selectați lista',
|
||||
selectPermissions_title: 'Selectați Permisiuni',
|
||||
selectProject: 'Selectați proiectul',
|
||||
settings: 'Setări',
|
||||
stopwatch: 'Cronometru',
|
||||
subscribeToMyOwnCardsByDefault: 'Abonați-vă la propriile carduri în mod implicit',
|
||||
taskActions_title: 'Acțiuni de sarcină',
|
||||
tasks: 'Sarcini',
|
||||
thereIsNoPreviewAvailableForThisAttachment:
|
||||
'Nu există nicio previzualizare disponibilă pentru acest atașament',
|
||||
time: 'Timp',
|
||||
title: 'Titlu',
|
||||
userActions_title: 'Acțiunile utilizatorului',
|
||||
userAddedThisCardToList: '<0>{{user}}</0><1> a adăugat acest card în {{list}}</1>',
|
||||
userLeftNewCommentToCard:
|
||||
'{{user}} a lăsat un nou comentariu «{{comment}}» în <2>{{card}}</2>',
|
||||
userMovedCardFromListToList:
|
||||
'{{user}} a mutat <2>{{card}}</2> din {{fromList}} în {{toList}}',
|
||||
userMovedThisCardFromListToList:
|
||||
'<0>{{user}}</0><1> a mutat aceast card din {{fromList}} în {{toList}}</1>',
|
||||
username: 'Nume utilizator',
|
||||
usernameAlreadyInUse: 'Nume utilizator deja exista',
|
||||
users: 'Utilizatori',
|
||||
version: 'Versiunea',
|
||||
viewer: 'Vizualizator',
|
||||
writeComment: 'Scrie un comentariu...',
|
||||
},
|
||||
|
||||
action: {
|
||||
addAnotherCard: 'Adăugați un alt card',
|
||||
addAnotherList: 'Adăugați o altă listă',
|
||||
addAnotherTask: 'Adăugați o altă sarcină',
|
||||
addCard: 'Adăugați card',
|
||||
addCard_title: 'Adăugați card',
|
||||
addComment: 'Adauga comentariu',
|
||||
addList: 'Adăugați listă',
|
||||
addMember: 'Adăugați membru',
|
||||
addMoreDetailedDescription: 'Adăugați descrierea mai detaliata',
|
||||
addTask: 'Adăugați sarcină',
|
||||
addToCard: 'Adauga în card',
|
||||
addUser: 'Adăugați utilizator',
|
||||
createBoard: 'Creați tablă',
|
||||
createFile: 'Creați fișier',
|
||||
createLabel: 'Creați eticheta',
|
||||
createNewLabel: 'Creați o nouă etichetă',
|
||||
createProject: 'Creați proiect',
|
||||
delete: 'Ștergeți',
|
||||
deleteAttachment: 'Ștergeți atașamentul',
|
||||
deleteAvatar: 'Ștergeți avatarul',
|
||||
deleteBoard: 'Ștergeți tabla',
|
||||
deleteCard: 'Ștergeți cardul',
|
||||
deleteCard_title: 'Ștergeți Cardul',
|
||||
deleteComment: 'Șterge comentariu',
|
||||
deleteImage: 'Șterge imaginea',
|
||||
deleteLabel: 'Ștergeți Eticheta',
|
||||
deleteList: 'Ștergeți lista',
|
||||
deleteList_title: 'Ștergeți Lista',
|
||||
deleteProject: 'Ștergeți proiectul',
|
||||
deleteProject_title: 'Ștergeți Proiectul',
|
||||
deleteTask: 'Ștergeți sarcina',
|
||||
deleteTask_title: 'Ștergeți Sarcina',
|
||||
deleteUser: 'Ștergeți utilizatorul',
|
||||
edit: 'Editați',
|
||||
editDueDate_title: 'Editați data scadentă',
|
||||
editDescription_title: 'Editați descrierea',
|
||||
editEmail_title: 'Editați e-mailul',
|
||||
editInformation_title: 'Editați informații',
|
||||
editPassword_title: 'Editați parola',
|
||||
editPermissions: 'Editați permisiunile',
|
||||
editStopwatch_title: 'Editați cronometrul',
|
||||
editTitle_title: 'Editați Titlul',
|
||||
editUsername_title: 'Editați Numele Utilizator',
|
||||
hideDetails: 'Ascunde detalii',
|
||||
import: 'Import',
|
||||
leaveBoard: 'Părăsiți bordul',
|
||||
leaveProject: 'Părăsiți proiect',
|
||||
logOut_title: 'Deconectați-vă',
|
||||
makeCover_title: 'Faceți Coperta',
|
||||
move: 'Mutați',
|
||||
moveCard_title: 'Mutați cardul',
|
||||
remove: 'Eliminați',
|
||||
removeBackground: 'Eliminați fundalul',
|
||||
removeCover_title: 'Eliminați Coperta',
|
||||
removeFromBoard: 'Eliminați din Tabla',
|
||||
removeFromProject: 'Eliminați din proiect',
|
||||
removeManager: 'Eliminați managerul',
|
||||
removeMember: 'Eliminați membrul',
|
||||
save: 'Salveaza',
|
||||
showAllAttachments: 'Afișați toate atașamentele ({{hidden}} ascunse)',
|
||||
showDetails: 'Arată detalii',
|
||||
showFewerAttachments: 'Afișați mai puține atașamente',
|
||||
start: 'Start',
|
||||
stop: 'Stop',
|
||||
subscribe: 'Abonati-va',
|
||||
unsubscribe: 'Dezabonați-vă',
|
||||
uploadNewAvatar: 'Încărcați un avatar nou',
|
||||
uploadNewImage: 'Încărcați o nouă imagine',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import login from './login';
|
||||
|
||||
export default {
|
||||
language: 'ro',
|
||||
country: 'ro',
|
||||
name: 'Română',
|
||||
embeddedLocale: login,
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
export default {
|
||||
translation: {
|
||||
common: {
|
||||
emailOrUsername: 'E-mail sau nume de utilizator',
|
||||
invalidEmailOrUsername: 'E-mail sau nume de utilizator introduse greșit',
|
||||
invalidPassword: 'Parola greșita',
|
||||
logInToPlanka: 'Autentificarea în Planka',
|
||||
noInternetConnection: 'Lipsește conexiunea cu internet',
|
||||
pageNotFound_title: 'Pagina n-a fost găsită',
|
||||
password: 'Parola',
|
||||
projectManagement: 'Gestionarea proiectului',
|
||||
serverConnectionFailed: 'Conexiunea cu Server a eșuat',
|
||||
unknownError: 'Eroarea necunoscuta, mai incercați',
|
||||
useSingleSignOn: 'Folosiți autentificarea unica',
|
||||
},
|
||||
|
||||
action: {
|
||||
logIn: 'Autentificarea',
|
||||
logInWithSSO: 'Autentificarea cu SSO',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -13,10 +13,12 @@ export default {
|
||||
|
||||
translation: {
|
||||
common: {
|
||||
aboutPlanka: 'О проекте Planka',
|
||||
account: 'Учетная запись',
|
||||
actions: 'Действия',
|
||||
addAttachment: 'Добавление вложения',
|
||||
addComment: 'Добавление комментария',
|
||||
addManager_title: 'Добавление менеджера',
|
||||
addMember: 'Добавление участника',
|
||||
addUser: 'Добавление пользователя',
|
||||
administrator: 'Администратор',
|
||||
@@ -32,11 +34,16 @@ export default {
|
||||
areYouSureYouWantToDeleteThisProject: 'Вы уверены, что хотите удалить этот проект?',
|
||||
areYouSureYouWantToDeleteThisTask: 'Вы уверены, что хотите удалить эту задачу?',
|
||||
areYouSureYouWantToDeleteThisUser: 'Вы уверены, что хотите удалить этого пользователя?',
|
||||
areYouSureYouWantToRemoveThisMemberFromProject:
|
||||
'Вы уверены, что хотите удалить этого участника из проекта?',
|
||||
areYouSureYouWantToLeaveBoard: 'Вы уверены, что хотите покинуть эту доску?',
|
||||
areYouSureYouWantToLeaveProject: 'Вы уверены, что хотите покинуть этот проект?',
|
||||
areYouSureYouWantToRemoveThisManagerFromProject:
|
||||
'Вы уверены, что хотите удалить этого менеджера из проекта?',
|
||||
areYouSureYouWantToRemoveThisMemberFromBoard:
|
||||
'Вы уверены, что хотите удалить этого участника из доски?',
|
||||
attachment: 'Вложение',
|
||||
attachments: 'Вложения',
|
||||
authentication: 'Аутентификация',
|
||||
background: 'Фон',
|
||||
board: 'Доска',
|
||||
boardNotFound: 'Доска не найдена',
|
||||
canComment: 'Может комментировать',
|
||||
@@ -53,6 +60,7 @@ export default {
|
||||
createProject: 'Создание проекта',
|
||||
createTextFile: 'Создание текстового файла',
|
||||
currentPassword: 'Текущий пароль',
|
||||
dangerZone_title: 'Опасная зона',
|
||||
date: 'Дата',
|
||||
dueDate: 'Срок',
|
||||
deleteAttachment: 'Удаление вложения',
|
||||
@@ -74,7 +82,8 @@ export default {
|
||||
editBackground: 'Изменение фона',
|
||||
editDueDate: 'Изменение срока',
|
||||
editEmail: 'Изменение e-mail',
|
||||
editLabel: 'Изменения метки',
|
||||
editInformation: 'Изменение информации',
|
||||
editLabel: 'Изменение метки',
|
||||
editPassword: 'Изменение пароля',
|
||||
editPermissions: 'Редактирование разрешений',
|
||||
editStopwatch: 'Изменение секундомера',
|
||||
@@ -91,12 +100,18 @@ export default {
|
||||
filterByLabels: 'Фильтр по меткам',
|
||||
filterByMembers: 'Фильтр по участникам',
|
||||
fromComputer: 'С компьютера',
|
||||
fromTrello: 'Из Trello',
|
||||
general: 'Основные',
|
||||
hours: 'Часы',
|
||||
importBoard: 'Импорт доски',
|
||||
invalidCurrentPassword: 'Неверный текущий пароль',
|
||||
labels: 'Метки',
|
||||
language: 'Язык',
|
||||
leaveBoard: 'Покинуть доску',
|
||||
leaveProject: 'Покинуть проект',
|
||||
list: 'Список',
|
||||
listActions: 'Действия со списком',
|
||||
managers: 'Менеджеры',
|
||||
members: 'Участники',
|
||||
minutes: 'Минуты',
|
||||
moveCard: 'Перемещение карточки',
|
||||
@@ -120,6 +135,7 @@ export default {
|
||||
project: 'Проект',
|
||||
projectActions: 'Действия с проектом',
|
||||
projectNotFound: 'Проект не найден',
|
||||
removeManager: 'Удалить менеджера',
|
||||
removeMember: 'Удаление участника',
|
||||
searchLabels: 'Поиск меток...',
|
||||
searchMembers: 'Поиск участников...',
|
||||
@@ -131,7 +147,7 @@ export default {
|
||||
selectProject: 'Выберите проект',
|
||||
settings: 'Настройки',
|
||||
stopwatch: 'Секундомер',
|
||||
subscribeToMyOwnCardsByDefault: 'По умолчанию подписаться на мои собственные карточки',
|
||||
subscribeToMyOwnCardsByDefault: 'По умолчанию подписываться на мои собственные карточки',
|
||||
taskActions: 'Действия с задачей',
|
||||
tasks: 'Задачи',
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Предпросмотр для этого вложения недоступен',
|
||||
@@ -147,6 +163,7 @@ export default {
|
||||
username: 'Имя пользователя',
|
||||
usernameAlreadyInUse: 'Имя пользователя уже занято',
|
||||
users: 'Пользователи',
|
||||
version: 'Версия',
|
||||
viewer: 'Читатель',
|
||||
writeComment: 'Напишите комментарий...',
|
||||
},
|
||||
@@ -185,6 +202,7 @@ export default {
|
||||
editDueDate: 'Изменить срок',
|
||||
editDescription: 'Изменить описание',
|
||||
editEmail: 'Изменить e-mail',
|
||||
editInformation: 'Изменить информацию',
|
||||
editPassword: 'Изменить пароль',
|
||||
editPermissions: 'Изменить разрешения',
|
||||
editStopwatch: 'Изменить секундомер',
|
||||
@@ -192,6 +210,9 @@ export default {
|
||||
editTitle: 'Изменить название',
|
||||
editUsername: 'Изменить имя пользователя',
|
||||
hideDetails: 'Скрыть подробности',
|
||||
import: 'Импорт',
|
||||
leaveBoard: 'Покинуть доску',
|
||||
leaveProject: 'Покинуть проект',
|
||||
logOut: 'Выйти',
|
||||
makeCover: 'Сделать обложкой',
|
||||
move: 'Переместить',
|
||||
@@ -199,7 +220,9 @@ export default {
|
||||
remove: 'Убрать',
|
||||
removeBackground: 'Убрать фон',
|
||||
removeCover: 'Убрать обложку',
|
||||
removeFromBoard: 'Удалить из доски',
|
||||
removeFromProject: 'Удалить из проекта',
|
||||
removeManager: 'Удалить менеджера',
|
||||
removeMember: 'Удалить участника',
|
||||
save: 'Сохранить',
|
||||
showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
import dateFns from 'date-fns/locale/tr';
|
||||
|
||||
export default {
|
||||
dateFns,
|
||||
|
||||
format: {
|
||||
date: 'd.MM.yyyy',
|
||||
time: 'p',
|
||||
dateTime: '$t(format:date) $t(format:time)',
|
||||
longDate: 'd. MMM',
|
||||
longDateTime: "d. MMMM 'Saat' p",
|
||||
},
|
||||
|
||||
translation: {
|
||||
common: {
|
||||
account: 'Hesap',
|
||||
actions: 'Eylemler',
|
||||
addAttachment_title: 'Dosya ekle',
|
||||
addComment: 'Yorum ekle',
|
||||
addManager_title: 'Yönetici ekle',
|
||||
addMember_title: 'Üye ekle',
|
||||
addUser_title: 'Kullanıcı ekle',
|
||||
administrator: 'Administrator',
|
||||
all: 'Tümü',
|
||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||
'Bağlantı yeniden kurulduğunda tüm değişiklikler kaydedilecektir.',
|
||||
areYouSureYouWantToDeleteThisAttachment: 'Bu eki silmek istediğinize emin misiniz?',
|
||||
areYouSureYouWantToDeleteThisBoard: 'Bu panoyu silmek istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToDeleteThisCard: 'Bu kartı silmek istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToDeleteThisComment: 'Bu yorumu silmek istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToDeleteThisLabel: 'Bu etiketi silmek istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToDeleteThisList: 'Bu listeyi silmek istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToDeleteThisProject: 'Bu projeyi silmek istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToDeleteThisTask: 'Bu görevi silmek istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToDeleteThisUser: 'Bu kullanıcıyı silmek istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToLeaveBoard: 'Panodan ayrılmak istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToLeaveProject: 'Projeden ayrılmak istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToRemoveThisManagerFromProject:
|
||||
'Bu yöneticiyi projeden çıkarmak istediğinizden emin misiniz?',
|
||||
areYouSureYouWantToRemoveThisMemberFromBoard:
|
||||
'Bu üyeyi panodan çıkarmak istediğinizden emin misiniz?',
|
||||
attachment: 'ek',
|
||||
attachments: 'ekler',
|
||||
authentication: 'kimlik doğrulama',
|
||||
background: 'arka plan',
|
||||
board: 'pano',
|
||||
boardNotFound_title: 'Pano bulunamadı',
|
||||
cardActions_title: 'Kart İşlemleri',
|
||||
cardNotFound_title: 'Kart bulunamadı',
|
||||
cardOrActionAreDeleted: 'Kart veya işlem silindi',
|
||||
color: 'renk',
|
||||
createBoard_title: 'Pano Oluştur',
|
||||
createLabel_title: 'Etiket Oluştur',
|
||||
createNewOneOrSelectExistingOne: 'Yeni bir tane oluşturun veya mevcut bir tanesini seçin.',
|
||||
createProject_title: 'Proje Oluştur',
|
||||
createTextFile_title: 'Metin Dosyası Oluştur',
|
||||
currentPassword: 'Geçerli şifre',
|
||||
dangerZone_title: 'Tehlikeli Bölge',
|
||||
date: 'tarih',
|
||||
dueDate_title: 'Termin Tarihi',
|
||||
deleteAttachment_title: 'Eki Sil',
|
||||
deleteBoard_title: 'Panoyu Sil',
|
||||
deleteCard_title: 'Kartı Sil',
|
||||
deleteComment_title: 'Yorumu Sil',
|
||||
deleteLabel_title: 'Etiketi Sil',
|
||||
deleteList_title: 'Listeyi Sil',
|
||||
deleteProject_title: 'Projeyi Sil',
|
||||
deleteTask_title: 'Görevi Sil',
|
||||
deleteUser_title: 'Kullanıcıyı Sil',
|
||||
description: 'açıklama',
|
||||
detectAutomaically: 'Otomatik olarak algıla',
|
||||
dropFileToUpload: 'Yüklenecek dosyayı buraya bırakın',
|
||||
editAttachment_title: 'Eki Düzenle',
|
||||
editAvatar_title: 'Avatarı Düzenle',
|
||||
editBoard_title: 'Panoyu Düzenle',
|
||||
editDueDate_title: 'Son Tarihi Düzenle',
|
||||
editEmail_title: 'E-posta Adresini Düzenle',
|
||||
editLabel_title: 'Etiketi Düzenle',
|
||||
editPassword_title: 'Şifreyi Değiştir',
|
||||
editStopwatch_title: 'Kronometreyi Düzenle',
|
||||
editUsername_title: 'Kullanıcı Adını Düzenle',
|
||||
email: 'e-posta',
|
||||
emailAlreadyInUse: 'E-posta adresi zaten kullanımda',
|
||||
enterCardTitle: 'Kart başlığını girin',
|
||||
enterDescription: 'Açıklamayı girin',
|
||||
enterFilename: 'Dosya adını girin',
|
||||
enterListTitle: 'Liste başlığını girin',
|
||||
enterProjectTitle: 'Proje başlığını girin',
|
||||
enterTaskDescription: 'Görev açıklamasını girin',
|
||||
filterByLabels_title: 'Etikete Göre Filtrele',
|
||||
filterByMembers_title: 'Üyelere göre filtrele',
|
||||
fromComputer_title: 'Bilgisayardan',
|
||||
general: 'Genel',
|
||||
hours: 'saat',
|
||||
invalidCurrentPassword: 'Mevcut şifre yanlış',
|
||||
labels: 'etiketler',
|
||||
language: 'dil',
|
||||
leaveBoard_title: 'Panodan Ayrıl',
|
||||
leaveProject_title: 'Projeden Ayrıl',
|
||||
list: 'Listeler',
|
||||
listActions_title: 'Görevleri Listele',
|
||||
managers: 'Yöneticiler',
|
||||
members: 'Üyeler',
|
||||
minutes: 'dakika',
|
||||
moveCard_title: 'Kartı Taşı',
|
||||
name: 'isim',
|
||||
newEmail: 'Yeni e-posta adresi',
|
||||
newPassword: 'Yeni şifre',
|
||||
newUsername: 'Yeni kullanıcı adı',
|
||||
noConnectionToServer: 'Sunucuya bağlantı yok',
|
||||
noBoards: 'Pano yok',
|
||||
noLists: 'Liste yok',
|
||||
noProjects: 'Proje yok',
|
||||
notifications: 'Bildirimler',
|
||||
noUnreadNotifications: 'Okunmamış bildirim yok',
|
||||
openBoard_title: 'Açık Pano',
|
||||
optional_inline: 'İsteğe bağlı',
|
||||
organization: 'Organizasyon',
|
||||
phone: 'telefon',
|
||||
preferences: 'Tercihler',
|
||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||
'İpucu: Panodan bir ek eklemek için CTRL-V ye (Macte Cmd-V) basın',
|
||||
project: 'Proje',
|
||||
projectNotFound_title: 'Proje bulunamadı',
|
||||
removeManager_title: 'Yöneticiyi Kaldır',
|
||||
removeMember_title: 'Üyeyi Kaldır',
|
||||
seconds: 'saniye',
|
||||
selectBoard: 'Pano Seç',
|
||||
selectList: 'Liste seç',
|
||||
selectProject: 'Proje seç',
|
||||
settings: 'Ayarlar',
|
||||
stopwatch: 'kronometre',
|
||||
subscribeToMyOwnCardsByDefault: 'Varsayılan olarak kendi kartlarıma abone ol',
|
||||
taskActions_title: 'Görev Eylemleri',
|
||||
tasks: 'Görevler',
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'Bu ek için önizleme mevcut değil',
|
||||
time: 'zaman',
|
||||
title: 'başlık',
|
||||
userActions_title: 'Kullanıcı İşlemleri',
|
||||
userAddedThisCardToList: '<0>{{user}}</0><1> bu kartı {{list}</1> listesine ekledi',
|
||||
userLeftNewCommentToCard:
|
||||
'{{user}} yeni bir yorum yazdı: <2>{{card}</2> kartına «{{comment}}»',
|
||||
userMovedCardFromListToList:
|
||||
'{{user}}, <2>{{card}></2> kartını {{fromList}} listesinden {{toList}} listesine taşıdı',
|
||||
userMovedThisCardFromListToList:
|
||||
'<0>{{user}}</0><1> bu kartı {{fromList}} konumundan {{toList}}</1> konumuna taşıdı',
|
||||
username: 'kullanıcı adı',
|
||||
usernameAlreadyInUse: 'Kullanıcı adı zaten kullanımda',
|
||||
users: 'kullanıcı',
|
||||
writeComment: 'Yorum yazın',
|
||||
},
|
||||
action: {
|
||||
addAnotherCard: 'Başka bir kart ekle',
|
||||
addAnotherList: 'Başka bir liste ekle',
|
||||
addAnotherTask: 'Başka bir görev ekle',
|
||||
addCard: 'Kart ekle',
|
||||
addCard_title: 'Kart Ekle',
|
||||
addComment: 'Yorum ekle',
|
||||
addList: 'Liste ekle',
|
||||
addMoreDetailedDescription: 'Ayrıntılı bir açıklama ekleyin',
|
||||
addTask: 'Görev ekle',
|
||||
addToCard: 'Karta ekle',
|
||||
addUser: 'Kullanıcı ekle',
|
||||
createBoard: 'Pano oluştur',
|
||||
createFile: 'Dosya oluştur',
|
||||
createLabel: 'Etiket Oluştur',
|
||||
createNewLabel: 'Yeni etiket oluştur',
|
||||
createProject: 'Proje oluştur',
|
||||
delete: 'Sil',
|
||||
deleteAttachment: 'Eki sil',
|
||||
deleteAvatar: 'Avatarı sil',
|
||||
deleteBoard: 'Panoyu Sil',
|
||||
deleteCard: 'Kartı sil',
|
||||
deleteCard_title: 'Kartı Sil',
|
||||
deleteComment: 'Yorumu sil',
|
||||
deleteImage: 'Resmi sil',
|
||||
deleteLabel: 'Etiketi sil',
|
||||
deleteList: 'Listeyi sil',
|
||||
deleteList_title: 'Listeyi Sil',
|
||||
deleteProject: 'Projeyi sil',
|
||||
deleteProject_title: 'Projeyi Sil',
|
||||
deleteTask: 'Görevi sil',
|
||||
deleteTask_title: 'Görevi Sil',
|
||||
deleteUser: 'Kullanıcıyı sil',
|
||||
edit: 'Düzenle',
|
||||
editDueDate_title: 'Son Tarihi Düzenle',
|
||||
editDescription_title: 'Açıklamayı Düzenle',
|
||||
editEmail_title: 'E-posta Adresini Düzenle',
|
||||
editPassword_title: 'Şifreyi Değiştir',
|
||||
editStopwatch_title: 'Kronometreyi Düzenle',
|
||||
editTitle_title: 'Başlığı düzenle',
|
||||
editUsername_title: 'Kullanıcı Adını Düzenle',
|
||||
hideDetails: 'Ayrıntıları gizle',
|
||||
leaveBoard: 'Panodan Ayrıl',
|
||||
leaveProject: 'Projeden ayrıl',
|
||||
logOut_title: 'Çıkış',
|
||||
makeCover_title: 'Önizleme olarak ayarla',
|
||||
move: 'Taşı',
|
||||
moveCard_title: 'Kartı Taşı',
|
||||
remove: 'Sil',
|
||||
removeBackground: 'Arka planı kaldır',
|
||||
removeCover_title: 'Önizlemeyi Kaldır',
|
||||
removeFromBoard: 'Panodan kaldır',
|
||||
removeFromProject: 'Projeden kaldır',
|
||||
removeManager: 'Yöneticiyi kaldır',
|
||||
removeMember: 'Üyeyi kaldır',
|
||||
save: 'Kaydet',
|
||||
showAllAttachments: 'Tüm ekleri göster ({{hidden}} gizli)',
|
||||
showDetails: 'Ayrıntıları göster',
|
||||
showFewerAttachments: 'Daha az ek göster',
|
||||
start: 'başlat',
|
||||
stop: 'dur',
|
||||
subscribe: 'Abone ol',
|
||||
unsubscribe: 'Abonelikten çık',
|
||||
uploadNewAvatar: 'Yeni avatar yükle',
|
||||
uploadNewImage: 'Yeni resim yükle',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import login from './login';
|
||||
|
||||
export default {
|
||||
language: 'tr',
|
||||
country: 'tr',
|
||||
name: 'Türkçe',
|
||||
embeddedLocale: login,
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
export default {
|
||||
translation: {
|
||||
common: {
|
||||
emailOrUsername: 'E-posta adresi veya Kullanıcı adı',
|
||||
invalidEmailOrUsername: 'Geçersiz e-posta adresi veya kullanıcı adı',
|
||||
invalidPassword: 'Hatalı Şifre',
|
||||
logInToPlanka: 'Giriş Yap',
|
||||
noInternetConnection: 'Internet bağlantısı yok',
|
||||
pageNotFound_title: 'Sayfa bulunamadı',
|
||||
password: 'Şifre',
|
||||
projectManagement: 'Proje Yönetimi',
|
||||
serverConnectionFailed: 'Sunucu bağlantı hatası',
|
||||
unknownError: 'Bilinmeyen Hata, daha sonra tekrar deneyin',
|
||||
},
|
||||
|
||||
action: {
|
||||
logIn: 'Giriş Yap',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
dueDate_title: '截止日期',
|
||||
deleteAttachment_title: '删除附件',
|
||||
deleteBoard_title: '删除面板',
|
||||
deleteCard_title: '删除擦片',
|
||||
deleteCard_title: '删除卡片',
|
||||
deleteComment_title: '删除评论',
|
||||
deleteLabel_title: '删除标签',
|
||||
deleteList_title: '删除列表',
|
||||
@@ -81,7 +81,7 @@ export default {
|
||||
enterTaskDescription: '输入任务描述...',
|
||||
filterByLabels_title: '通过标签筛选',
|
||||
filterByMembers_title: '通过成员筛选',
|
||||
fromComputer_title: '从电脑',
|
||||
fromComputer_title: '从本机',
|
||||
general: '全体',
|
||||
hours: '小时',
|
||||
invalidCurrentPassword: '当前密码错误',
|
||||
@@ -110,7 +110,7 @@ export default {
|
||||
phone: '电话',
|
||||
preferences: '偏好',
|
||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||
'提示: 点击 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件',
|
||||
'提示: 按下 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件',
|
||||
project: '项目',
|
||||
projectNotFound_title: '项目未找到',
|
||||
removeManager_title: '删除管理员',
|
||||
@@ -134,7 +134,7 @@ export default {
|
||||
userMovedThisCardFromListToList:
|
||||
'<0>{{user}}</0><1> 将该卡片从 {{fromList}} 移动到 {{toList}}</1>',
|
||||
username: '用户名',
|
||||
usernameAlreadyInUse: '用户名已占用',
|
||||
usernameAlreadyInUse: '用户名已被占用',
|
||||
users: '用户',
|
||||
writeComment: '编写评论...',
|
||||
},
|
||||
@@ -149,7 +149,7 @@ export default {
|
||||
addList: '添加列表',
|
||||
addMoreDetailedDescription: '添加更多详细描述',
|
||||
addTask: '添加任务',
|
||||
addToCard: '添加擦篇',
|
||||
addToCard: '添加到卡片',
|
||||
addUser: '添加用户',
|
||||
createBoard: '创建面板',
|
||||
createFile: '创建文件',
|
||||
@@ -177,7 +177,7 @@ export default {
|
||||
editDescription_title: '编辑描述',
|
||||
editEmail_title: '编辑邮箱',
|
||||
editPassword_title: '编辑密码',
|
||||
editStopwatch_title: '编辑实践',
|
||||
editStopwatch_title: '编辑时间',
|
||||
editTitle_title: '编辑标题',
|
||||
editUsername_title: '编辑用户名',
|
||||
leaveBoard: '离开面板',
|
||||
|
||||
@@ -12,6 +12,9 @@ export default class extends BaseModel {
|
||||
coverUrl: attr(),
|
||||
image: attr(),
|
||||
name: attr(),
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
cardId: fk({
|
||||
to: 'Card',
|
||||
as: 'card',
|
||||
|
||||
@@ -172,7 +172,7 @@ export default class extends BaseModel {
|
||||
}
|
||||
|
||||
getOrderedMembershipsQuerySet() {
|
||||
return this.memberships.orderBy('id');
|
||||
return this.memberships.orderBy('createdAt');
|
||||
}
|
||||
|
||||
getOrderedLabelsQuerySet() {
|
||||
|
||||
@@ -10,6 +10,9 @@ export default class extends BaseModel {
|
||||
id: attr(),
|
||||
role: attr(),
|
||||
canComment: attr(),
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
boardId: fk({
|
||||
to: 'Board',
|
||||
as: 'board',
|
||||
|
||||
@@ -244,7 +244,7 @@ export default class extends BaseModel {
|
||||
}
|
||||
|
||||
getOrderedAttachmentsQuerySet() {
|
||||
return this.attachments.orderBy('id', false);
|
||||
return this.attachments.orderBy('createdAt', false);
|
||||
}
|
||||
|
||||
getFilteredOrderedInCardActivitiesQuerySet() {
|
||||
@@ -256,7 +256,7 @@ export default class extends BaseModel {
|
||||
filter.type = ActivityTypes.COMMENT_CARD;
|
||||
}
|
||||
|
||||
return this.activities.filter(filter).orderBy('id', false);
|
||||
return this.activities.filter(filter).orderBy('createdAt', false);
|
||||
}
|
||||
|
||||
getUnreadNotificationsQuerySet() {
|
||||
|
||||
@@ -137,7 +137,7 @@ export default class extends BaseModel {
|
||||
}
|
||||
|
||||
getOrderedManagersQuerySet() {
|
||||
return this.managers.orderBy('id');
|
||||
return this.managers.orderBy('createdAt');
|
||||
}
|
||||
|
||||
getOrderedBoardsQuerySet() {
|
||||
|
||||
@@ -8,6 +8,9 @@ export default class extends BaseModel {
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
projectId: fk({
|
||||
to: 'Project',
|
||||
as: 'project',
|
||||
|
||||
@@ -43,9 +43,13 @@ export default class extends BaseModel {
|
||||
organization: attr(),
|
||||
language: attr(),
|
||||
subscribeToOwnCards: attr(),
|
||||
isAdmin: attr(),
|
||||
isLocked: attr(),
|
||||
isRoleLocked: attr(),
|
||||
isDeletionLocked: attr(),
|
||||
deletedAt: attr(),
|
||||
isAdmin: attr({
|
||||
getDefault: () => false,
|
||||
createdAt: attr({
|
||||
getDefault: () => new Date(),
|
||||
}),
|
||||
isAvatarUpdating: attr({
|
||||
getDefault: () => false,
|
||||
@@ -293,15 +297,15 @@ export default class extends BaseModel {
|
||||
static getOrderedUndeletedQuerySet() {
|
||||
return this.filter({
|
||||
deletedAt: null,
|
||||
}).orderBy('id');
|
||||
}).orderBy('createdAt');
|
||||
}
|
||||
|
||||
getOrderedProjectManagersQuerySet() {
|
||||
return this.projectManagers.orderBy('id');
|
||||
return this.projectManagers.orderBy('createdAt');
|
||||
}
|
||||
|
||||
getOrderedBoardMembershipsQuerySet() {
|
||||
return this.boardMemberships.orderBy('id');
|
||||
return this.boardMemberships.orderBy('createdAt');
|
||||
}
|
||||
|
||||
getOrderedUnreadNotificationsQuerySet() {
|
||||
@@ -309,7 +313,7 @@ export default class extends BaseModel {
|
||||
.filter({
|
||||
isRead: false,
|
||||
})
|
||||
.orderBy('id', false);
|
||||
.orderBy('createdAt', false);
|
||||
}
|
||||
|
||||
getOrderedAvailableProjectsModelArray() {
|
||||
|
||||
@@ -10,6 +10,7 @@ const initialState = {
|
||||
export default (state = initialState, { type, payload }) => {
|
||||
switch (type) {
|
||||
case ActionTypes.AUTHENTICATE__SUCCESS:
|
||||
case ActionTypes.USING_OIDC_AUTHENTICATE__SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
accessToken: payload.accessToken,
|
||||
|
||||
@@ -4,7 +4,6 @@ import ActionTypes from '../constants/ActionTypes';
|
||||
import ModalTypes from '../constants/ModalTypes';
|
||||
|
||||
const initialState = {
|
||||
isInitializing: true,
|
||||
isLogouting: false,
|
||||
currentModal: null,
|
||||
};
|
||||
@@ -18,11 +17,6 @@ export default (state = initialState, { type, payload }) => {
|
||||
...state,
|
||||
currentModal: null,
|
||||
};
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
return {
|
||||
...state,
|
||||
isInitializing: false,
|
||||
};
|
||||
case ActionTypes.LOGOUT__ACCESS_TOKEN_INVALIDATE:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { combineReducers } from 'redux';
|
||||
import router from './router';
|
||||
import socket from './socket';
|
||||
import orm from './orm';
|
||||
import root from './root';
|
||||
import auth from './auth';
|
||||
import core from './core';
|
||||
import ui from './ui';
|
||||
@@ -11,6 +12,7 @@ export default combineReducers({
|
||||
router,
|
||||
socket,
|
||||
orm,
|
||||
root,
|
||||
auth,
|
||||
core,
|
||||
ui,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const initialState = {
|
||||
isInitializing: true,
|
||||
config: null,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line default-param-last
|
||||
export default (state = initialState, { type, payload }) => {
|
||||
switch (type) {
|
||||
case ActionTypes.LOGIN_INITIALIZE:
|
||||
return {
|
||||
...state,
|
||||
isInitializing: false,
|
||||
config: payload.config,
|
||||
};
|
||||
case ActionTypes.AUTHENTICATE__SUCCESS:
|
||||
case ActionTypes.USING_OIDC_AUTHENTICATE__SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
isInitializing: true,
|
||||
};
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
return {
|
||||
...state,
|
||||
isInitializing: false,
|
||||
};
|
||||
case ActionTypes.CORE_INITIALIZE__CONFIG_FETCH:
|
||||
return {
|
||||
...state,
|
||||
config: payload.config,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,7 @@
|
||||
import { LOCATION_CHANGE_HANDLE } from '../../lib/redux-router';
|
||||
|
||||
import ActionTypes from '../../constants/ActionTypes';
|
||||
import Paths from '../../constants/Paths';
|
||||
|
||||
const initialState = {
|
||||
data: {
|
||||
@@ -6,12 +9,22 @@ const initialState = {
|
||||
password: '',
|
||||
},
|
||||
isSubmitting: false,
|
||||
isSubmittingUsingOidc: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line default-param-last
|
||||
export default (state = initialState, { type, payload }) => {
|
||||
switch (type) {
|
||||
case LOCATION_CHANGE_HANDLE:
|
||||
if (payload.location.pathname === Paths.OIDC_CALLBACK) {
|
||||
return {
|
||||
...state,
|
||||
isSubmittingUsingOidc: true,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
case ActionTypes.AUTHENTICATE:
|
||||
return {
|
||||
...state,
|
||||
@@ -22,6 +35,7 @@ export default (state = initialState, { type, payload }) => {
|
||||
isSubmitting: true,
|
||||
};
|
||||
case ActionTypes.AUTHENTICATE__SUCCESS:
|
||||
case ActionTypes.USING_OIDC_AUTHENTICATE__SUCCESS:
|
||||
return initialState;
|
||||
case ActionTypes.AUTHENTICATE__FAILURE:
|
||||
return {
|
||||
@@ -29,6 +43,12 @@ export default (state = initialState, { type, payload }) => {
|
||||
isSubmitting: false,
|
||||
error: payload.error,
|
||||
};
|
||||
case ActionTypes.USING_OIDC_AUTHENTICATE__FAILURE:
|
||||
return {
|
||||
...state,
|
||||
isSubmittingUsingOidc: false,
|
||||
error: payload.error,
|
||||
};
|
||||
case ActionTypes.AUTHENTICATE_ERROR_CLEAR:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { all, apply, fork, take } from 'redux-saga/effects';
|
||||
import { all, apply, fork, select, take } from 'redux-saga/effects';
|
||||
|
||||
import watchers from './watchers';
|
||||
import services from './services';
|
||||
import selectors from '../../selectors';
|
||||
import { socket } from '../../api';
|
||||
import ActionTypes from '../../constants/ActionTypes';
|
||||
import Paths from '../../constants/Paths';
|
||||
@@ -14,5 +15,12 @@ export default function* coreSaga() {
|
||||
|
||||
yield take(ActionTypes.LOGOUT);
|
||||
|
||||
const oidcConfig = yield select(selectors.selectOidcConfig);
|
||||
|
||||
if (oidcConfig && oidcConfig.endSessionUrl !== null) {
|
||||
// Redirect the user to the IDP to log out.
|
||||
window.location.href = oidcConfig.endSessionUrl;
|
||||
} else {
|
||||
window.location.href = Paths.LOGIN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
import { call, put, take } from 'redux-saga/effects';
|
||||
import { call, put, select, take } from 'redux-saga/effects';
|
||||
|
||||
import request from '../request';
|
||||
import requests from '../requests';
|
||||
import selectors from '../../../selectors';
|
||||
import actions from '../../../actions';
|
||||
import api from '../../../api';
|
||||
import i18n from '../../../i18n';
|
||||
import { removeAccessToken } from '../../../utils/access-token-storage';
|
||||
|
||||
export function* initializeCore() {
|
||||
const currentConfig = yield select(selectors.selectConfig); // TODO: add boolean selector?
|
||||
|
||||
let config;
|
||||
if (!currentConfig) {
|
||||
({ item: config } = yield call(api.getConfig)); // TODO: handle error
|
||||
|
||||
yield put(actions.initializeCore.fetchConfig(config));
|
||||
}
|
||||
|
||||
const {
|
||||
user,
|
||||
board,
|
||||
|
||||
@@ -33,15 +33,16 @@ export function* handleLocationChange() {
|
||||
|
||||
switch (pathsMatch.pattern.path) {
|
||||
case Paths.LOGIN:
|
||||
case Paths.OIDC_CALLBACK:
|
||||
yield call(goToRoot);
|
||||
|
||||
break;
|
||||
return;
|
||||
default:
|
||||
}
|
||||
|
||||
const isCoreInitializing = yield select(selectors.selectIsCoreInitializing);
|
||||
const isInitializing = yield select(selectors.selectIsInitializing);
|
||||
|
||||
if (isCoreInitializing) {
|
||||
if (isInitializing) {
|
||||
yield take(ActionTypes.CORE_INITIALIZE);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,25 @@ export function* handleSocketReconnect() {
|
||||
|
||||
yield put(actions.handleSocketReconnect.fetchCore(currentUserId, boardId));
|
||||
|
||||
const {
|
||||
let user;
|
||||
let board;
|
||||
let users;
|
||||
let projects;
|
||||
let projectManagers;
|
||||
let boards;
|
||||
let boardMemberships;
|
||||
let labels;
|
||||
let lists;
|
||||
let cards;
|
||||
let cardMemberships;
|
||||
let cardLabels;
|
||||
let tasks;
|
||||
let attachments;
|
||||
let activities;
|
||||
let notifications;
|
||||
|
||||
try {
|
||||
({
|
||||
user,
|
||||
board,
|
||||
users,
|
||||
@@ -31,7 +49,10 @@ export function* handleSocketReconnect() {
|
||||
attachments,
|
||||
activities,
|
||||
notifications,
|
||||
} = yield call(requests.fetchCore); // TODO: handle error
|
||||
} = yield call(requests.fetchCore));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield put(
|
||||
actions.handleSocketReconnect(
|
||||
|
||||
@@ -4,8 +4,5 @@ import services from '../services';
|
||||
import EntryActionTypes from '../../../constants/EntryActionTypes';
|
||||
|
||||
export default function* coreWatchers() {
|
||||
yield all([
|
||||
takeEvery(EntryActionTypes.CORE_INITIALIZE, () => services.initializeCore()),
|
||||
takeEvery(EntryActionTypes.LOGOUT, () => services.logout()),
|
||||
]);
|
||||
yield all([takeEvery(EntryActionTypes.LOGOUT, () => services.logout())]);
|
||||
}
|
||||
|
||||
@@ -16,17 +16,17 @@ const createSocketEventsChannel = () =>
|
||||
emit(entryActions.handleSocketReconnect());
|
||||
};
|
||||
|
||||
const handleUserCreate = ({ item }) => {
|
||||
const handleUserCreate = api.makeHandleUserCreate(({ item }) => {
|
||||
emit(entryActions.handleUserCreate(item));
|
||||
};
|
||||
});
|
||||
|
||||
const handleUserUpdate = ({ item }) => {
|
||||
const handleUserUpdate = api.makeHandleUserUpdate(({ item }) => {
|
||||
emit(entryActions.handleUserUpdate(item));
|
||||
};
|
||||
});
|
||||
|
||||
const handleUserDelete = ({ item }) => {
|
||||
const handleUserDelete = api.makeHandleUserDelete(({ item }) => {
|
||||
emit(entryActions.handleUserDelete(item));
|
||||
};
|
||||
});
|
||||
|
||||
const handleProjectCreate = ({ item }) => {
|
||||
emit(entryActions.handleProjectCreate(item));
|
||||
@@ -40,13 +40,13 @@ const createSocketEventsChannel = () =>
|
||||
emit(entryActions.handleProjectDelete(item));
|
||||
};
|
||||
|
||||
const handleProjectManagerCreate = ({ item }) => {
|
||||
const handleProjectManagerCreate = api.makeHandleProjectManagerCreate(({ item }) => {
|
||||
emit(entryActions.handleProjectManagerCreate(item));
|
||||
};
|
||||
});
|
||||
|
||||
const handleProjectManagerDelete = ({ item }) => {
|
||||
const handleProjectManagerDelete = api.makeHandleProjectManagerDelete(({ item }) => {
|
||||
emit(entryActions.handleProjectManagerDelete(item));
|
||||
};
|
||||
});
|
||||
|
||||
const handleBoardCreate = ({ item, requestId }) => {
|
||||
emit(entryActions.handleBoardCreate(item, requestId));
|
||||
@@ -60,17 +60,17 @@ const createSocketEventsChannel = () =>
|
||||
emit(entryActions.handleBoardDelete(item));
|
||||
};
|
||||
|
||||
const handleBoardMembershipCreate = ({ item }) => {
|
||||
const handleBoardMembershipCreate = api.makeHandleBoardMembershipCreate(({ item }) => {
|
||||
emit(entryActions.handleBoardMembershipCreate(item));
|
||||
};
|
||||
});
|
||||
|
||||
const handleBoardMembershipUpdate = ({ item }) => {
|
||||
const handleBoardMembershipUpdate = api.makeHandleBoardMembershipUpdate(({ item }) => {
|
||||
emit(entryActions.handleBoardMembershipUpdate(item));
|
||||
};
|
||||
});
|
||||
|
||||
const handleBoardMembershipDelete = ({ item }) => {
|
||||
const handleBoardMembershipDelete = api.makeHandleBoardMembershipDelete(({ item }) => {
|
||||
emit(entryActions.handleBoardMembershipDelete(item));
|
||||
};
|
||||
});
|
||||
|
||||
const handleListCreate = ({ item }) => {
|
||||
emit(entryActions.handleListCreate(item));
|
||||
|
||||
@@ -7,7 +7,9 @@ import ActionTypes from '../../constants/ActionTypes';
|
||||
export default function* loginSaga() {
|
||||
const watcherTasks = yield all(watchers.map((watcher) => fork(watcher)));
|
||||
|
||||
yield take(ActionTypes.AUTHENTICATE__SUCCESS);
|
||||
yield fork(services.initializeLogin);
|
||||
|
||||
yield take([ActionTypes.AUTHENTICATE__SUCCESS, ActionTypes.USING_OIDC_AUTHENTICATE__SUCCESS]);
|
||||
|
||||
yield cancel(watcherTasks);
|
||||
yield call(services.goToRoot);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user