Compare commits

...
28 Commits
Author SHA1 Message Date
Daniel Hiller a26c468a9d Add: AI-generated translations
Adding missing translation from the top 10 most spoken languages

* Hindi
* Arabic
* Bengali

* Dutch (because requested)
2023-09-30 02:44:26 +02:00
Daniel Hiller 0f076f4cc4 Add: Translation Bengali (AI) 2023-09-30 02:33:09 +02:00
Daniel Hiller f1865db477 Add: Translation Arabic (AI) 2023-09-30 02:27:51 +02:00
Daniel Hiller b80127e952 Add: Translation Dutch (AI) 2023-09-30 02:18:27 +02:00
Daniel Hiller 5e400c5c4e Add: Translation Hindi (AI) 2023-09-30 02:15:22 +02:00
Daniel Hiller 8367fa4a29 Add: Dutch translation (AI) 2023-09-30 02:04:48 +02:00
Daniel Hiller aae69cb5e4 CI: Add prebuild planka package on release 2023-09-30 01:59:09 +02:00
Daniel Hiller 782c0356c8 Meta: Add missing return 2023-09-23 03:48:32 +02:00
Daniel Hiller 757cdb14a4 Meta: Add Automated installation 2023-09-23 03:47:46 +02:00
Daniel Hiller d896c3183b chore: fix docker dev build 2023-09-14 01:27:29 +02:00
Daniel Hiller 13ee0c3466 chore: fix runs-on name 2023-09-13 22:55:09 +02:00
Daniel Hiller 211c841379 chore: Switch to self hostet action runner 2023-09-13 22:47:08 +02:00
Daniel Hiller 90a30961ff chore: Add dev container, based on latest commit 2023-09-13 22:39:37 +02:00
Daniel Hiller 7cb9774e05 chore: Switch to pnpm package manager 2023-09-13 22:38:37 +02:00
Maksim Eltyshev 875895b331 fix: Fix starting without OIDC environment variables
Closes #503
2023-09-12 01:20:21 +02:00
Maksim Eltyshev 91bc889fed feat: Use environment variables for default admin configuration 2023-09-12 01:12:38 +02:00
Gilberto Vidal a34d8e9753 fix: Fix Turkish translation linting (#504) 2023-09-11 22:21:46 +05:00
gorrilla10101 107cb85ba2 feat: OIDC with PKCE flow (#491) 2023-09-04 20:06:59 +05:00
creador30 9333ef562e feat: Add Turkish translation (#501) 2023-09-04 19:41:11 +05:00
RocketProto 7d28abc7e6 fix: Decouple BASE_URL from Ingress host (#498)
Closes #496
2023-08-27 02:26:45 +05:00
Andrey 5317950b5c fix: Update Russian translation (#497) 2023-08-22 14:34:45 +05:00
Maksim Eltyshev 7501619b7e fix: Fix Czech translation linting
Closes #487
2023-07-31 11:54:50 +02:00
leroyloren 080e29aa03 fix: Update Czech translation (#485) 2023-07-27 04:07:47 +05:00
[object Object] 48c532b2c2 feat: Add edit icon to project name (#457) 2023-07-09 20:49:08 +05:00
Maksim Eltyshev a62815f825 ref: Little refactoring 2023-06-13 12:06:28 +02:00
Maksim Eltyshev 6938232dc4 fix: Keep task edits when textarea loses focus 2023-06-13 12:04:49 +02:00
[object Object] 4c4cfa9c66 fix: Keep comment edits when textarea loses focus (#443)
Closes #441
2023-06-13 14:49:54 +05:00
Maksim Eltyshev ba89d41b19 chore: Update chart version 2023-06-13 00:46:17 +02:00
74 changed files with 2558 additions and 212 deletions
@@ -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,34 @@
name: Build and push Docker DEV image
on:
push:
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
+2 -2
View File
@@ -13,10 +13,10 @@ jobs:
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
contents: write
runs-on: ubuntu-latest
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
+4 -2
View File
@@ -5,7 +5,8 @@ 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 install --prod
FROM node:lts AS client
@@ -14,7 +15,8 @@ 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 install --prod
COPY client .
RUN DISABLE_ESLINT_PLUGIN=true npm run build
+1
View File
@@ -23,6 +23,7 @@
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)
- [Automated installation](https://github.com/plankanban/planka-installer)
For configuration, please see the [configuration section](https://docs.planka.cloud/docs/category/configuration).
+1 -1
View File
@@ -15,7 +15,7 @@ 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.0
version: 0.1.2
# 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
+3 -1
View File
@@ -64,7 +64,9 @@ spec:
name: planka-postgresql-svcbind-custom-user
key: uri
- name: BASE_URL
{{- if .Values.ingress.enabled }}
{{- 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
+5
View File
@@ -17,6 +17,10 @@ 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
@@ -50,6 +54,7 @@ ingress:
# 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: /
+54
View File
@@ -30,6 +30,7 @@
"react-i18next": "^12.0.0",
"react-input-mask": "^2.0.4",
"react-markdown": "^8.0.3",
"react-oidc-context": "^2.2.2",
"react-photoswipe-gallery": "^2.2.2",
"react-redux": "^8.0.5",
"react-router-dom": "^6.4.3",
@@ -7189,6 +7190,12 @@
"node": ">= 8"
}
},
"node_modules/crypto-js": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==",
"peer": true
},
"node_modules/crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -17035,6 +17042,19 @@
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
},
"node_modules/oidc-client-ts": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.2.4.tgz",
"integrity": "sha512-nOZwIomju+AmXObl5Oq5PjrES/qTt8bLsENJCIydVgi9TEWk7SCkOU6X3RNkY7yfySRM1OJJvDKdREZdmnDT2g==",
"peer": true,
"dependencies": {
"crypto-js": "^4.1.1",
"jwt-decode": "^3.1.2"
},
"engines": {
"node": ">=12.13.0"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -19276,6 +19296,18 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/react-oidc-context": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-2.2.2.tgz",
"integrity": "sha512-rke8goKuxxQhSAR11h8wVn56m7kEa1mcAfYDlIycsIgmbZOFzKA0Un0y0RodHZ/M/CG6u0JD1I8RKZHqRJjWnA==",
"engines": {
"node": ">=12.13.0"
},
"peerDependencies": {
"oidc-client-ts": "^2.2.1",
"react": ">=16.8.0"
}
},
"node_modules/react-onclickoutside": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz",
@@ -28767,6 +28799,12 @@
"which": "^2.0.1"
}
},
"crypto-js": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==",
"peer": true
},
"crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -35933,6 +35971,16 @@
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
},
"oidc-client-ts": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.2.4.tgz",
"integrity": "sha512-nOZwIomju+AmXObl5Oq5PjrES/qTt8bLsENJCIydVgi9TEWk7SCkOU6X3RNkY7yfySRM1OJJvDKdREZdmnDT2g==",
"peer": true,
"requires": {
"crypto-js": "^4.1.1",
"jwt-decode": "^3.1.2"
}
},
"on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -37371,6 +37419,12 @@
}
}
},
"react-oidc-context": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-2.2.2.tgz",
"integrity": "sha512-rke8goKuxxQhSAR11h8wVn56m7kEa1mcAfYDlIycsIgmbZOFzKA0Un0y0RodHZ/M/CG6u0JD1I8RKZHqRJjWnA==",
"requires": {}
},
"react-onclickoutside": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz",
+1
View File
@@ -77,6 +77,7 @@
"react-i18next": "^12.0.0",
"react-input-mask": "^2.0.4",
"react-markdown": "^8.0.3",
"react-oidc-context": "^2.2.2",
"react-photoswipe-gallery": "^2.2.2",
"react-redux": "^8.0.5",
"react-router-dom": "^6.4.3",
+3
View File
@@ -4,6 +4,8 @@ import socket from './socket';
/* Actions */
const createAccessToken = (data, headers) => http.post('/access-tokens', data, headers);
const exchangeOidcToken = (accessToken, headers) =>
http.post('/access-tokens/exchange', { token: accessToken }, headers);
const deleteCurrentAccessToken = (headers) =>
socket.delete('/access-tokens/me', undefined, headers);
@@ -11,4 +13,5 @@ const deleteCurrentAccessToken = (headers) =>
export default {
createAccessToken,
deleteCurrentAccessToken,
exchangeOidcToken,
};
@@ -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>
);
@@ -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>
);
+18 -10
View File
@@ -2,7 +2,8 @@ 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 { useAuth } from 'react-oidc-context';
import { usePopup } from '../../lib/popup';
import Paths from '../../constants/Paths';
@@ -29,6 +30,7 @@ const Header = React.memo(
onUserSettingsClick,
onLogout,
}) => {
const auth = useAuth();
const handleProjectSettingsClick = useCallback(() => {
if (canEditProject) {
onProjectSettingsClick();
@@ -38,6 +40,11 @@ const Header = React.memo(
const NotificationsPopup = usePopup(NotificationsStep, POPUP_PROPS);
const UserPopup = usePopup(UserStep, POPUP_PROPS);
const onFullLogout = () => {
auth.signoutSilent();
onLogout();
};
return (
<div className={styles.wrapper}>
{!project && (
@@ -55,15 +62,16 @@ const Header = React.memo(
>
<Icon fitted name="arrow left" />
</Menu.Item>
<Menu.Item
className={classNames(
styles.item,
canEditProject && styles.itemHoverable,
styles.title,
)}
onClick={handleProjectSettingsClick}
>
<Menu.Item className={classNames(styles.item, styles.title)}>
{project.name}
{canEditProject && (
<Button
className={classNames(styles.editButton, styles.target)}
onClick={handleProjectSettingsClick}
>
<Icon fitted name="pencil" size="small" />
</Button>
)}
</Menu.Item>
</Menu.Menu>
)}
@@ -87,7 +95,7 @@ const Header = React.memo(
<UserPopup
isLogouting={isLogouting}
onSettingsClick={onUserSettingsClick}
onLogout={onLogout}
onLogout={onFullLogout}
>
<Menu.Item className={classNames(styles.item, styles.itemHoverable)}>
{user.name}
@@ -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;
}
}
}
+5
View File
@@ -1,5 +1,6 @@
import isEmail from 'validator/lib/isEmail';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { useAuth } from 'react-oidc-context';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
@@ -48,6 +49,7 @@ const createMessage = (error) => {
const Login = React.memo(
({ defaultData, isSubmitting, error, onAuthenticate, onMessageDismiss }) => {
const auth = useAuth();
const [t] = useTranslation();
const wasSubmitting = usePrevious(isSubmitting);
@@ -171,6 +173,9 @@ const Login = React.memo(
disabled={isSubmitting}
/>
</Form>
<Form.Button type="button" onClick={() => auth.signinRedirect()}>
Log in with SSO
</Form.Button>
</div>
</div>
</Grid.Column>
+19
View File
@@ -0,0 +1,19 @@
import { useAuth } from 'react-oidc-context';
import PropTypes from 'prop-types';
import React from 'react';
let isLoggingIn = true;
const OidcLogin = React.memo(({ onAuthenticate }) => {
const auth = useAuth();
if (isLoggingIn && auth.user) {
isLoggingIn = false;
const { user } = auth;
onAuthenticate(user);
}
});
OidcLogin.propTypes = {
onAuthenticate: PropTypes.func.isRequired,
};
export default OidcLogin;
+3
View File
@@ -0,0 +1,3 @@
import OidcLogin from './OidcLogin';
export default OidcLogin;
+27 -15
View File
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { AuthProvider } from 'react-oidc-context';
import { Provider } from 'react-redux';
import { Route, Routes } from 'react-router-dom';
import { ReduxRouter } from '../lib/redux-router';
@@ -13,30 +14,41 @@ 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';
import OidcLoginContainer from '../containers/OidcLoginContainer';
function Root({ store, history }) {
function Root({ store, history, config }) {
return (
<Provider store={store}>
<ReduxRouter history={history}>
<Routes>
<Route path={Paths.LOGIN} element={<LoginContainer />} />
<Route path={Paths.ROOT} element={<CoreContainer />} />
<Route path={Paths.PROJECTS} element={<CoreContainer />} />
<Route path={Paths.BOARDS} element={<CoreContainer />} />
<Route path={Paths.CARDS} element={<CoreContainer />} />
<Route path="*" element={<NotFound />} />
</Routes>
</ReduxRouter>
</Provider>
<AuthProvider
authority={config.authority}
client_id={config.clientId}
redirect_uri={config.redirectUri}
scope={config.scopes}
onSigninCallback={() => {
window.history.replaceState({}, document.title, window.location.pathname);
}}
>
<Provider store={store}>
<ReduxRouter history={history}>
<Routes>
<Route path={Paths.LOGIN} element={<LoginContainer />} />
<Route path={Paths.OIDC_LOGIN} element={<OidcLoginContainer />} />
<Route path={Paths.ROOT} element={<CoreContainer />} />
<Route path={Paths.PROJECTS} element={<CoreContainer />} />
<Route path={Paths.BOARDS} element={<CoreContainer />} />
<Route path={Paths.CARDS} element={<CoreContainer />} />
<Route path="*" element={<NotFound />} />
</Routes>
</ReduxRouter>
</Provider>
</AuthProvider>
);
}
Root.propTypes = {
/* eslint-disable react/forbid-prop-types */
store: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
config: PropTypes.object.isRequired,
/* eslint-enable react/forbid-prop-types */
};
@@ -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 (!cleanData.name) {
nameField.current.select();
return;
}
if (isNameEditable) {
if (!cleanData.name) {
nameField.current.select();
return;
}
onUpdate(cleanData);
}, [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,33 +5,40 @@ import { Popup } from '../lib/custom-ui';
import UserInformationEdit from './UserInformationEdit';
const UserInformationEditStep = React.memo(({ defaultData, onUpdate, onBack, onClose }) => {
const [t] = useTranslation();
const UserInformationEditStep = React.memo(
({ defaultData, isNameEditable, onUpdate, onBack, onClose }) => {
const [t] = useTranslation();
const handleUpdate = useCallback(
(data) => {
onUpdate(data);
onClose();
},
[onUpdate, onClose],
);
const handleUpdate = useCallback(
(data) => {
onUpdate(data);
onClose();
},
[onUpdate, onClose],
);
return (
<>
<Popup.Header onBack={onBack}>
{t('common.editInformation', {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<UserInformationEdit defaultData={defaultData} onUpdate={handleUpdate} />
</Popup.Content>
</>
);
});
return (
<>
<Popup.Header onBack={onBack}>
{t('common.editInformation', {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<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,63 +104,67 @@ const AccountPane = React.memo(
value={language || 'auto'}
onChange={handleLanguageChange}
/>
<Divider horizontal section>
<Header as="h4">
{t('common.authentication', {
context: 'title',
})}
</Header>
</Divider>
<div className={styles.action}>
<UserUsernameEditPopup
usePasswordConfirmation
defaultData={usernameUpdateForm.data}
username={username}
isSubmitting={usernameUpdateForm.isSubmitting}
error={usernameUpdateForm.error}
onUpdate={onUsernameUpdate}
onMessageDismiss={onUsernameUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editUsername', {
context: 'title',
})}
</Button>
</UserUsernameEditPopup>
</div>
<div className={styles.action}>
<UserEmailEditPopup
usePasswordConfirmation
defaultData={emailUpdateForm.data}
email={email}
isSubmitting={emailUpdateForm.isSubmitting}
error={emailUpdateForm.error}
onUpdate={onEmailUpdate}
onMessageDismiss={onEmailUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editEmail', {
context: 'title',
})}
</Button>
</UserEmailEditPopup>
</div>
<div className={styles.action}>
<UserPasswordEditPopup
usePasswordConfirmation
defaultData={passwordUpdateForm.data}
isSubmitting={passwordUpdateForm.isSubmitting}
error={passwordUpdateForm.error}
onUpdate={onPasswordUpdate}
onMessageDismiss={onPasswordUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editPassword', {
context: 'title',
})}
</Button>
</UserPasswordEditPopup>
</div>
{!isLocked && (
<>
<Divider horizontal section>
<Header as="h4">
{t('common.authentication', {
context: 'title',
})}
</Header>
</Divider>
<div className={styles.action}>
<UserUsernameEditPopup
usePasswordConfirmation
defaultData={usernameUpdateForm.data}
username={username}
isSubmitting={usernameUpdateForm.isSubmitting}
error={usernameUpdateForm.error}
onUpdate={onUsernameUpdate}
onMessageDismiss={onUsernameUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editUsername', {
context: 'title',
})}
</Button>
</UserUsernameEditPopup>
</div>
<div className={styles.action}>
<UserEmailEditPopup
usePasswordConfirmation
defaultData={emailUpdateForm.data}
email={email}
isSubmitting={emailUpdateForm.isSubmitting}
error={emailUpdateForm.error}
onUpdate={onEmailUpdate}
onMessageDismiss={onEmailUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editEmail', {
context: 'title',
})}
</Button>
</UserEmailEditPopup>
</div>
<div className={styles.action}>
<UserPasswordEditPopup
usePasswordConfirmation
defaultData={passwordUpdateForm.data}
isSubmitting={passwordUpdateForm.isSubmitting}
error={passwordUpdateForm.error}
onUpdate={onPasswordUpdate}
onMessageDismiss={onPasswordUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editPassword', {
context: 'title',
})}
</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,
@@ -16,6 +16,7 @@ const UserSettingsModal = React.memo(
phone,
organization,
language,
isLocked,
subscribeToOwnCards,
isAvatarUpdating,
usernameUpdateForm,
@@ -48,6 +49,7 @@ const UserSettingsModal = React.memo(
phone={phone}
organization={organization}
language={language}
isLocked={isLocked}
isAvatarUpdating={isAvatarUpdating}
usernameUpdateForm={usernameUpdateForm}
emailUpdateForm={emailUpdateForm}
@@ -104,6 +106,7 @@ UserSettingsModal.propTypes = {
phone: PropTypes.string,
organization: PropTypes.string,
language: PropTypes.string,
isLocked: PropTypes.bool.isRequired,
subscribeToOwnCards: PropTypes.bool.isRequired,
isAvatarUpdating: PropTypes.bool.isRequired,
/* eslint-disable react/forbid-prop-types */
@@ -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,26 +136,30 @@ const ActionsStep = React.memo(
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
{t('action.editUsername', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>
{t('action.editEmail', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditPasswordClick}>
{t('action.editPassword', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
{t('action.deleteUser', {
context: 'title',
})}
</Menu.Item>
{!user.isLocked && (
<>
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
{t('action.editUsername', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>
{t('action.editEmail', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditPasswordClick}>
{t('action.editPassword', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
{t('action.deleteUser', {
context: 'title',
})}
</Menu.Item>
</>
)}
</Menu>
</Popup.Content>
</>
@@ -17,6 +17,7 @@ const Item = React.memo(
organization,
phone,
isAdmin,
isLocked,
emailUpdateForm,
passwordUpdateForm,
usernameUpdateForm,
@@ -46,7 +47,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={isLocked} onChange={handleIsAdminChange} />
</Table.Cell>
<Table.Cell textAlign="right">
<ActionsPopup
@@ -57,6 +58,7 @@ const Item = React.memo(
organization,
phone,
isAdmin,
isLocked,
emailUpdateForm,
passwordUpdateForm,
usernameUpdateForm,
@@ -88,6 +90,7 @@ Item.propTypes = {
organization: PropTypes.string,
phone: PropTypes.string,
isAdmin: PropTypes.bool.isRequired,
isLocked: PropTypes.bool.isRequired,
/* eslint-disable react/forbid-prop-types */
emailUpdateForm: PropTypes.object.isRequired,
passwordUpdateForm: PropTypes.object.isRequired,
@@ -110,6 +110,7 @@ const UsersModal = React.memo(
organization={item.organization}
phone={item.phone}
isAdmin={item.isAdmin}
isLocked={item.isLocked}
emailUpdateForm={item.emailUpdateForm}
passwordUpdateForm={item.passwordUpdateForm}
usernameUpdateForm={item.usernameUpdateForm}
+2
View File
@@ -2,6 +2,7 @@ import Config from './Config';
const ROOT = `${Config.BASE_PATH}/`;
const LOGIN = `${Config.BASE_PATH}/login`;
const OIDC_LOGIN = `${Config.BASE_PATH}/oidclogin`;
const PROJECTS = `${Config.BASE_PATH}/projects/:id`;
const BOARDS = `${Config.BASE_PATH}/boards/:id`;
const CARDS = `${Config.BASE_PATH}/cards/:id`;
@@ -12,4 +13,5 @@ export default {
PROJECTS,
BOARDS,
CARDS,
OIDC_LOGIN,
};
@@ -0,0 +1,26 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import entryActions from '../entry-actions';
import OidcLogin from '../components/OIDC';
const mapStateToProps = ({
ui: {
authenticateForm: { data: defaultData, isSubmitting, error },
},
}) => ({
defaultData,
isSubmitting,
error,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators(
{
onAuthenticate: entryActions.authenticate,
onMessageDismiss: entryActions.clearAuthenticateError,
},
dispatch,
);
export default connect(mapStateToProps, mapDispatchToProps)(OidcLogin);
@@ -14,6 +14,7 @@ const mapStateToProps = (state) => {
phone,
organization,
language,
isLocked,
subscribeToOwnCards,
isAvatarUpdating,
emailUpdateForm,
@@ -29,6 +30,7 @@ const mapStateToProps = (state) => {
phone,
organization,
language,
isLocked,
subscribeToOwnCards,
isAvatarUpdating,
emailUpdateForm,
+7 -3
View File
@@ -1,11 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import Config from './constants/Config';
import store from './store';
import history from './history';
import Root from './components/Root';
import './i18n';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(React.createElement(Root, { store, history }));
fetch(`${Config.SERVER_BASE_URL}/api/appconfig`).then((response) => {
response.json().then((config) => {
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(React.createElement(Root, { store, history, config }));
});
});
+233
View File
@@ -0,0 +1,233 @@
export default {
format: {
date: 'M/d/yyyy',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'MMM d',
longDateTime: "MMMM d 'at' p",
},
translation: {
common: {
aboutPlanka: 'حول بلانكا',
account: 'الحساب',
actions: 'الإجراءات',
addAttachment_title: 'إضافة مرفق',
addComment: 'إضافة تعليق',
addManager_title: 'إضافة مدير',
addMember_title: 'إضافة عضو',
addUser_title: 'إضافة مستخدم',
administrator: 'المدير',
all: 'الكل',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'سيتم حفظ جميع التغييرات تلقائيًا<br />عند استعادة الاتصال',
areYouSureYouWantToDeleteThisAttachment: 'هل أنت متأكد أنك تريد حذف هذا المرفق؟',
areYouSureYouWantToDeleteThisBoard: 'هل أنت متأكد أنك تريد حذف هذا اللوح؟',
areYouSureYouWantToDeleteThisCard: 'هل أنت متأكد أنك تريد حذف هذه البطاقة؟',
areYouSureYouWantToDeleteThisComment: 'هل أنت متأكد أنك تريد حذف هذا التعليق؟',
areYouSureYouWantToDeleteThisLabel: 'هل أنت متأكد أنك تريد حذف هذه العلامة؟',
areYouSureYouWantToDeleteThisList: 'هل أنت متأكد أنك تريد حذف هذه القائمة؟',
areYouSureYouWantToDeleteThisProject: 'هل أنت متأكد أنك تريد حذف هذا المشروع؟',
areYouSureYouWantToDeleteThisTask: 'هل أنت متأكد أنك تريد حذف هذه المهمة؟',
areYouSureYouWantToDeleteThisUser: 'هل أنت متأكد أنك تريد حذف هذا المستخدم؟',
areYouSureYouWantToLeaveBoard: 'هل أنت متأكد أنك تريد مغادرة اللوحة؟',
areYouSureYouWantToLeaveProject: 'هل أنت متأكد أنك تريد مغادرة المشروع؟',
areYouSureYouWantToRemoveThisManagerFromProject:
'هل أنت متأكد أنك تريد إزالة هذا المدير من المشروع؟',
areYouSureYouWantToRemoveThisMemberFromBoard:
'هل أنت متأكد أنك تريد إزالة هذا العضو من اللوحة؟',
attachment: 'المرفق',
attachments: 'المرفقات',
authentication: 'المصادقة',
background: 'الخلفية',
board: 'اللوحة',
boardNotFound_title: 'اللوحة غير موجودة',
canComment: 'يمكن التعليق',
canEditContentOfBoard: 'يمكن تحرير محتوى اللوحة',
canOnlyViewBoard: 'يمكن فقط عرض اللوحة',
cardActions_title: 'إجراءات البطاقة',
cardNotFound_title: 'البطاقة غير موجودة',
cardOrActionAreDeleted: 'تم حذف البطاقة أو الإجراء',
color: 'اللون',
createBoard_title: 'إنشاء لوحة',
createLabel_title: 'إنشاء علامة',
createNewOneOrSelectExistingOne: 'إنشاء واحدة جديدة أو اختيار<br />واحدة موجودة بالفعل',
createProject_title: 'إنشاء مشروع',
createTextFile_title: 'إنشاء ملف نصي',
currentPassword: 'كلمة المرور الحالية',
dangerZone_title: 'المنطقة الخطرة',
date: 'التاريخ',
dueDate_title: 'تاريخ الاستحقاق',
deleteAttachment_title: 'حذف المرفق',
deleteBoard_title: 'حذف اللوحة',
deleteCard_title: 'حذف البطاقة',
deleteComment_title: 'حذف التعليق',
deleteLabel_title: 'حذف العلامة',
deleteList_title: 'حذف القائمة',
deleteProject_title: 'حذف المشروع',
deleteTask_title: 'حذف المهمة',
deleteUser_title: 'حذف المستخدم',
description: 'الوصف',
detectAutomatically: 'الكشف تلقائياً',
dropFileToUpload: 'إفلات الملف للتحميل',
editor: 'المحرر',
editAttachment_title: 'تعديل المرفق',
editAvatar_title: 'تعديل الصورة الرمزية',
editBoard_title: 'تعديل اللوحة',
editDueDate_title: 'تعديل تاريخ الاستحقاق',
editEmail_title: 'تعديل البريد الإلكتروني',
editInformation_title: 'تعديل المعلومات',
editLabel_title: 'تعديل العلامة',
editPassword_title: 'تعديل كلمة المرور',
editPermissions_title: 'تعديل الصلاحيات',
editStopwatch_title: 'تعديل ساعة التوقف',
editUsername_title: 'تعديل اسم المستخدم',
email: 'البريد الإلكتروني',
emailAlreadyInUse: 'البريد الإلكتروني مستخدم بالفعل',
enterCardTitle: 'أدخل عنوان البطاقة... [Ctrl+Enter] للفتح التلقائي',
enterDescription: 'أدخل الوصف...',
enterFilename: 'أدخل اسم الملف',
enterListTitle: 'أدخل عنوان القائمة...',
enterProjectTitle: 'أدخل عنوان المشروع',
enterTaskDescription: 'أدخل وصف المهمة...',
filterByLabels_title: 'تصفية حسب العلامات',
filterByMembers_title: 'تصفية حسب الأعضاء',
fromComputer_title: 'من الكمبيوتر',
fromTrello: 'من تريلو',
general: 'عام',
hours: 'ساعات',
importBoard_title: 'استيراد اللوحة',
invalidCurrentPassword: 'كلمة المرور الحالية غير صالحة',
labels: 'العلامات',
language: 'اللغة',
leaveBoard_title: 'مغادرة اللوحة',
leaveProject_title: 'مغادرة المشروع',
list: 'القائمة',
listActions_title: 'إجراءات القائمة',
managers: 'المديرين',
members: 'الأعضاء',
minutes: 'دقائق',
moveCard_title: 'نقل البطاقة',
name: 'الاسم',
newEmail: 'البريد الإلكتروني الجديد',
newPassword: 'كلمة المرور الجديدة',
newUsername: 'اسم المستخدم الجديد',
noConnectionToServer: 'لا يوجد اتصال بالخادم',
noBoards: 'لا توجد لوحات',
noLists: 'لا توجد قوائم',
noProjects: 'لا توجد مشاريع',
notifications: 'الإشعارات',
noUnreadNotifications: 'لا توجد إشعارات غير مقروءة',
openBoard_title: 'فتح اللوحة',
optional_inline: 'اختياري',
organization: 'المنظمة',
phone: 'الهاتف',
preferences: 'التفضيلات',
pressPasteShortcutToAddAttachmentFromClipboard:
'نصيحة: اضغط على Ctrl-V (Cmd-V على ماك) لإضافة مرفق من الحافظة',
project: 'المشروع',
projectNotFound_title: 'المشروع غير موجود',
removeManager_title: 'إزالة المدير',
removeMember_title: 'إزالة العضو',
searchLabels: 'البحث في العلامات...',
searchMembers: 'البحث في الأعضاء...',
searchUsers: 'البحث في المستخدمين...',
seconds: 'ثواني',
selectBoard: 'اختيار اللوحة',
selectList: 'اختيار القائمة',
selectPermissions_title: 'اختيار الصلاحيات',
selectProject: 'اختيار المشروع',
settings: 'الإعدادات',
stopwatch: 'ساعة التوقف',
subscribeToMyOwnCardsByDefault: 'الاشتراك في بطاقاتي افتراضيًا',
taskActions_title: 'إجراءات المهمة',
tasks: 'المهام',
thereIsNoPreviewAvailableForThisAttachment: 'لا يوجد معاينة متاحة لهذا المرفق',
time: 'الوقت',
title: 'العنوان',
userActions_title: 'إجراءات المستخدم',
userAddedThisCardToList: '<0>{{user}}</0><1> أضاف هذه البطاقة إلى {{list}}</1>',
userLeftNewCommentToCard: '{{user}} ترك تعليقًا جديدًا «{{comment}}» على <2>{{card}}</2>',
userMovedCardFromListToList: '{{user}} نقل <2>{{card}}</2> من {{fromList}} إلى {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> نقل هذه البطاقة من {{fromList}} إلى {{toList}}</1>',
username: 'اسم المستخدم',
usernameAlreadyInUse: 'اسم المستخدم مستخدم بالفعل',
users: 'المستخدمين',
version: 'الإصدار',
viewer: 'المشاهد',
writeComment: 'كتابة تعليق...',
},
action: {
addAnotherCard: 'إضافة بطاقة أخرى',
addAnotherList: 'إضافة قائمة أخرى',
addAnotherTask: 'إضافة مهمة أخرى',
addCard: 'إضافة بطاقة',
addCard_title: 'إضافة بطاقة',
addComment: 'إضافة تعليق',
addList: 'إضافة قائمة',
addMember: 'إضافة عضو',
addMoreDetailedDescription: 'إضافة وصف أكثر تفصيلًا',
addTask: 'إضافة مهمة',
addToCard: 'إضافة إلى البطاقة',
addUser: 'إضافة مستخدم',
createBoard: 'إنشاء لوحة',
createFile: 'إنشاء ملف',
createLabel: 'إنشاء علامة',
createNewLabel: 'إنشاء علامة جديدة',
createProject: 'إنشاء مشروع',
delete: 'حذف',
deleteAttachment: 'حذف المرفق',
deleteAvatar: 'حذف الصورة الرمزية',
deleteBoard: 'حذف اللوحة',
deleteCard: 'حذف البطاقة',
deleteCard_title: 'حذف البطاقة',
deleteComment: 'حذف التعليق',
deleteImage: 'حذف الصورة',
deleteLabel: 'حذف العلامة',
deleteList: 'حذف القائمة',
deleteList_title: 'حذف القائمة',
deleteProject: 'حذف المشروع',
deleteProject_title: 'حذف المشروع',
deleteTask: 'حذف المهمة',
deleteTask_title: 'حذف المهمة',
deleteUser: 'حذف المستخدم',
edit: 'تعديل',
editDueDate_title: 'تعديل تاريخ الاستحقاق',
editDescription_title: 'تعديل الوصف',
editEmail_title: 'تعديل البريد الإلكتروني',
editInformation_title: 'تعديل المعلومات',
editPassword_title: 'تعديل كلمة المرور',
editPermissions: 'تعديل الصلاحيات',
editStopwatch_title: 'تعديل ساعة التوقف',
editTitle_title: 'تعديل العنوان',
editUsername_title: 'تعديل اسم المستخدم',
hideDetails: 'إخفاء التفاصيل',
import: 'استيراد',
leaveBoard: 'مغادرة اللوحة',
leaveProject: 'مغادرة المشروع',
logOut_title: 'تسجيل الخروج',
makeCover_title: 'جعلها غلافًا',
move: 'نقل',
moveCard_title: 'نقل البطاقة',
remove: 'إزالة',
removeBackground: 'إزالة الخلفية',
removeCover_title: 'إزالة الغلاف',
removeFromBoard: 'إزالة من اللوحة',
removeFromProject: 'إزالة من المشروع',
removeManager: 'إزالة المدير',
removeMember: 'إزالة العضو',
save: 'حفظ',
showAllAttachments: 'عرض جميع المرفقات ({{hidden}} مخفية)',
showDetails: 'عرض التفاصيل',
showFewerAttachments: 'عرض مرفقات أقل',
start: 'بدء',
stop: 'إيقاف',
subscribe: 'الاشتراك',
unsubscribe: 'إلغاء الاشتراك',
uploadNewAvatar: 'تحميل صورة رمزية جديدة',
uploadNewImage: 'تحميل صورة جديدة',
},
},
};
+11
View File
@@ -0,0 +1,11 @@
import merge from 'lodash/merge';
import login from './login';
import core from './core';
export default {
language: 'ar',
country: 'ar',
name: 'Arabic',
embeddedLocale: merge(login, core),
};
+20
View File
@@ -0,0 +1,20 @@
export default {
translation: {
common: {
emailOrUsername: 'البريد الإلكتروني أو اسم المستخدم',
invalidEmailOrUsername: 'بريد إلكتروني أو اسم مستخدم غير صالح',
invalidPassword: 'كلمة مرور غير صالحة',
logInToPlanka: 'تسجيل الدخول إلى بلانكا',
noInternetConnection: 'لا يوجد اتصال بالإنترنت',
pageNotFound_title: 'الصفحة غير موجودة',
password: 'كلمة المرور',
projectManagement: 'إدارة المشروع',
serverConnectionFailed: 'فشل الاتصال بالخادم',
unknownError: 'خطأ غير معروف، حاول مرة أخرى لاحقًا',
},
action: {
logIn: 'تسجيل الدخول',
},
},
};
+235
View File
@@ -0,0 +1,235 @@
export default {
format: {
date: 'M/d/yyyy',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'MMM d',
longDateTime: "MMMM d 'তে' p",
},
translation: {
common: {
aboutPlanka: 'প্লাঙ্কা সম্পর্কে',
account: 'অ্যাকাউন্ট',
actions: 'ক্রিয়া',
addAttachment_title: 'সংযুক্তি যোগ করুন',
addComment: 'মন্তব্য যোগ করুন',
addManager_title: 'ম্যানেজার যোগ করুন',
addMember_title: 'সদস্য যোগ করুন',
addUser_title: 'ব্যবহারকারী যোগ করুন',
administrator: 'প্রশাসক',
all: 'সব',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'সমস্ত পরিবর্তন স্বয়ংক্রিয়ভাবে সংরক্ষিত হবে<br />যখন সংযোগ পুনর্স্থাপন হবে',
areYouSureYouWantToDeleteThisAttachment:
'আপনি কি নিশ্চিত যে আপনি এই সংযুক্তি মুছে ফেলতে চান?',
areYouSureYouWantToDeleteThisBoard: 'আপনি কি নিশ্চিত যে আপনি এই বোর্ড মুছে ফেলতে চান?',
areYouSureYouWantToDeleteThisCard: 'আপনি কি নিশ্চিত যে আপনি এই কার্ড মুছে ফেলতে চান?',
areYouSureYouWantToDeleteThisComment: 'আপনি কি নিশ্চিত যে আপনি এই মন্তব্য মুছে ফেলতে চান?',
areYouSureYouWantToDeleteThisLabel: 'আপনি কি নিশ্চিত যে আপনি এই লেবেল মুছে ফেলতে চান?',
areYouSureYouWantToDeleteThisList: 'আপনি কি নিশ্চিত যে আপনি এই তালিকা মুছে ফেলতে চান?',
areYouSureYouWantToDeleteThisProject: 'আপনি কি নিশ্চিত যে আপনি এই প্রকল্প মুছে ফেলতে চান?',
areYouSureYouWantToDeleteThisTask: 'আপনি কি নিশ্চিত যে আপনি এই টাস্ক মুছে ফেলতে চান?',
areYouSureYouWantToDeleteThisUser: 'আপনি কি নিশ্চিত যে আপনি এই ব্যবহারকারী মুছে ফেলতে চান?',
areYouSureYouWantToLeaveBoard: 'আপনি কি নিশ্চিত যে আপনি বোর্ড ত্যাগ করতে চান?',
areYouSureYouWantToLeaveProject: 'আপনি কি নিশ্চিত যে আপনি প্রকল্প ত্যাগ করতে চান?',
areYouSureYouWantToRemoveThisManagerFromProject:
'আপনি কি নিশ্চিত যে আপনি এই প্রকল্প থেকে এই ম্যানেজারকে সরাতে চান?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'আপনি কি নিশ্চিত যে আপনি এই মেম্বারকে বোর্ড থেকে সরাতে চান?',
attachment: 'সংযুক্তি',
attachments: 'সংযুক্তিসমূহ',
authentication: 'প্রমাণীকরণ',
background: 'পেশাদার',
board: 'বোর্ড',
boardNotFound_title: 'বোর্ড পাওয়া যায়নি',
canComment: 'মন্তব্য করতে পারে',
canEditContentOfBoard: 'বোর্ডের সামগ্রী সম্পাদনা করতে পারে',
canOnlyViewBoard: 'মাত্র বোর্ড দেখতে পারে',
cardActions_title: 'কার্ড ক্রিয়াকলাপ',
cardNotFound_title: 'কার্ড পাওয়া যায়নি',
cardOrActionAreDeleted: 'কার্ড বা ক্রিয়াকলাপ মোছে গেছে',
color: 'রং',
createBoard_title: 'বোর্ড তৈরি করুন',
createLabel_title: 'লেবেল তৈরি করুন',
createNewOneOrSelectExistingOne: 'নতুন তৈরি করুন অথবা<br />বিদ্যমান একটি নির্বাচন করুন',
createProject_title: 'প্রকল্প তৈরি করুন',
createTextFile_title: 'টেক্সট ফাইল তৈরি করুন',
currentPassword: 'বর্তমান পাসওয়ার্ড',
dangerZone_title: 'আপত্তি জোন',
date: 'তারিখ',
dueDate_title: 'নির্ধারণিত তারিখ',
deleteAttachment_title: 'সংযুক্তি মুছে ফেলুন',
deleteBoard_title: 'বোর্ড মুছে ফেলুন',
deleteCard_title: 'কার্ড মুছে ফেলুন',
deleteComment_title: 'মন্তব্য মুছে ফেলুন',
deleteLabel_title: 'লেবেল মুছে ফেলুন',
deleteList_title: 'তালিকা মুছে ফেলুন',
deleteProject_title: 'প্রকল্প মুছে ফেলুন',
deleteTask_title: 'টাস্ক মুছে ফেলুন',
deleteUser_title: 'ব্যবহারকারী মুছে ফেলুন',
description: 'বর্ণনা',
detectAutomatically: 'স্বয়ংক্রিয়ভাবে চেষ্টা করুন',
dropFileToUpload: 'আপলোড করার জন্য ফাইল প্রস্থান করুন',
editor: 'সম্পাদক',
editAttachment_title: 'সংযুক্তি সম্পাদনা করুন',
editAvatar_title: 'অবতার সম্পাদনা করুন',
editBoard_title: 'বোর্ড সম্পাদনা করুন',
editDueDate_title: 'নির্ধারণিত তারিখ সম্পাদনা করুন',
editEmail_title: 'ইমেইল সম্পাদনা করুন',
editInformation_title: 'তথ্য সম্পাদনা করুন',
editLabel_title: 'লেবেল সম্পাদনা করুন',
editPassword_title: 'পাসওয়ার্ড সম্পাদনা করুন',
editPermissions_title: 'অনুমতি সম্পাদনা করুন',
editStopwatch_title: 'স্টপওয়াচ সম্পাদনা করুন',
editUsername_title: 'ব্যবহারকারীর নাম সম্পাদনা করুন',
email: 'ইমেইল',
emailAlreadyInUse: 'ইমেইল ইতিমধ্যে ব্যবহৃত',
enterCardTitle: 'কার্ডের শিরোনাম লিখুন... [Ctrl+Enter] দিয়ে স্বয়ংক্রিয়ভাবে খোলুন',
enterDescription: 'বর্ণনা লিখুন...',
enterFilename: 'ফাইলের নাম লিখুন',
enterListTitle: 'তালিকা শিরোনাম লিখুন...',
enterProjectTitle: 'প্রকল্পের শিরোনাম লিখুন',
enterTaskDescription: 'টাস্কের বর্ণনা লিখুন...',
filterByLabels_title: 'লেবেল দ্বারা ছাঁকো',
filterByMembers_title: 'সদস্য দ্বারা ছাঁকো',
fromComputer_title: 'কম্পিউটার থেকে',
fromTrello: 'ট্রেলো থেকে',
general: 'সাধারণ',
hours: 'ঘণ্টা',
importBoard_title: 'বোর্ড আমদানি করুন',
invalidCurrentPassword: 'বর্তমান পাসওয়ার্ড অবৈধ',
labels: 'লেবেল',
language: 'ভাষা',
leaveBoard_title: 'বোর্ড ত্যাগ করুন',
leaveProject_title: 'প্রকল্প ত্যাগ করুন',
list: 'তালিকা',
listActions_title: 'তালিকা ক্রিয়াকলাপ',
managers: 'ম্যানেজারসমূহ',
members: 'সদস্যগণ',
minutes: 'মিনিট',
moveCard_title: 'কার্ড সরান',
name: 'নাম',
newEmail: 'নতুন ইমেইল',
newPassword: 'নতুন পাসওয়ার্ড',
newUsername: 'নতুন ব্যবহারকারীর নাম',
noConnectionToServer: 'সার্ভারে সংযোগ নেই',
noBoards: 'কোন বোর্ড নেই',
noLists: 'কোন তালিকা নেই',
noProjects: 'কোন প্রকল্প নেই',
notifications: 'বিজ্ঞপ্তি',
noUnreadNotifications: 'কোন অপঠিত বিজ্ঞপ্তি নেই',
openBoard_title: 'বোর্ড খুলুন',
optional_inline: 'ঐচ্ছিক',
organization: 'সংগঠন',
phone: 'ফোন',
preferences: 'পছন্দ',
pressPasteShortcutToAddAttachmentFromClipboard:
'প্রয়োজনীয়: Ctrl-V (Mac উপর Cmd-V) চাপুন যেটাতে ক্লিপবোর্ড থেকে সংযুক্তি যোগ করতে পারবেন',
project: 'প্রকল্প',
projectNotFound_title: 'প্রকল্প পাওয়া যায়নি',
removeManager_title: 'ম্যানেজার সরান',
removeMember_title: 'সদস্য সরান',
searchLabels: 'লেবেল খুঁজুন...',
searchMembers: 'সদস্য খুঁজুন...',
searchUsers: 'ব্যবহারকারী খুঁজুন...',
seconds: 'সেকেন্ড',
selectBoard: 'বোর্ড নির্বাচন করুন',
selectList: 'তালিকা নির্বাচন করুন',
selectPermissions_title: 'অনুমতি নির্বাচন করুন',
selectProject: 'প্রকল্প নির্বাচন করুন',
settings: 'সেটিংস',
stopwatch: 'স্টপওয়াচ',
subscribeToMyOwnCardsByDefault: 'ডিফল্ট অবস্থায় আমার নিজের কার্ডে সাবস্ক্রাইব করুন',
taskActions_title: 'টাস্ক ক্রিয়াকলাপ',
tasks: 'টাস্কগুলি',
thereIsNoPreviewAvailableForThisAttachment: 'এই সংযুক্তির জন্য কোন পূর্বরূপ নেই',
time: 'সময়',
title: 'শিরোনাম',
userActions_title: 'ব্যবহারকারী ক্রিয়াকলাপ',
userAddedThisCardToList: '<0>{{user}}</0><1> {{list}} তে এই কার্ড যোগ করেছেন</1>',
userLeftNewCommentToCard: '{{user}} নতুন মন্তব্য ছেড়েছেন «{{comment}}» <2>{{card}}</2> এ',
userMovedCardFromListToList:
'{{user}} <2>{{card}}</2> মোব করেছেন {{fromList}} থেকে {{toList}} এ',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> {{fromList}} থেকে {{toList}} এ এই কার্ড মোব করেছেন</1>',
username: 'ব্যবহারকারীর নাম',
usernameAlreadyInUse: 'ব্যবহারকারীর নাম ইতিমধ্যে ব্যবহৃত',
users: 'ব্যবহারকারীগণ',
version: 'সংস্করণ',
viewer: 'দর্শক',
writeComment: 'মন্তব্য লিখুন...',
},
action: {
addAnotherCard: 'আরেকটি কার্ড যোগ করুন',
addAnotherList: 'আরেকটি তালিকা যোগ করুন',
addAnotherTask: 'আরেকটি টাস্ক যোগ করুন',
addCard: 'কার্ড যোগ করুন',
addCard_title: 'কার্ড যোগ করুন',
addComment: 'মন্তব্য যোগ করুন',
addList: 'তালিকা যোগ করুন',
addMember: 'সদস্য যোগ করুন',
addMoreDetailedDescription: 'আরও বিস্তারিত বর্ণনা যোগ করুন',
addTask: 'টাস্ক যোগ করুন',
addToCard: 'কার্ডে যোগ করুন',
addUser: 'ব্যবহারকারী যোগ করুন',
createBoard: 'বোর্ড তৈরি করুন',
createFile: 'ফাইল তৈরি করুন',
createLabel: 'লেবেল তৈরি করুন',
createNewLabel: 'নতুন লেবেল তৈরি করুন',
createProject: 'প্রকল্প তৈরি করুন',
delete: 'মুছে ফেলুন',
deleteAttachment: 'সংযুক্তি মুছে ফেলুন',
deleteAvatar: 'অবতার মুছে ফেলুন',
deleteBoard: 'বোর্ড মুছে ফেলুন',
deleteCard: 'কার্ড মুছে ফেলুন',
deleteCard_title: 'কার্ড মুছে ফেলুন',
deleteComment: 'মন্তব্য মুছে ফেলুন',
deleteImage: 'চিত্র মুছে ফেলুন',
deleteLabel: 'লেবেল মুছে ফেলুন',
deleteList: 'তালিকা মুছে ফেলুন',
deleteList_title: 'তালিকা মুছে ফেলুন',
deleteProject: 'প্রকল্প মুছে ফেলুন',
deleteProject_title: 'প্রকল্প মুছে ফেলুন',
deleteTask: 'টাস্ক মুছে ফেলুন',
deleteTask_title: 'টাস্ক মুছে ফেলুন',
deleteUser: 'ব্যবহারকারী মুছে ফেলুন',
edit: 'সম্পাদনা করুন',
editDueDate_title: 'নির্ধারণিত তারিখ সম্পাদনা করুন',
editDescription_title: 'বর্ণনা সম্পাদনা করুন',
editEmail_title: 'ইমেইল সম্পাদনা করুন',
editInformation_title: 'তথ্য সম্পাদনা করুন',
editPassword_title: 'পাসওয়ার্ড সম্পাদনা করুন',
editPermissions: 'অনুমতি সম্পাদনা করুন',
editStopwatch_title: 'স্টপওয়াচ সম্পাদনা করুন',
editTitle_title: 'শিরোনাম সম্পাদনা করুন',
editUsername_title: 'ব্যবহারকারীর নাম সম্পাদনা করুন',
hideDetails: 'বিশদ লুকান',
import: 'আমদানি করুন',
leaveBoard: 'বোর্ড ত্যাগ করুন',
leaveProject: 'প্রকল্প ত্যাগ করুন',
logOut_title: 'লগ আউট',
makeCover_title: 'কভার তৈরি করুন',
move: 'সরান',
moveCard_title: 'কার্ড সরান',
remove: 'সরান',
removeBackground: 'পেশাদার সরান',
removeCover_title: 'কভার সরান',
removeFromBoard: 'বোর্ড থেকে সরান',
removeFromProject: 'প্রকল্প থেকে সরান',
removeManager: 'ম্যানেজার সরান',
removeMember: 'সদস্য সরান',
save: 'সংরক্ষণ করুন',
showAllAttachments: 'সমস্ত সংযুক্তিসমূহ দেখান ({{hidden}} লুকান)',
showDetails: 'বিশদ দেখান',
showFewerAttachments: 'কম সংযুক্তি দেখান',
start: 'শুরু',
stop: 'বন্ধ',
subscribe: 'সাবস্ক্রাইব করুন',
unsubscribe: 'সাবস্ক্রাইব বাতিল করুন',
uploadNewAvatar: 'নতুন অবতার আপলোড করুন',
uploadNewImage: 'নতুন চিত্র আপলোড করুন',
},
},
};
+11
View File
@@ -0,0 +1,11 @@
import merge from 'lodash/merge';
import login from './login';
import core from './core';
export default {
language: 'bn',
country: 'bn',
name: 'Bengali',
embeddedLocale: merge(login, core),
};
+20
View File
@@ -0,0 +1,20 @@
export default {
translation: {
common: {
emailOrUsername: 'ইমেইল বা ব্যবহারকারীর নাম',
invalidEmailOrUsername: 'অবৈধ ইমেইল বা ব্যবহারকারীর নাম',
invalidPassword: 'অবৈধ পাসওয়ার্ড',
logInToPlanka: 'Planka-তে লগ ইন করুন',
noInternetConnection: 'ইন্টারনেট সংযোগ নেই',
pageNotFound_title: 'পেজ পাওয়া যায়নি',
password: 'পাসওয়ার্ড',
projectManagement: 'প্রকল্প পরিচালনা',
serverConnectionFailed: 'সার্ভার সংযোগ ব্যর্থ হয়েছে',
unknownError: 'অজানা ত্রুটি, পরে আবার চেষ্টা করুন',
},
action: {
logIn: 'লগ ইন করুন',
},
},
};
+24
View File
@@ -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',
+236
View File
@@ -0,0 +1,236 @@
export default {
format: {
date: 'd/M/yyyy',
time: 'A',
dateTime: '$t(format:date), $t(format:time)',
longDate: 'd MMM',
longDateTime: "d MMMM 'को' $t(format:time)",
},
translation: {
common: {
aboutPlanka: 'Planka के बारे में',
account: 'खाता',
actions: 'क्रियाएँ',
addAttachment_title: 'अटैचमेंट जोड़ें',
addComment: 'टिप्पणी जोड़ें',
addManager_title: 'मैनेजर जोड़ें',
addMember_title: 'सदस्य जोड़ें',
addUser_title: 'उपयोगकर्ता जोड़ें',
administrator: 'प्रशासक',
all: 'सभी',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'सभी परिवर्तन अपने आप सहेजे जाएंगे<br />कनेक्शन बहाल होने के बाद',
areYouSureYouWantToDeleteThisAttachment: 'क्या आप वाकई इस अटैचमेंट को हटाना चाहते हैं?',
areYouSureYouWantToDeleteThisBoard: 'क्या आप वाकई इस बोर्ड को हटाना चाहते हैं?',
areYouSureYouWantToDeleteThisCard: 'क्या आप वाकई इस कार्ड को हटाना चाहते हैं?',
areYouSureYouWantToDeleteThisComment: 'क्या आप वाकई इस टिप्पणी को हटाना चाहते हैं?',
areYouSureYouWantToDeleteThisLabel: 'क्या आप वाकई इस लेबल को हटाना चाहते हैं?',
areYouSureYouWantToDeleteThisList: 'क्या आप वाकई इस सूची को हटाना चाहते हैं?',
areYouSureYouWantToDeleteThisProject: 'क्या आप वाकई इस परियोजना को हटाना चाहते हैं?',
areYouSureYouWantToDeleteThisTask: 'क्या आप वाकई इस कार्य को हटाना चाहते हैं?',
areYouSureYouWantToDeleteThisUser: 'क्या आप वाकई इस उपयोगकर्ता को हटाना चाहते हैं?',
areYouSureYouWantToLeaveBoard: 'क्या आप वाकई इस बोर्ड को छोड़ना चाहते हैं?',
areYouSureYouWantToLeaveProject: 'क्या आप वाकई इस परियोजना को छोड़ना चाहते हैं?',
areYouSureYouWantToRemoveThisManagerFromProject:
'क्या आप वाकई इस परियोजना से इस मैनेजर को हटाना चाहते हैं?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'क्या आप वाकई इस बोर्ड से इस सदस्य को हटाना चाहते हैं?',
attachment: 'अटैचमेंट',
attachments: 'अटैचमेंट्स',
authentication: 'प्रमाणीकरण',
background: 'पृष्ठभूमि',
board: 'बोर्ड',
boardNotFound_title: 'बोर्ड नहीं मिला',
canComment: 'टिप्पणी कर सकते हैं',
canEditContentOfBoard: 'बोर्ड की सामग्री को संपादित कर सकते हैं',
canOnlyViewBoard: 'केवल बोर्ड देख सकते हैं',
cardActions_title: 'कार्ड क्रियाएँ',
cardNotFound_title: 'कार्ड नहीं मिला',
cardOrActionAreDeleted: 'कार्ड या क्रिया हटा दी गई हैं',
color: 'रंग',
createBoard_title: 'बोर्ड बनाएं',
createLabel_title: 'लेबल बनाएं',
createNewOneOrSelectExistingOne: 'नया बनाएं या<br />मौजूदा चुनें',
createProject_title: 'परियोजना बनाएं',
createTextFile_title: 'टेक्स्ट फ़ाइल बनाएं',
currentPassword: 'वर्तमान पासवर्ड',
dangerZone_title: 'खतरे का क्षेत्र',
date: 'तारीख',
dueDate_title: 'नियत तारीख',
deleteAttachment_title: 'अटैचमेंट हटाएं',
deleteBoard_title: 'बोर्ड हटाएं',
deleteCard_title: 'कार्ड हटाएं',
deleteComment_title: 'टिप्पणी हटाएं',
deleteLabel_title: 'लेबल हटाएं',
deleteList_title: 'सूची हटाएं',
deleteProject_title: 'परियोजना हटाएं',
deleteTask_title: 'कार्य हटाएं',
deleteUser_title: 'उपयोगकर्ता हटाएं',
description: 'विवरण',
detectAutomatically: 'स्वचालित खोजें',
dropFileToUpload: 'अपलोड करने के लिए फ़ाइल छोड़ें',
editor: 'संपादक',
editAttachment_title: 'अटैचमेंट संपादित करें',
editAvatar_title: 'अवतार संपादित करें',
editBoard_title: 'बोर्ड संपादित करें',
editDueDate_title: 'नियत तारीख संपादित करें',
editEmail_title: 'ईमेल संपादित करें',
editInformation_title: 'जानकारी संपादित करें',
editLabel_title: 'लेबल संपादित करें',
editPassword_title: 'पासवर्ड संपादित करें',
editPermissions_title: 'अनुमतियाँ संपादित करें',
editStopwatch_title: 'स्टॉपवॉच संपादित करें',
editUsername_title: 'उपयोगकर्ता नाम संपादित करें',
email: 'ईमेल',
emailAlreadyInUse: 'ईमेल पहले से उपयोग में है',
enterCardTitle: 'कार्ड शीर्षक दर्ज करें... [Ctrl+Enter] ऑटो-खुला करने के लिए',
enterDescription: 'विवरण दर्ज करें...',
enterFilename: 'फ़ाइल का नाम दर्ज करें',
enterListTitle: 'सूची का शीर्षक दर्ज करें...',
enterProjectTitle: 'परियोजना का शीर्षक दर्ज करें',
enterTaskDescription: 'कार्य का विवरण दर्ज करें...',
filterByLabels_title: 'लेबल से फ़िल्टर करें',
filterByMembers_title: 'सदस्यों से फ़िल्टर करें',
fromComputer_title: 'कंप्यूटर से',
fromTrello: 'Trello से',
general: 'सामान्य',
hours: 'घंटे',
importBoard_title: 'बोर्ड आयात करें',
invalidCurrentPassword: 'अमान्य वर्तमान पासवर्ड',
labels: 'लेबल',
language: 'भाषा',
leaveBoard_title: 'बोर्ड छोड़ें',
leaveProject_title: 'परियोजना छोड़ें',
list: 'सूची',
listActions_title: 'सूची क्रियाएँ',
managers: 'मैनेजर',
members: 'सदस्य',
minutes: 'मिनट',
moveCard_title: 'कार्ड को ले जाएं',
name: 'नाम',
newEmail: 'नई ईमेल',
newPassword: 'नया पासवर्ड',
newUsername: 'नया उपयोगकर्ता नाम',
noConnectionToServer: 'सर्वर से कोई जुड़ाव नहीं है',
noBoards: 'कोई बोर्ड नहीं',
noLists: 'कोई सूची नहीं',
noProjects: 'कोई परियोजनाएँ नहीं',
notifications: 'सूचनाएँ',
noUnreadNotifications: 'कोई अपठित सूचनाएँ नहीं हैं',
openBoard_title: 'बोर्ड खोलें',
optional_inline: 'वैकल्पिक',
organization: 'संगठन',
phone: 'फ़ोन',
preferences: 'प्राथमिकताएँ',
pressPasteShortcutToAddAttachmentFromClipboard:
'सुझाव: क्लिपबोर्ड से अटैचमेंट जोड़ने के लिए Ctrl-V (Mac पर Cmd-V) दबाएं',
project: 'परियोजना',
projectNotFound_title: 'परियोजना नहीं मिली',
removeManager_title: 'मैनेजर हटाएं',
removeMember_title: 'सदस्य हटाएं',
searchLabels: 'लेबल खोजें...',
searchMembers: 'सदस्य खोजें...',
searchUsers: 'उपयोगकर्ताओं को खोजें...',
seconds: 'सेकंड्स',
selectBoard: 'बोर्ड चुनें',
selectList: 'सूची चुनें',
selectPermissions_title: 'अनुमतियाँ चुनें',
selectProject: 'परियोजना चुनें',
settings: 'सेटिंग्स',
stopwatch: 'स्टॉपवॉच',
subscribeToMyOwnCardsByDefault: 'डिफ़ॉल्ट रूप से मेरे अपने कार्डों की सदस्यता लें',
taskActions_title: 'कार्य क्रियाएँ',
tasks: 'कार्य',
thereIsNoPreviewAvailableForThisAttachment:
'इस अटैचमेंट के लिए कोई पूर्वावलोकन उपलब्ध नहीं है',
time: 'समय',
title: 'शीर्षक',
userActions_title: 'उपयोगकर्ता क्रियाएँ',
userAddedThisCardToList: '<0>{{user}}</0><1> ने इस कार्ड को {{list}} में जोड़ा</1>',
userLeftNewCommentToCard:
'{{user}} ने कार्ड <2>{{card}}</2> पर नई टिप्पणी छोड़ी «{{comment}}»',
userMovedCardFromListToList:
'{{user}} ने <2>{{card}}</2> को {{fromList}} से {{toList}} में मूव किया',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> ने इस कार्ड को {{fromList}} से {{toList}} में मूव किया</1>',
username: 'उपयोगकर्ता नाम',
usernameAlreadyInUse: 'उपयोगकर्ता नाम पहले से उपयोग में है',
users: 'उपयोगकर्ताओं',
version: 'संस्करण',
viewer: 'दर्शक',
writeComment: 'टिप्पणी लिखें...',
},
action: {
addAnotherCard: 'एक और कार्ड जोड़ें',
addAnotherList: 'एक और सूची जोड़ें',
addAnotherTask: 'एक और कार्य जोड़ें',
addCard: 'कार्ड जोड़ें',
addCard_title: 'कार्ड जोड़ें',
addComment: 'टिप्पणी जोड़ें',
addList: 'सूची जोड़ें',
addMember: 'सदस्य जोड़ें',
addMoreDetailedDescription: 'अधिक विस्तारित विवरण जोड़ें',
addTask: 'कार्य जोड़ें',
addToCard: 'कार्ड में जोड़ें',
addUser: 'उपयोगकर्ता जोड़ें',
createBoard: 'बोर्ड बनाएं',
createFile: 'फ़ाइल बनाएं',
createLabel: 'लेबल बनाएं',
createNewLabel: 'नई लेबल बनाएं',
createProject: 'परियोजना बनाएं',
delete: 'हटाएं',
deleteAttachment: 'अटैचमेंट हटाएं',
deleteAvatar: 'अवतार हटाएं',
deleteBoard: 'बोर्ड हटाएं',
deleteCard: 'कार्ड हटाएं',
deleteCard_title: 'कार्ड हटाएं',
deleteComment: 'टिप्पणी हटाएं',
deleteImage: 'छवि हटाएं',
deleteLabel: 'लेबल हटाएं',
deleteList: 'सूची हटाएं',
deleteList_title: 'सूची हटाएं',
deleteProject: 'परियोजना हटाएं',
deleteProject_title: 'परियोजना हटाएं',
deleteTask: 'कार्य हटाएं',
deleteTask_title: 'कार्य हटाएं',
deleteUser: 'उपयोगकर्ता हटाएं',
edit: 'संपादित करें',
editDueDate_title: 'नियत तारीख संपादित करें',
editDescription_title: 'विवरण संपादित करें',
editEmail_title: 'ईमेल संपादित करें',
editInformation_title: 'जानकारी संपादित करें',
editPassword_title: 'पासवर्ड संपादित करें',
editPermissions: 'अनुमतियाँ संपादित करें',
editStopwatch_title: 'स्टॉपवॉच संपादित करें',
editTitle_title: 'शीर्षक संपादित करें',
editUsername_title: 'उपयोगकर्ता नाम संपादित करें',
hideDetails: 'विवरण छुपाएं',
import: 'आयात करें',
leaveBoard: 'बोर्ड छोड़ें',
leaveProject: 'परियोजना छोड़ें',
logOut_title: 'लॉग आउट',
makeCover_title: 'कवर बनाएं',
move: 'मूव करें',
moveCard_title: 'कार्ड को ले जाएं',
remove: 'हटाएं',
removeBackground: 'पृष्ठभूमि हटाएं',
removeCover_title: 'कवर हटाएं',
removeFromBoard: 'बोर्ड से हटाएं',
removeFromProject: 'परियोजना से हटाएं',
removeManager: 'मैनेजर हटाएं',
removeMember: 'सदस्य हटाएं',
save: 'सहेजें',
showAllAttachments: 'सभी अटैचमेंट दिखाएं ({{hidden}} छुपे हुए)',
showDetails: 'विवरण दिखाएं',
showFewerAttachments: 'कम अटैचमेंट दिखाएं',
start: 'शुरू करें',
stop: 'रोकें',
subscribe: 'सदस्यता लें',
unsubscribe: 'सदस्यता रद्द करें',
uploadNewAvatar: 'नया अवतार अपलोड करें',
uploadNewImage: 'नई छवि अपलोड करें',
},
},
};
+11
View File
@@ -0,0 +1,11 @@
import merge from 'lodash/merge';
import login from './login';
import core from './core';
export default {
language: 'hi',
country: 'hi',
name: 'Hindi',
embeddedLocale: merge(login, core),
};
+20
View File
@@ -0,0 +1,20 @@
export default {
translation: {
common: {
emailOrUsername: 'ईमेल या उपयोगकर्ता नाम',
invalidEmailOrUsername: 'अवैध ईमेल या उपयोगकर्ता नाम',
invalidPassword: 'अवैध पासवर्ड',
logInToPlanka: 'Planka में लॉग इन करें',
noInternetConnection: 'कोई इंटरनेट कनेक्शन नहीं',
pageNotFound_title: 'पृष्ठ नहीं मिला',
password: 'पासवर्ड',
projectManagement: 'परियोजना प्रबंधन',
serverConnectionFailed: 'सर्वर कनेक्शन विफल हो गया',
unknownError: 'अज्ञात त्रुटि, बाद में पुनः प्रयास करें',
},
action: {
logIn: 'लॉग इन करें',
},
},
};
+6 -1
View File
@@ -1,20 +1,25 @@
import ar from './ar';
import bn from './bn';
import cs from './cs';
import da from './da';
import de from './de';
import en from './en';
import es from './es';
import fr from './fr';
import hi from './hi';
import it from './it';
import ja from './ja';
import ko from './ko';
import nl from './nl';
import pl from './pl';
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 = [ar, bn, cs, da, de, en, es, fr, hi, it, ja, ko, nl, pl, ru, sk, sv, tr, uz, zh];
export default locales;
+236
View File
@@ -0,0 +1,236 @@
export default {
format: {
date: 'd-M-yyyy',
time: 'HH:mm',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'd MMM',
longDateTime: "d MMMM 'om' HH:mm",
},
translation: {
common: {
aboutPlanka: 'Over Planka',
account: 'Account',
actions: 'Acties',
addAttachment_title: 'Bijlage Toevoegen',
addComment: 'Opmerking Toevoegen',
addManager_title: 'Manager Toevoegen',
addMember_title: 'Lid Toevoegen',
addUser_title: 'Gebruiker Toevoegen',
administrator: 'Beheerder',
all: 'Alles',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Alle wijzigingen worden automatisch opgeslagen<br />nadat de verbinding is hersteld',
areYouSureYouWantToDeleteThisAttachment: 'Weet u zeker dat u deze bijlage wilt verwijderen?',
areYouSureYouWantToDeleteThisBoard: 'Weet u zeker dat u dit bord wilt verwijderen?',
areYouSureYouWantToDeleteThisCard: 'Weet u zeker dat u deze kaart wilt verwijderen?',
areYouSureYouWantToDeleteThisComment: 'Weet u zeker dat u deze opmerking wilt verwijderen?',
areYouSureYouWantToDeleteThisLabel: 'Weet u zeker dat u deze label wilt verwijderen?',
areYouSureYouWantToDeleteThisList: 'Weet u zeker dat u deze lijst wilt verwijderen?',
areYouSureYouWantToDeleteThisProject: 'Weet u zeker dat u dit project wilt verwijderen?',
areYouSureYouWantToDeleteThisTask: 'Weet u zeker dat u deze taak wilt verwijderen?',
areYouSureYouWantToDeleteThisUser: 'Weet u zeker dat u deze gebruiker wilt verwijderen?',
areYouSureYouWantToLeaveBoard: 'Weet u zeker dat u het bord wilt verlaten?',
areYouSureYouWantToLeaveProject: 'Weet u zeker dat u het project wilt verlaten?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Weet u zeker dat u deze manager uit het project wilt verwijderen?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'Weet u zeker dat u dit lid uit het bord wilt verwijderen?',
attachment: 'Bijlage',
attachments: 'Bijlagen',
authentication: 'Authenticatie',
background: 'Achtergrond',
board: 'Bord',
boardNotFound_title: 'Bord Niet Gevonden',
canComment: 'Kan opmerking plaatsen',
canEditContentOfBoard: 'Kan de inhoud van het bord bewerken',
canOnlyViewBoard: 'Kan alleen het bord bekijken',
cardActions_title: 'Kaartacties',
cardNotFound_title: 'Kaart Niet Gevonden',
cardOrActionAreDeleted: 'Kaart of actie zijn verwijderd',
color: 'Kleur',
createBoard_title: 'Bord Aanmaken',
createLabel_title: 'Label Aanmaken',
createNewOneOrSelectExistingOne: 'Maak een nieuwe aan of selecteer<br />een bestaande',
createProject_title: 'Project Aanmaken',
createTextFile_title: 'Tekstbestand Aanmaken',
currentPassword: 'Huidig wachtwoord',
dangerZone_title: 'Gevaarlijke Zone',
date: 'Datum',
dueDate_title: 'Vervaldatum',
deleteAttachment_title: 'Bijlage Verwijderen',
deleteBoard_title: 'Bord Verwijderen',
deleteCard_title: 'Kaart Verwijderen',
deleteComment_title: 'Opmerking Verwijderen',
deleteLabel_title: 'Label Verwijderen',
deleteList_title: 'Lijst Verwijderen',
deleteProject_title: 'Project Verwijderen',
deleteTask_title: 'Taak Verwijderen',
deleteUser_title: 'Gebruiker Verwijderen',
description: 'Beschrijving',
detectAutomatically: 'Automatisch detecteren',
dropFileToUpload: 'Sleep het bestand om te uploaden',
editor: 'Editor',
editAttachment_title: 'Bijlage Bewerken',
editAvatar_title: 'Avatar Bewerken',
editBoard_title: 'Bord Bewerken',
editDueDate_title: 'Vervaldatum Bewerken',
editEmail_title: 'E-mail Bewerken',
editInformation_title: 'Informatie Bewerken',
editLabel_title: 'Label Bewerken',
editPassword_title: 'Wachtwoord Bewerken',
editPermissions_title: 'Rechten Bewerken',
editStopwatch_title: 'Stopwatch Bewerken',
editUsername_title: 'Gebruikersnaam Bewerken',
email: 'E-mail',
emailAlreadyInUse: 'E-mail is al in gebruik',
enterCardTitle: 'Voer de kaarttitel in... [Ctrl+Enter] om automatisch te openen',
enterDescription: 'Voer beschrijving in...',
enterFilename: 'Voer bestandsnaam in',
enterListTitle: 'Voer de lijsttitel in...',
enterProjectTitle: 'Voer de projecttitel in',
enterTaskDescription: 'Voer taakbeschrijving in...',
filterByLabels_title: 'Filteren op Labels',
filterByMembers_title: 'Filteren op Leden',
fromComputer_title: 'Van Computer',
fromTrello: 'Van Trello',
general: 'Algemeen',
hours: 'Uren',
importBoard_title: 'Bord Importeren',
invalidCurrentPassword: 'Ongeldig huidig wachtwoord',
labels: 'Labels',
language: 'Taal',
leaveBoard_title: 'Bord Verlaten',
leaveProject_title: 'Project Verlaten',
list: 'Lijst',
listActions_title: 'Lijstacties',
managers: 'Managers',
members: 'Leden',
minutes: 'Minuten',
moveCard_title: 'Kaart Verplaatsen',
name: 'Naam',
newEmail: 'Nieuwe e-mail',
newPassword: 'Nieuw wachtwoord',
newUsername: 'Nieuwe gebruikersnaam',
noConnectionToServer: 'Geen verbinding met de server',
noBoards: 'Geen borden',
noLists: 'Geen lijsten',
noProjects: 'Geen projecten',
notifications: 'Meldingen',
noUnreadNotifications: 'Geen ongelezen meldingen',
openBoard_title: 'Bord Openen',
optional_inline: 'optioneel',
organization: 'Organisatie',
phone: 'Telefoon',
preferences: 'Voorkeuren',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: druk op Ctrl-V (Cmd-V op Mac) om een bijlage van het klembord toe te voegen',
project: 'Project',
projectNotFound_title: 'Project Niet Gevonden',
removeManager_title: 'Manager Verwijderen',
removeMember_title: 'Lid Verwijderen',
searchLabels: 'Zoek labels...',
searchMembers: 'Zoek leden...',
searchUsers: 'Zoek gebruikers...',
seconds: 'Seconden',
selectBoard: 'Selecteer bord',
selectList: 'Selecteer lijst',
selectPermissions_title: 'Selecteer Rechten',
selectProject: 'Selecteer project',
settings: 'Instellingen',
stopwatch: 'Stopwatch',
subscribeToMyOwnCardsByDefault: 'Standaard abonneren op mijn eigen kaarten',
taskActions_title: 'Taakacties',
tasks: 'Taken',
thereIsNoPreviewAvailableForThisAttachment:
'Er is geen voorbeeld beschikbaar voor deze bijlage',
time: 'Tijd',
title: 'Titel',
userActions_title: 'Gebruikersacties',
userAddedThisCardToList: '<0>{{user}}</0><1> voegde deze kaart toe aan {{list}}</1>',
userLeftNewCommentToCard:
'{{user}} liet een nieuwe opmerking achter «{{comment}}» bij <2>{{card}}</2>',
userMovedCardFromListToList:
'{{user}} verplaatste <2>{{kaart}}</2> van {{fromList}} naar {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> verplaatste deze kaart van {{fromList}} naar {{toList}}</1>',
username: 'Gebruikersnaam',
usernameAlreadyInUse: 'Gebruikersnaam is al in gebruik',
users: 'Gebruikers',
version: 'Versie',
viewer: 'Viewer',
writeComment: 'Schrijf een opmerking...',
},
action: {
addAnotherCard: 'Voeg nog een kaart toe',
addAnotherList: 'Voeg nog een lijst toe',
addAnotherTask: 'Voeg nog een taak toe',
addCard: 'Kaart toevoegen',
addCard_title: 'Kaart Toevoegen',
addComment: 'Opmerking toevoegen',
addList: 'Lijst toevoegen',
addMember: 'Lid toevoegen',
addMoreDetailedDescription: 'Voeg een meer gedetailleerde beschrijving toe',
addTask: 'Taak toevoegen',
addToCard: 'Toevoegen aan kaart',
addUser: 'Gebruiker toevoegen',
createBoard: 'Bord aanmaken',
createFile: 'Bestand aanmaken',
createLabel: 'Label aanmaken',
createNewLabel: 'Nieuw label aanmaken',
createProject: 'Project aanmaken',
delete: 'Verwijderen',
deleteAttachment: 'Bijlage verwijderen',
deleteAvatar: 'Avatar verwijderen',
deleteBoard: 'Bord verwijderen',
deleteCard: 'Kaart verwijderen',
deleteCard_title: 'Kaart Verwijderen',
deleteComment: 'Opmerking verwijderen',
deleteImage: 'Afbeelding verwijderen',
deleteLabel: 'Label verwijderen',
deleteList: 'Lijst verwijderen',
deleteList_title: 'Lijst Verwijderen',
deleteProject: 'Project verwijderen',
deleteProject_title: 'Project Verwijderen',
deleteTask: 'Taak verwijderen',
deleteTask_title: 'Taak Verwijderen',
deleteUser: 'Gebruiker verwijderen',
edit: 'Bewerken',
editDueDate_title: 'Vervaldatum Bewerken',
editDescription_title: 'Beschrijving Bewerken',
editEmail_title: 'E-mail Bewerken',
editInformation_title: 'Informatie Bewerken',
editPassword_title: 'Wachtwoord Bewerken',
editPermissions: 'Rechten Bewerken',
editStopwatch_title: 'Stopwatch Bewerken',
editTitle_title: 'Titel Bewerken',
editUsername_title: 'Gebruikersnaam Bewerken',
hideDetails: 'Details verbergen',
import: 'Importeren',
leaveBoard: 'Bord verlaten',
leaveProject: 'Project verlaten',
logOut_title: 'Uitloggen',
makeCover_title: 'Maak Omslag',
move: 'Verplaatsen',
moveCard_title: 'Kaart Verplaatsen',
remove: 'Verwijderen',
removeBackground: 'Achtergrond verwijderen',
removeCover_title: 'Omslag Verwijderen',
removeFromBoard: 'Verwijderen van bord',
removeFromProject: 'Verwijderen van project',
removeManager: 'Manager verwijderen',
removeMember: 'Lid verwijderen',
save: 'Opslaan',
showAllAttachments: 'Toon alle bijlagen ({{hidden}} verborgen)',
showDetails: 'Details weergeven',
showFewerAttachments: 'Toon minder bijlagen',
start: 'Start',
stop: 'Stop',
subscribe: 'Abonneren',
unsubscribe: 'Afmelden',
uploadNewAvatar: 'Nieuwe avatar uploaden',
uploadNewImage: 'Nieuwe afbeelding uploaden',
},
},
};
+11
View File
@@ -0,0 +1,11 @@
import merge from 'lodash/merge';
import login from './login';
import core from './core';
export default {
language: 'nl',
country: 'nl',
name: 'Dutch',
embeddedLocale: merge(login, core),
};
+20
View File
@@ -0,0 +1,20 @@
export default {
translation: {
common: {
emailOrUsername: 'E-mail of gebruikersnaam',
invalidEmailOrUsername: 'Ongeldige e-mail of gebruikersnaam',
invalidPassword: 'Ongeldig wachtwoord',
logInToPlanka: 'Inloggen op Planka',
noInternetConnection: 'Geen internetverbinding',
pageNotFound_title: 'Pagina Niet Gevonden',
password: 'Wachtwoord',
projectManagement: 'Projectbeheer',
serverConnectionFailed: 'Verbinding met server mislukt',
unknownError: 'Onbekende fout, probeer het later opnieuw',
},
action: {
logIn: 'Inloggen',
},
},
};
+27 -4
View File
@@ -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}} скрыто)',
+219
View File
@@ -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',
},
},
};
+8
View File
@@ -0,0 +1,8 @@
import login from './login';
export default {
language: 'tr',
country: 'tr',
name: 'Türkçe',
embeddedLocale: login,
};
+20
View File
@@ -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',
},
},
};
+3
View File
@@ -50,6 +50,9 @@ export default class extends BaseModel {
isAdmin: attr({
getDefault: () => false,
}),
isLocked: attr({
getDefault: () => false,
}),
isAvatarUpdating: attr({
getDefault: () => false,
}),
+6 -2
View File
@@ -7,9 +7,13 @@ import { setAccessToken } from '../../../utils/access-token-storage';
export function* authenticate(data) {
yield put(actions.authenticate(data));
let accessToken;
let accessToken = data.access_token;
try {
({ item: accessToken } = yield call(api.createAccessToken, data));
if (accessToken) {
({ item: accessToken } = yield call(api.exchangeOidcToken, accessToken));
} else {
({ item: accessToken } = yield call(api.createAccessToken, data));
}
} catch (error) {
yield put(actions.authenticate.failure(error));
return;
+6
View File
@@ -24,6 +24,12 @@ services:
- DATABASE_URL=postgresql://postgres@postgres/planka
- SECRET_KEY=notsecretkey
# Can be removed after installation
- DEFAULT_ADMIN_EMAIL=demo@demo.demo # Do not remove if you want to prevent this user from being edited/deleted
- DEFAULT_ADMIN_PASSWORD=demo
- DEFAULT_ADMIN_NAME=Demo Demo
- DEFAULT_ADMIN_USERNAME=demo
# related: https://github.com/knex/knex/issues/2354
# As knex does not pass query parameters from the connection string we
# have to use environment variables in order to pass the desired values, e.g.
+7
View File
@@ -4,6 +4,13 @@ BASE_URL=http://localhost:1337
DATABASE_URL=postgresql://postgres@localhost/planka
SECRET_KEY=notsecretkey
## Can be removed after installation
DEFAULT_ADMIN_EMAIL=demo@demo.demo # Do not remove if you want to prevent this user from being edited/deleted
DEFAULT_ADMIN_PASSWORD=demo
DEFAULT_ADMIN_NAME=Demo Demo
DEFAULT_ADMIN_USERNAME=demo
## Optional
# TRUST_PROXY=0
@@ -0,0 +1,177 @@
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const openidClient = require('openid-client');
const { getRemoteAddress } = require('../../../utils/remoteAddress');
const Errors = {
INVALID_TOKEN: {
invalidToken: 'Access Token is invalid',
},
MISSING_VALUES: {
missingValues:
'Unable to retrieve required values. Verify the access token or UserInfo endpoint has email, username and name claims',
},
};
const jwks = jwksClient({
jwksUri: sails.config.custom.oidcJwksUri,
requestHeaders: {}, // Optional
timeout: 30000, // Defaults to 30s
});
const getJwtVerificationOptions = () => {
const options = {};
if (sails.config.custom.oidcIssuer) {
options.issuer = sails.config.custom.oidcIssuer;
}
if (sails.config.custom.oidcAudience) {
options.audience = sails.config.custom.oidcAudience;
}
return options;
};
const validateAndDecodeToken = async (accessToken, options) => {
const keys = await jwks.getSigningKeys();
let validToken = {};
const isTokenValid = keys.some((signingKey) => {
try {
const key = signingKey.getPublicKey();
validToken = jwt.verify(accessToken, key, options);
return 'true';
} catch (error) {
sails.log.error(error);
}
return false;
});
if (!isTokenValid) {
const tokenForLogging = jwt.decode(accessToken);
const remoteAddress = getRemoteAddress(this.req);
sails.log.warn(
`invalid token: sub: "${tokenForLogging.sub}" issuer: "${tokenForLogging.iss}" audience: "${tokenForLogging.aud}" exp: ${tokenForLogging.exp} (IP: ${remoteAddress})`,
);
throw Errors.INVALID_TOKEN;
}
return validToken;
};
const getUserInfo = async (accessToken, options) => {
if (sails.config.custom.oidcSkipUserInfo) {
return {};
}
const issuer = await openidClient.Issuer.discover(options.issuer);
const oidcClient = new issuer.Client({
client_id: 'irrelevant',
});
const userInfo = await oidcClient.userinfo(accessToken);
return userInfo;
};
const mergeUserData = (validToken, userInfo) => {
const oidcUser = { ...validToken, ...userInfo };
return oidcUser;
};
const getOrCreateUser = async (newUser) => {
const user = await User.findOne({
where: {
username: newUser.username,
},
});
if (user) {
return user;
}
return User.create(newUser).fetch();
};
module.exports = {
inputs: {
token: {
type: 'string',
required: true,
},
},
exits: {
invalidToken: {
responseType: 'unauthorized',
},
missingValues: {
responseType: 'unauthorized',
},
},
async fn(inputs) {
const options = getJwtVerificationOptions();
const validToken = await validateAndDecodeToken(inputs.token, options);
const userInfo = await getUserInfo(inputs.token, options);
const oidcUser = mergeUserData(validToken, userInfo);
const now = new Date();
let isAdmin = false;
if (sails.config.custom.oidcAdminRoles.includes('*')) isAdmin = true;
else if (Array.isArray(oidcUser[sails.config.custom.oidcRolesAttribute])) {
const userRoles = new Set(oidcUser[sails.config.custom.oidcRolesAttribute]);
isAdmin = sails.config.custom.oidcAdminRoles.findIndex((role) => userRoles.has(role)) > -1;
}
const newUser = {
email: oidcUser.email,
isAdmin,
name: oidcUser.name,
username: oidcUser.preferred_username,
subscribeToOwnCards: false,
createdAt: now,
updatedAt: now,
locked: true,
};
if (!newUser.email || !newUser.username || !newUser.name) {
sails.log.error(Errors.MISSING_VALUES.missingValues);
throw Errors.MISSING_VALUES;
}
const identityProviderUser = await IdentityProviderUser.findOne({
where: {
issuer: oidcUser.iss,
sub: oidcUser.sub,
},
}).populate('userId');
let user = identityProviderUser ? identityProviderUser.userId : {};
if (!identityProviderUser) {
user = await getOrCreateUser(newUser);
await IdentityProviderUser.create({
issuer: oidcUser.iss,
sub: oidcUser.sub,
userId: user.id,
});
}
const controlledFields = ['email', 'password', 'isAdmin', 'name', 'username'];
const updateFields = {};
controlledFields.forEach((field) => {
if (user[field] !== newUser[field]) {
updateFields[field] = newUser[field];
}
});
if (Object.keys(updateFields).length > 0) {
updateFields.updatedAt = now;
await User.updateOne({ id: user.id }).set(updateFields);
}
const plankaToken = sails.helpers.utils.createToken(user.id);
const remoteAddress = getRemoteAddress(this.req);
await Session.create({
accessToken: plankaToken,
remoteAddress,
userId: user.id,
userAgent: this.req.headers['user-agent'],
});
return {
item: plankaToken,
};
},
};
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
async fn() {
const config = {
authority: sails.config.custom.oidcIssuer,
clientId: sails.config.custom.oidcClientId,
redirectUri: sails.config.custom.oidcredirectUri,
scopes: sails.config.custom.oidcScopes,
};
return config;
},
};
+4
View File
@@ -26,6 +26,10 @@ module.exports = {
throw Errors.USER_NOT_FOUND;
}
if (user.email === sails.config.custom.defaultAdminEmail) {
throw Errors.USER_NOT_FOUND; // Forbidden
}
user = await sails.helpers.users.deleteOne.with({
record: user,
request: this.req,
@@ -59,6 +59,10 @@ module.exports = {
throw Errors.USER_NOT_FOUND;
}
if (user.email === sails.config.custom.defaultAdminEmail) {
throw Errors.USER_NOT_FOUND; // Forbidden
}
if (
inputs.id === currentUser.id &&
!bcrypt.compareSync(inputs.currentPassword, user.password)
@@ -58,6 +58,10 @@ module.exports = {
throw Errors.USER_NOT_FOUND;
}
if (user.email === sails.config.custom.defaultAdminEmail) {
throw Errors.USER_NOT_FOUND; // Forbidden
}
if (
inputs.id === currentUser.id &&
!bcrypt.compareSync(inputs.currentPassword, user.password)
@@ -61,6 +61,10 @@ module.exports = {
throw Errors.USER_NOT_FOUND;
}
if (user.email === sails.config.custom.defaultAdminEmail) {
throw Errors.USER_NOT_FOUND; // Forbidden
}
if (
inputs.id === currentUser.id &&
!bcrypt.compareSync(inputs.currentPassword, user.password)
+7
View File
@@ -67,6 +67,13 @@ module.exports = {
throw Errors.USER_NOT_FOUND;
}
if (user.email === sails.config.custom.defaultAdminEmail) {
/* eslint-disable no-param-reassign */
delete inputs.isAdmin;
delete inputs.name;
/* eslint-enable no-param-reassign */
}
const values = {
..._.pick(inputs, [
'isAdmin',
+40
View File
@@ -0,0 +1,40 @@
/**
* ProjectManager.js
*
* @description :: A model definition represents a database table/collection.
* @docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/
module.exports = {
attributes: {
issuer: {
type: 'string',
isNotEmptyString: true,
allowNull: true,
},
sub: {
type: 'string',
isNotEmptyString: true,
allowNull: true,
},
// ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
// ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝
// ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
// ╠═╣╚═╗╚═╗║ ║║ ║╠═╣ ║ ║║ ║║║║╚═╗
// ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝
userId: {
model: 'User',
required: true,
columnName: 'user_id',
},
},
tableName: 'identity_provider_user',
};
+9 -1
View File
@@ -18,7 +18,6 @@ module.exports = {
},
password: {
type: 'string',
required: true,
},
isAdmin: {
type: 'boolean',
@@ -68,6 +67,10 @@ module.exports = {
type: 'ref',
columnName: 'password_changed_at',
},
locked: {
type: 'boolean',
columnName: 'locked',
},
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
@@ -97,6 +100,10 @@ module.exports = {
via: 'userId',
through: 'CardMembership',
},
identityProviders: {
collection: 'IdentityProviderUser',
via: 'userId',
},
},
tableName: 'user_account',
@@ -107,6 +114,7 @@ module.exports = {
avatarUrl:
this.avatar &&
`${sails.config.custom.userAvatarsUrl}/${this.avatar.dirname}/square-100.${this.avatar.extension}`,
isLocked: this.email === sails.config.custom.defaultAdminEmail,
};
},
};
+12
View File
@@ -30,4 +30,16 @@ module.exports.custom = {
attachmentsPath: path.join(sails.config.appPath, 'private', 'attachments'),
attachmentsUrl: `${process.env.BASE_URL}/attachments`,
oidcIssuer: process.env.OIDC_ISSUER,
oidcAudience: process.env.OIDC_AUDIENCE,
oidcClientId: process.env.OIDC_CLIENT_ID,
oidcRolesAttribute: process.env.OIDC_ROLES_ATTRIBUTE || 'groups',
oidcAdminRoles: process.env.OIDC_ADMIN_ROLES ? process.env.OIDC_ADMIN_ROLES.split(',') : [],
oidcredirectUri: process.env.OIDC_REDIRECT_URI,
oidcJwksUri: process.env.OIDC_JWKS_URI,
oidcScopes: process.env.OIDC_SCOPES || 'openid profile email',
oidcSkipUserInfo: process.env.OIDC_SKIP_USER_INFO === 'true',
defaultAdminEmail: process.env.DEFAULT_ADMIN_EMAIL,
};
+2
View File
@@ -24,4 +24,6 @@ module.exports.policies = {
'projects/create': ['is-authenticated', 'is-admin'],
'access-tokens/create': true,
'access-tokens/exchange': true,
'appconfig/index': true,
};
+3
View File
@@ -9,7 +9,10 @@
*/
module.exports.routes = {
'GET /api/appconfig': 'appconfig/index',
'POST /api/access-tokens': 'access-tokens/create',
'POST /api/access-tokens/exchange': 'access-tokens/exchange',
'DELETE /api/access-tokens/me': 'access-tokens/delete',
'GET /api/users': 'users/index',
+1 -5
View File
@@ -6,12 +6,8 @@ const knex = initKnex(knexfile);
(async () => {
try {
const isExists = await knex.schema.hasTable(knexfile.migrations.tableName);
await knex.migrate.latest();
if (!isExists) {
await knex.seed.run();
}
await knex.seed.run();
} catch (error) {
process.exitCode = 1;
@@ -0,0 +1,24 @@
module.exports.up = (knex) =>
knex.schema.createTable('identity_provider_user', (table) => {
/* Columns */
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
table.timestamp('created_at', true);
table.timestamp('updated_at', true);
table
.bigInteger('user_id')
.notNullable()
.references('id')
.inTable('user_account')
.onDelete('CASCADE');
table.text('issuer').notNullable();
table.text('sub').notNullable();
/* Indexes */
table.index('user_id');
});
module.exports.down = (knex) => knex.schema.dropTable('identity_provider_user');
@@ -0,0 +1,11 @@
module.exports.up = async (knex) => {
return knex.schema.table('user_account', (table) => {
table.setNullable('password');
});
};
module.exports.down = async (knex) => {
return knex.schema.table('user_account', (table) => {
table.dropNullable('password');
});
};
@@ -0,0 +1,11 @@
module.exports.up = async (knex) => {
return knex.schema.table('user_account', (table) => {
table.boolean('locked').default(false);
});
};
module.exports.down = async (knex) => {
return knex.schema.table('user_account', (table) => {
table.dropColumn('locked');
});
};
+39 -9
View File
@@ -1,12 +1,42 @@
const bcrypt = require('bcrypt');
exports.seed = (knex) =>
knex('user_account').insert({
email: 'demo@demo.demo',
password: bcrypt.hashSync('demo', 10),
const buildData = () => {
const data = {
isAdmin: true,
name: 'Demo Demo',
username: 'demo',
subscribeToOwnCards: false,
createdAt: new Date().toISOString(),
});
};
if (process.env.DEFAULT_ADMIN_PASSWORD) {
data.password = bcrypt.hashSync(process.env.DEFAULT_ADMIN_PASSWORD, 10);
}
if (process.env.DEFAULT_ADMIN_NAME) {
data.name = process.env.DEFAULT_ADMIN_NAME;
}
if (process.env.DEFAULT_ADMIN_USERNAME) {
data.username = process.env.DEFAULT_ADMIN_USERNAME;
}
return data;
};
exports.seed = async (knex) => {
if (!process.env.DEFAULT_ADMIN_EMAIL) {
return;
}
const data = buildData();
try {
await knex('user_account').insert({
...data,
email: process.env.DEFAULT_ADMIN_EMAIL,
subscribeToOwnCards: false,
createdAt: new Date().toISOString(),
});
} catch (error) {
if (Object.keys(data).length === 0) {
return;
}
await knex('user_account').update(data).where('email', process.env.DEFAULT_ADMIN_EMAIL);
}
};
+345
View File
@@ -11,10 +11,12 @@
"dotenv-cli": "^6.0.0",
"filenamify": "^4.3.0",
"jsonwebtoken": "^9.0.0",
"jwks-rsa": "^3.0.1",
"knex": "^2.3.0",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"move-file": "^2.1.0",
"openid-client": "^5.4.3",
"rimraf": "^3.0.2",
"sails": "^1.5.3",
"sails-hook-orm": "^4.0.2",
@@ -198,12 +200,103 @@
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
"integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="
},
"node_modules/@types/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
"integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
}
},
"node_modules/@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/express": {
"version": "4.17.17",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
"integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==",
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.17.35",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz",
"integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==",
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*",
"@types/send": "*"
}
},
"node_modules/@types/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"node_modules/@types/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"node_modules/@types/node": {
"version": "20.4.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz",
"integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg=="
},
"node_modules/@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw=="
},
"node_modules/@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw=="
},
"node_modules/@types/send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
"integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==",
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
}
},
"node_modules/@types/serve-static": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz",
"integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==",
"dependencies": {
"@types/http-errors": "*",
"@types/mime": "*",
"@types/node": "*"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -3634,6 +3727,14 @@
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
},
"node_modules/jose": {
"version": "4.14.4",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz",
"integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/js-sdsl": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
@@ -3709,6 +3810,22 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/jwks-rsa": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz",
"integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==",
"dependencies": {
"@types/express": "^4.17.14",
"@types/jsonwebtoken": "^9.0.0",
"debug": "^4.3.4",
"jose": "^4.10.4",
"limiter": "^1.1.5",
"lru-memoizer": "^2.1.4"
},
"engines": {
"node": ">=14"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
@@ -3839,6 +3956,11 @@
"node": ">= 0.10"
}
},
"node_modules/limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
"integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
},
"node_modules/localforage": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.3.0.tgz",
@@ -3867,6 +3989,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
"node_modules/lodash.issafeinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.issafeinteger/-/lodash.issafeinteger-4.0.4.tgz",
@@ -3926,6 +4053,29 @@
"node": ">=10"
}
},
"node_modules/lru-memoizer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz",
"integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==",
"dependencies": {
"lodash.clonedeep": "^4.5.0",
"lru-cache": "~4.0.0"
}
},
"node_modules/lru-memoizer/node_modules/lru-cache": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
"integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==",
"dependencies": {
"pseudomap": "^1.0.1",
"yallist": "^2.0.0"
}
},
"node_modules/lru-memoizer/node_modules/yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
},
"node_modules/machine": {
"version": "15.2.2",
"resolved": "https://registry.npmjs.org/machine/-/machine-15.2.2.tgz",
@@ -4546,6 +4696,14 @@
"node": ">=0.10.0"
}
},
"node_modules/object-hash": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
"engines": {
"node": ">= 6"
}
},
"node_modules/object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
@@ -4625,6 +4783,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/oidc-token-hash": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
"integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==",
"engines": {
"node": "^10.13.0 || >=12.0.0"
}
},
"node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -4660,6 +4826,20 @@
"fn.name": "1.x.x"
}
},
"node_modules/openid-client": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.3.tgz",
"integrity": "sha512-sVQOvjsT/sbSfYsQI/9liWQGVZH/Pp3rrtlGEwgk/bbHfrUDZ24DN57lAagIwFtuEu+FM9Ev7r85s8S/yPjimQ==",
"dependencies": {
"jose": "^4.14.4",
"lru-cache": "^6.0.0",
"object-hash": "^2.2.0",
"oidc-token-hash": "^5.0.3"
},
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/opn": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz",
@@ -8039,12 +8219,103 @@
}
}
},
"@types/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
"integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
"requires": {
"@types/connect": "*",
"@types/node": "*"
}
},
"@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
"requires": {
"@types/node": "*"
}
},
"@types/express": {
"version": "4.17.17",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
"integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==",
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.17.35",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz",
"integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==",
"requires": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*",
"@types/send": "*"
}
},
"@types/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ=="
},
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"@types/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==",
"requires": {
"@types/node": "*"
}
},
"@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"@types/node": {
"version": "20.4.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz",
"integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg=="
},
"@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw=="
},
"@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw=="
},
"@types/send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
"integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==",
"requires": {
"@types/mime": "^1",
"@types/node": "*"
}
},
"@types/serve-static": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz",
"integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==",
"requires": {
"@types/http-errors": "*",
"@types/mime": "*",
"@types/node": "*"
}
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -10706,6 +10977,11 @@
}
}
},
"jose": {
"version": "4.14.4",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz",
"integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g=="
},
"js-sdsl": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
@@ -10771,6 +11047,19 @@
"safe-buffer": "^5.0.1"
}
},
"jwks-rsa": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz",
"integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==",
"requires": {
"@types/express": "^4.17.14",
"@types/jsonwebtoken": "^9.0.0",
"debug": "^4.3.4",
"jose": "^4.10.4",
"limiter": "^1.1.5",
"lru-memoizer": "^2.1.4"
}
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
@@ -10861,6 +11150,11 @@
}
}
},
"limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
"integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
},
"localforage": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.3.0.tgz",
@@ -10883,6 +11177,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
"lodash.issafeinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.issafeinteger/-/lodash.issafeinteger-4.0.4.tgz",
@@ -10933,6 +11232,31 @@
"yallist": "^4.0.0"
}
},
"lru-memoizer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz",
"integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==",
"requires": {
"lodash.clonedeep": "^4.5.0",
"lru-cache": "~4.0.0"
},
"dependencies": {
"lru-cache": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
"integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==",
"requires": {
"pseudomap": "^1.0.1",
"yallist": "^2.0.0"
}
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
}
}
},
"machine": {
"version": "15.2.2",
"resolved": "https://registry.npmjs.org/machine/-/machine-15.2.2.tgz",
@@ -11406,6 +11730,11 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
"object-hash": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="
},
"object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
@@ -11461,6 +11790,11 @@
"es-abstract": "^1.20.4"
}
},
"oidc-token-hash": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
"integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw=="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -11490,6 +11824,17 @@
"fn.name": "1.x.x"
}
},
"openid-client": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.3.tgz",
"integrity": "sha512-sVQOvjsT/sbSfYsQI/9liWQGVZH/Pp3rrtlGEwgk/bbHfrUDZ24DN57lAagIwFtuEu+FM9Ev7r85s8S/yPjimQ==",
"requires": {
"jose": "^4.14.4",
"lru-cache": "^6.0.0",
"object-hash": "^2.2.0",
"oidc-token-hash": "^5.0.3"
}
},
"opn": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz",
+2
View File
@@ -32,10 +32,12 @@
"dotenv-cli": "^6.0.0",
"filenamify": "^4.3.0",
"jsonwebtoken": "^9.0.0",
"jwks-rsa": "^3.0.1",
"knex": "^2.3.0",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"move-file": "^2.1.0",
"openid-client": "^5.4.3",
"rimraf": "^3.0.2",
"sails": "^1.5.3",
"sails-hook-orm": "^4.0.2",