diff --git a/charts/planka/values.yaml b/charts/planka/values.yaml
index 8b084ff8..27bd67c1 100644
--- a/charts/planka/values.yaml
+++ b/charts/planka/values.yaml
@@ -224,11 +224,11 @@ extraEnv: []
## value: "Your Name"
## - name: SMTP_SECURE
## value: "true"
+## - name: SMTP_TLS_REJECT_UNAUTHORIZED
+## value: "false"
## - name: SMTP_USER
## value: "your_email@example.com"
## - name: SMTP_PASSWORD
## value: "your_password"
## - name: SMTP_FROM
## value: "your_email@example.com"
-## - name: SMTP_TLS_REJECT_UNAUTHORIZED
-## value: "false"
diff --git a/client/src/actions/config.js b/client/src/actions/config.js
new file mode 100644
index 00000000..97a694cc
--- /dev/null
+++ b/client/src/actions/config.js
@@ -0,0 +1,59 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+import ActionTypes from '../constants/ActionTypes';
+
+const updateConfig = (data) => ({
+ type: ActionTypes.CONFIG_UPDATE,
+ payload: {
+ data,
+ },
+});
+
+updateConfig.success = (config) => ({
+ type: ActionTypes.CONFIG_UPDATE__SUCCESS,
+ payload: {
+ config,
+ },
+});
+
+updateConfig.failure = (error) => ({
+ type: ActionTypes.CONFIG_UPDATE__FAILURE,
+ payload: {
+ error,
+ },
+});
+
+const handleConfigUpdate = (config) => ({
+ type: ActionTypes.CONFIG_UPDATE_HANDLE,
+ payload: {
+ config,
+ },
+});
+
+const testSmtpConfig = () => ({
+ type: ActionTypes.SMTP_CONFIG_TEST,
+ payload: {},
+});
+
+testSmtpConfig.success = (logs) => ({
+ type: ActionTypes.SMTP_CONFIG_TEST__SUCCESS,
+ payload: {
+ logs,
+ },
+});
+
+testSmtpConfig.failure = (error) => ({
+ type: ActionTypes.SMTP_CONFIG_TEST__FAILURE,
+ payload: {
+ error,
+ },
+});
+
+export default {
+ updateConfig,
+ handleConfigUpdate,
+ testSmtpConfig,
+};
diff --git a/client/src/actions/core.js b/client/src/actions/core.js
index a2f05f4a..85182ffd 100644
--- a/client/src/actions/core.js
+++ b/client/src/actions/core.js
@@ -6,6 +6,7 @@
import ActionTypes from '../constants/ActionTypes';
const initializeCore = (
+ config,
user,
board,
webhooks,
@@ -32,6 +33,7 @@ const initializeCore = (
) => ({
type: ActionTypes.CORE_INITIALIZE,
payload: {
+ config,
user,
board,
webhooks,
@@ -58,10 +60,10 @@ const initializeCore = (
},
});
-initializeCore.fetchConfig = (config) => ({
- type: ActionTypes.CORE_INITIALIZE__CONFIG_FETCH,
+initializeCore.fetchBootstrap = (bootstrap) => ({
+ type: ActionTypes.CORE_INITIALIZE__BOOTSTRAP_FETCH,
payload: {
- config,
+ bootstrap,
},
});
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index 46a254a1..fcf3a702 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -8,6 +8,7 @@ import socket from './socket';
import login from './login';
import core from './core';
import modals from './modals';
+import config from './config';
import webhooks from './webhooks';
import users from './users';
import projects from './projects';
@@ -36,6 +37,7 @@ export default {
...login,
...core,
...modals,
+ ...config,
...webhooks,
...users,
...projects,
diff --git a/client/src/actions/login.js b/client/src/actions/login.js
index 67e31e11..da0ccaa1 100644
--- a/client/src/actions/login.js
+++ b/client/src/actions/login.js
@@ -5,10 +5,10 @@
import ActionTypes from '../constants/ActionTypes';
-const initializeLogin = (config) => ({
+const initializeLogin = (bootstrap) => ({
type: ActionTypes.LOGIN_INITIALIZE,
payload: {
- config,
+ bootstrap,
},
});
diff --git a/client/src/actions/socket.js b/client/src/actions/socket.js
index 8e6f07f4..16dfd77f 100644
--- a/client/src/actions/socket.js
+++ b/client/src/actions/socket.js
@@ -11,6 +11,7 @@ const handleSocketDisconnect = () => ({
});
const handleSocketReconnect = (
+ bootstrap,
config,
user,
board,
@@ -38,6 +39,7 @@ const handleSocketReconnect = (
) => ({
type: ActionTypes.SOCKET_RECONNECT_HANDLE,
payload: {
+ bootstrap,
config,
user,
board,
diff --git a/client/src/actions/users.js b/client/src/actions/users.js
index 101d65c6..1b2ece4a 100644
--- a/client/src/actions/users.js
+++ b/client/src/actions/users.js
@@ -65,6 +65,7 @@ const handleUserUpdate = (
user,
projectIds,
boardIds,
+ bootstrap,
config,
board,
webhooks,
@@ -94,6 +95,7 @@ const handleUserUpdate = (
user,
projectIds,
boardIds,
+ bootstrap,
config,
board,
webhooks,
diff --git a/client/src/api/bootstrap.js b/client/src/api/bootstrap.js
new file mode 100644
index 00000000..4253466f
--- /dev/null
+++ b/client/src/api/bootstrap.js
@@ -0,0 +1,14 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+import http from './http';
+
+/* Actions */
+
+const getBootstrap = (headers) => http.get('/bootstrap', undefined, headers);
+
+export default {
+ getBootstrap,
+};
diff --git a/client/src/api/config.js b/client/src/api/config.js
index 65577881..ea127a17 100644
--- a/client/src/api/config.js
+++ b/client/src/api/config.js
@@ -3,12 +3,18 @@
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
-import http from './http';
+import socket from './socket';
/* Actions */
-const getConfig = (headers) => http.get('/config', undefined, headers);
+const getConfig = (headers) => socket.get('/config', undefined, headers);
+
+const updateConfig = (data, headers) => socket.patch('/config', data, headers);
+
+const testSmtpConfig = (headers) => socket.post('/config/test-smtp', undefined, headers);
export default {
getConfig,
+ updateConfig,
+ testSmtpConfig,
};
diff --git a/client/src/api/index.js b/client/src/api/index.js
index 98d045f1..eea95c26 100755
--- a/client/src/api/index.js
+++ b/client/src/api/index.js
@@ -5,9 +5,10 @@
import http from './http';
import socket from './socket';
-import config from './config';
+import bootstrap from './bootstrap';
import terms from './terms';
import accessTokens from './access-tokens';
+import config from './config';
import webhooks from './webhooks';
import users from './users';
import projects from './projects';
@@ -35,9 +36,10 @@ import notificationServices from './notification-services';
export { http, socket };
export default {
- ...config,
+ ...bootstrap,
...terms,
...accessTokens,
+ ...config,
...webhooks,
...users,
...projects,
diff --git a/client/src/components/common/AdministrationModal/AdministrationModal.jsx b/client/src/components/common/AdministrationModal/AdministrationModal.jsx
index d79d22ec..01fe2aa9 100644
--- a/client/src/components/common/AdministrationModal/AdministrationModal.jsx
+++ b/client/src/components/common/AdministrationModal/AdministrationModal.jsx
@@ -5,18 +5,22 @@
import React, { useCallback, useState } from 'react';
import classNames from 'classnames';
-import { useDispatch } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Modal, Tab } from 'semantic-ui-react';
+import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import { useClosableModal } from '../../../hooks';
import UsersPane from './UsersPane';
+import SmtpPane from './SmtpPane';
import WebhooksPane from './WebhooksPane';
import styles from './AdministrationModal.module.scss';
const AdministrationModal = React.memo(() => {
+ const config = useSelector(selectors.selectConfig);
+
const dispatch = useDispatch();
const [t] = useTranslation();
const [activeTabIndex, setActiveTabIndex] = useState(0);
@@ -38,13 +42,21 @@ const AdministrationModal = React.memo(() => {
}),
render: () => ,
},
- {
- menuItem: t('common.webhooks', {
+ ];
+ if (config.smtpHost !== undefined) {
+ panes.push({
+ menuItem: t('common.smtp', {
context: 'title',
}),
- render: () => ,
- },
- ];
+ render: () => ,
+ });
+ }
+ panes.push({
+ menuItem: t('common.webhooks', {
+ context: 'title',
+ }),
+ render: () => ,
+ });
const isUsersPaneActive = activeTabIndex === 0;
diff --git a/client/src/components/common/AdministrationModal/SmtpPane.jsx b/client/src/components/common/AdministrationModal/SmtpPane.jsx
new file mode 100644
index 00000000..80d8955a
--- /dev/null
+++ b/client/src/components/common/AdministrationModal/SmtpPane.jsx
@@ -0,0 +1,227 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+import { dequal } from 'dequal';
+import React, { useCallback, useMemo } from 'react';
+import classNames from 'classnames';
+import { useDispatch, useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
+import TextareaAutosize from 'react-textarea-autosize';
+import { Button, Checkbox, Divider, Form, Header, Tab, TextArea } from 'semantic-ui-react';
+import { Input } from '../../../lib/custom-ui';
+
+import selectors from '../../../selectors';
+import entryActions from '../../../entry-actions';
+import { useForm, useNestedRef } from '../../../hooks';
+
+import styles from './SmtpPane.module.scss';
+
+const SmtpPane = React.memo(() => {
+ const config = useSelector(selectors.selectConfig);
+ const smtpTest = useSelector(selectors.selectSmtpTest);
+
+ const dispatch = useDispatch();
+ const [t] = useTranslation();
+
+ const [passwordFieldRef, handlePasswordFieldRef] = useNestedRef('inputRef');
+
+ const defaultData = useMemo(
+ () => ({
+ smtpHost: config.smtpHost,
+ smtpPort: config.smtpPort,
+ smtpName: config.smtpName,
+ smtpSecure: config.smtpSecure,
+ smtpTlsRejectUnauthorized: config.smtpTlsRejectUnauthorized,
+ smtpUser: config.smtpUser,
+ smtpPassword: config.smtpPassword,
+ smtpFrom: config.smtpFrom,
+ }),
+ [config],
+ );
+
+ const [data, handleFieldChange] = useForm(() => ({
+ ...defaultData,
+ smtpHost: defaultData.smtpHost || '',
+ smtpPort: defaultData.smtpPort || '',
+ smtpName: defaultData.smtpName || '',
+ smtpSecure: defaultData.smtpSecure,
+ smtpTlsRejectUnauthorized: defaultData.smtpTlsRejectUnauthorized,
+ smtpUser: defaultData.smtpUser || '',
+ smtpPassword: defaultData.smtpPassword || '',
+ smtpFrom: defaultData.smtpFrom || '',
+ }));
+
+ const isPasswordSet = defaultData.smtpPassword === undefined;
+
+ const cleanData = useMemo(
+ () => ({
+ ...data,
+ smtpHost: data.smtpHost.trim() || null,
+ smtpPort: parseInt(data.smtpPort, 10) || null,
+ smtpName: data.smtpName.trim() || null,
+ smtpUser: data.smtpUser.trim() || null,
+ smtpPassword: data.smtpPassword || (isPasswordSet ? undefined : null),
+ smtpFrom: data.smtpFrom.trim() || null,
+ }),
+ [data, isPasswordSet],
+ );
+
+ const handleSubmit = useCallback(() => {
+ dispatch(entryActions.updateConfig(cleanData));
+ }, [dispatch, cleanData]);
+
+ const handlePasswordClear = useCallback(() => {
+ dispatch(
+ entryActions.updateConfig({
+ smtpPassword: null,
+ }),
+ );
+
+ passwordFieldRef.current.focus();
+ }, [dispatch, passwordFieldRef]);
+
+ const handleTestClick = useCallback(() => {
+ dispatch(entryActions.testSmtpConfig());
+ }, [dispatch]);
+
+ const isModified = !dequal(cleanData, defaultData);
+
+ return (
+
+
+ {smtpTest.logs && (
+ <>
+
+
+ {t('common.testLog', {
+ context: 'title',
+ })}
+
+
+
+ >
+ )}
+
+ );
+});
+
+export default SmtpPane;
diff --git a/client/src/components/common/AdministrationModal/SmtpPane.module.scss b/client/src/components/common/AdministrationModal/SmtpPane.module.scss
new file mode 100644
index 00000000..939990cd
--- /dev/null
+++ b/client/src/components/common/AdministrationModal/SmtpPane.module.scss
@@ -0,0 +1,41 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+:global(#app) {
+ .checkbox {
+ display: block;
+ padding: 8px 0;
+ }
+
+ .controls {
+ display: flex;
+ justify-content: space-between;
+ }
+
+ .field {
+ margin-bottom: 8px;
+ }
+
+ .testLog {
+ border: 1px solid rgba(9, 30, 66, 0.13);
+ border-radius: 3px;
+ color: #333;
+ line-height: 1.4;
+ padding: 8px 12px;
+ width: 100%;
+ }
+
+ .text {
+ color: #444444;
+ font-size: 12px;
+ font-weight: bold;
+ padding-bottom: 6px;
+ }
+
+ .wrapper {
+ border: none;
+ box-shadow: none;
+ }
+}
diff --git a/client/src/components/common/AdministrationModal/UsersPane/UsersPane.jsx b/client/src/components/common/AdministrationModal/UsersPane/UsersPane.jsx
index 8cb0980b..132919eb 100755
--- a/client/src/components/common/AdministrationModal/UsersPane/UsersPane.jsx
+++ b/client/src/components/common/AdministrationModal/UsersPane/UsersPane.jsx
@@ -22,8 +22,8 @@ const UsersPane = React.memo(() => {
const activeUsersTotal = useSelector(selectors.selectActiveUsersTotal);
const canAdd = useSelector((state) => {
- const oidcConfig = selectors.selectOidcConfig(state);
- return !oidcConfig || !oidcConfig.isEnforced;
+ const oidcBootstrap = selectors.selectOidcBootstrap(state);
+ return !oidcBootstrap || !oidcBootstrap.isEnforced;
});
const [t] = useTranslation();
diff --git a/client/src/components/common/Core/Core.jsx b/client/src/components/common/Core/Core.jsx
index 7e79372f..306fb15c 100644
--- a/client/src/components/common/Core/Core.jsx
+++ b/client/src/components/common/Core/Core.jsx
@@ -30,8 +30,8 @@ const Core = React.memo(() => {
// TODO: move to selector?
const isNewVersionAvailable = useSelector((state) => {
- const config = selectors.selectConfig(state);
- return !!config && config.version !== version;
+ const bootstrap = selectors.selectBootstrap(state);
+ return !!bootstrap && bootstrap.version !== version;
});
const [t] = useTranslation();
diff --git a/client/src/components/common/Login/Content.jsx b/client/src/components/common/Login/Content.jsx
index 650f9093..e13f9505 100644
--- a/client/src/components/common/Login/Content.jsx
+++ b/client/src/components/common/Login/Content.jsx
@@ -86,7 +86,7 @@ const createMessage = (error) => {
};
const Content = React.memo(() => {
- const config = useSelector(selectors.selectConfig);
+ const bootstrap = useSelector(selectors.selectBootstrap);
const {
data: defaultData,
@@ -139,8 +139,8 @@ const Content = React.memo(() => {
dispatch(entryActions.clearAuthenticateError());
}, [dispatch]);
- const withOidc = !!config.oidc;
- const isOidcEnforced = withOidc && config.oidc.isEnforced;
+ const withOidc = !!bootstrap.oidc;
+ const isOidcEnforced = withOidc && bootstrap.oidc.isEnforced;
useEffect(() => {
if (!isOidcEnforced) {
diff --git a/client/src/constants/ActionTypes.js b/client/src/constants/ActionTypes.js
index b99702f4..50c45da3 100644
--- a/client/src/constants/ActionTypes.js
+++ b/client/src/constants/ActionTypes.js
@@ -39,7 +39,7 @@ export default {
/* Core */
CORE_INITIALIZE: 'CORE_INITIALIZE',
- CORE_INITIALIZE__CONFIG_FETCH: 'CORE_INITIALIZE__CONFIG_FETCH',
+ CORE_INITIALIZE__BOOTSTRAP_FETCH: 'CORE_INITIALIZE__BOOTSTRAP_FETCH',
FAVORITES_TOGGLE: 'FAVORITES_TOGGLE',
EDIT_MODE_TOGGLE: 'EDIT_MODE_TOGGLE',
HOME_VIEW_UPDATE: 'HOME_VIEW_UPDATE',
@@ -51,6 +51,16 @@ export default {
MODAL_OPEN: 'MODAL_OPEN',
MODAL_CLOSE: 'MODAL_CLOSE',
+ /* Config */
+
+ CONFIG_UPDATE: 'CONFIG_UPDATE',
+ CONFIG_UPDATE__SUCCESS: 'CONFIG_UPDATE__SUCCESS',
+ CONFIG_UPDATE__FAILURE: 'CONFIG_UPDATE__FAILURE',
+ CONFIG_UPDATE_HANDLE: 'CONFIG_UPDATE_HANDLE',
+ SMTP_CONFIG_TEST: 'SMTP_CONFIG_TEST',
+ SMTP_CONFIG_TEST__SUCCESS: 'SMTP_CONFIG_TEST__SUCCESS',
+ SMTP_CONFIG_TEST__FAILURE: 'SMTP_CONFIG_TEST__FAILURE',
+
/* Webhooks */
WEBHOOK_CREATE: 'WEBHOOK_CREATE',
diff --git a/client/src/constants/EntryActionTypes.js b/client/src/constants/EntryActionTypes.js
index 7097bdfe..cce04507 100755
--- a/client/src/constants/EntryActionTypes.js
+++ b/client/src/constants/EntryActionTypes.js
@@ -34,6 +34,12 @@ export default {
MODAL_OPEN: `${PREFIX}/MODAL_OPEN`,
MODAL_CLOSE: `${PREFIX}/MODAL_CLOSE`,
+ /* Config */
+
+ CONFIG_UPDATE: `${PREFIX}/CONFIG_UPDATE`,
+ CONFIG_UPDATE_HANDLE: `${PREFIX}/CONFIG_UPDATE_HANDLE`,
+ SMTP_CONFIG_TEST: `${PREFIX}/SMTP_CONFIG_TEST`,
+
/* Webhooks */
WEBHOOK_CREATE: `${PREFIX}/WEBHOOK_CREATE`,
diff --git a/client/src/constants/WebhookEvents.js b/client/src/constants/WebhookEvents.js
index 886ccfb2..0b0b21a7 100644
--- a/client/src/constants/WebhookEvents.js
+++ b/client/src/constants/WebhookEvents.js
@@ -39,6 +39,8 @@ export default [
'commentUpdate',
'commentDelete',
+ 'configUpdate',
+
'customFieldCreate',
'customFieldUpdate',
'customFieldDelete',
diff --git a/client/src/entry-actions/config.js b/client/src/entry-actions/config.js
new file mode 100644
index 00000000..65e8e6d0
--- /dev/null
+++ b/client/src/entry-actions/config.js
@@ -0,0 +1,31 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+import EntryActionTypes from '../constants/EntryActionTypes';
+
+const updateConfig = (data) => ({
+ type: EntryActionTypes.CONFIG_UPDATE,
+ payload: {
+ data,
+ },
+});
+
+const handleConfigUpdate = (config) => ({
+ type: EntryActionTypes.CONFIG_UPDATE_HANDLE,
+ payload: {
+ config,
+ },
+});
+
+const testSmtpConfig = () => ({
+ type: EntryActionTypes.SMTP_CONFIG_TEST,
+ payload: {},
+});
+
+export default {
+ updateConfig,
+ handleConfigUpdate,
+ testSmtpConfig,
+};
diff --git a/client/src/entry-actions/index.js b/client/src/entry-actions/index.js
index a0356efc..f36fb20e 100755
--- a/client/src/entry-actions/index.js
+++ b/client/src/entry-actions/index.js
@@ -7,6 +7,7 @@ import socket from './socket';
import login from './login';
import core from './core';
import modals from './modals';
+import config from './config';
import webhooks from './webhooks';
import users from './users';
import projects from './projects';
@@ -34,6 +35,7 @@ export default {
...login,
...core,
...modals,
+ ...config,
...webhooks,
...users,
...projects,
diff --git a/client/src/hooks/use-form.js b/client/src/hooks/use-form.js
index 8f6176ef..9115e846 100644
--- a/client/src/hooks/use-form.js
+++ b/client/src/hooks/use-form.js
@@ -5,13 +5,15 @@
import { useCallback, useState } from 'react';
+const CHECKED_TYPES_SET = new Set(['checkbox', 'radio']);
+
export default (initialData) => {
const [data, setData] = useState(initialData);
const handleFieldChange = useCallback((_, { type, name: fieldName, value, checked }) => {
setData((prevData) => ({
...prevData,
- [fieldName]: type === 'radio' ? checked : value,
+ [fieldName]: CHECKED_TYPES_SET.has(type) ? checked : value,
}));
}, []);
diff --git a/client/src/lib/custom-ui/components/Input/InputPassword.jsx b/client/src/lib/custom-ui/components/Input/InputPassword.jsx
index cbe27768..aa52d038 100644
--- a/client/src/lib/custom-ui/components/Input/InputPassword.jsx
+++ b/client/src/lib/custom-ui/components/Input/InputPassword.jsx
@@ -14,7 +14,7 @@ import styles from './InputPassword.module.css';
const STRENGTH_SCORE_COLORS = ['red', 'orange', 'yellow', 'olive', 'green'];
const InputPassword = React.forwardRef(
- ({ value, withStrengthBar, minStrengthScore, className, ...props }, ref) => {
+ ({ value, withStrengthBar, minStrengthScore, className, onClear, ...props }, ref) => {
const [isVisible, toggleVisible] = useToggle();
const strengthScore = useMemo(() => {
@@ -32,8 +32,13 @@ const InputPassword = React.forwardRef(
const inputProps = {
...props,
ref,
+ value,
type: isVisible ? 'text' : 'password',
- icon: ,
+ icon: onClear ? (
+
+ ) : (
+
+ ),
};
if (!withStrengthBar) {
@@ -68,12 +73,14 @@ InputPassword.propTypes = {
withStrengthBar: PropTypes.bool,
minStrengthScore: PropTypes.number,
className: PropTypes.string,
+ onClear: PropTypes.func,
};
InputPassword.defaultProps = {
withStrengthBar: false,
minStrengthScore: 2,
className: undefined,
+ onClear: undefined,
};
export default React.memo(InputPassword);
diff --git a/client/src/locales/ar-YE/core.js b/client/src/locales/ar-YE/core.js
index 3c1c1970..baebbb90 100644
--- a/client/src/locales/ar-YE/core.js
+++ b/client/src/locales/ar-YE/core.js
@@ -98,6 +98,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'اللون',
comments: null,
@@ -120,6 +121,7 @@ export default {
date: 'تاريخ',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'حذف المرفق',
@@ -183,6 +185,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'ساعات',
importBoard_title: 'استيراد اللوحة',
invalidCurrentPassword: 'كلمة المرور الحالية غير صالحة',
@@ -226,8 +229,10 @@ export default {
optional_inline: 'اختياري',
organization: 'المنظمة',
others: null,
+ passwordIsSet: null,
phone: 'الهاتف',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'التفضيلات',
pressPasteShortcutToAddAttachmentFromClipboard:
'نصيحة: اضغط على Ctrl-V (Cmd-V على Mac) لإضافة مرفق من الحافظة.',
@@ -236,6 +241,7 @@ export default {
projectNotFound_title: 'المشروع غير موجود',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'إزالة المدير',
removeMember_title: 'إزالة العضو',
role: null,
@@ -262,6 +268,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: 'فرز القائمة',
stopwatch: 'المؤقت',
story: null,
@@ -273,6 +280,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'لا يوجد معاينة متاحة لهذا المرفق.',
time: 'الوقت',
title: 'العنوان',
@@ -286,6 +294,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'إجراءات المستخدم',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> تمت إضافة هذه البطاقة إلى {{list}}',
@@ -422,6 +431,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'حفظ',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'إظهار جميع المرفقات ({{hidden}} hidden)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/bg-BG/core.js b/client/src/locales/bg-BG/core.js
index f73a6d43..daad8787 100644
--- a/client/src/locales/bg-BG/core.js
+++ b/client/src/locales/bg-BG/core.js
@@ -102,6 +102,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'Цвят',
comments: null,
@@ -124,6 +125,7 @@ export default {
date: 'Дата',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Изтриване на прикачен файл',
@@ -187,6 +189,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'Часове',
importBoard_title: 'Импортиране на табло',
invalidCurrentPassword: 'Невалидна текуща парола',
@@ -230,8 +233,10 @@ export default {
optional_inline: 'по желание',
organization: 'Организация',
others: null,
+ passwordIsSet: null,
phone: 'Телефон',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Предпочитания',
pressPasteShortcutToAddAttachmentFromClipboard:
'Съвет: натиснете Ctrl-V (Cmd-V на Mac), за да добавите прикачен файл от клипборда',
@@ -240,6 +245,7 @@ export default {
projectNotFound_title: 'Проектът не е намерен',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Премахване на мениджър',
removeMember_title: 'Премахване на член',
role: null,
@@ -266,6 +272,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: 'Сортиране на списък',
stopwatch: 'Хронометър',
story: null,
@@ -277,6 +284,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Няма наличен преглед за този прикачен файл.',
time: 'Време',
title: 'Заглавие',
@@ -290,6 +298,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Потребителски действия',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> добави тази карта в {{list}}',
@@ -427,6 +436,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Запазване',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'Показване на всички прикачени файлове ({{hidden}} скрити)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/cs-CZ/core.js b/client/src/locales/cs-CZ/core.js
index 247e4dfb..32596553 100644
--- a/client/src/locales/cs-CZ/core.js
+++ b/client/src/locales/cs-CZ/core.js
@@ -111,6 +111,7 @@ export default {
'Karty na tomto seznamu jsou kompletní a připravené k archivaci.',
cardsOnThisListAreReadyToBeWorkedOn: 'Karty na tomto seznamu jsou připraveny k práci.',
clickHereOrRefreshPageToUpdate: '<0>Klikněte sem0> nebo aktualizujte stránku.',
+ clientHostnameInEhlo: null,
closed: 'Uzavřeno',
color: 'Barva',
comments: 'Komentáře',
@@ -133,6 +134,7 @@ export default {
date: 'Datum',
deactivateUser_title: 'Deaktivace uživatele',
defaultCardType_title: 'Výchozí typ karty',
+ defaultFrom: null,
defaultView_title: 'Výchozí zobrazení',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Pro smazání tohoto projektu je třeba nejprve smazat všechny nástěnky',
@@ -197,6 +199,7 @@ export default {
grid: 'Mřížka',
hideCompletedTasks: 'Skrýt dokončené úkoly',
hideFromProjectListAndFavorites: 'Skrýt ze seznamu projektů a oblíbených položek',
+ host: null,
hours: 'Hodiny',
importBoard_title: 'Importovat nástěnku',
invalidCurrentPassword: 'Neplatné aktuální heslo',
@@ -241,9 +244,11 @@ export default {
optional_inline: 'volitelné',
organization: 'Společnost',
others: 'Jiné',
+ passwordIsSet: null,
phone: 'Telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA používá <1><0>Apprise0>1> k zasílání oznámení do více než 100 oblíbených služeb.',
+ port: null,
preferences: 'Volby',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: stisknutím Ctrl-V (Cmd-V na Macu) přidáte přílohu ze schránky.',
@@ -252,6 +257,7 @@ export default {
projectNotFound_title: 'Projekt nenalezen',
projectOwner: 'Vlastník projektu',
referenceDataAndKnowledgeStorage: 'Uchovávání referenčních údajů a znalostí.',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Odstranit správce',
removeMember_title: 'Odstranit člena',
role: 'Role',
@@ -278,6 +284,7 @@ export default {
shared: 'Sdílené',
sharedWithMe_title: 'Sdíleno se mnou',
showOnFrontOfCard: 'Zobrazit na přední straně karty',
+ smtp: null,
sortList_title: 'Řadit podle',
stopwatch: 'Časovač',
story: 'Příběh',
@@ -289,6 +296,7 @@ export default {
taskList_title: 'Seznam úkolů',
team: 'Tým',
terms: 'Podmínky',
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Pro tuto přílohu není k dispozici žádný náhled.',
time: 'Čas',
title: 'Titulek',
@@ -302,6 +310,7 @@ export default {
uploadFailedNotEnoughStorageSpace: 'Nahrávání se nezdařilo: Nedostatek úložného prostoru.',
uploadedImages: 'Nahrané obrázky',
url: 'URL',
+ useSecureConnection: null,
userActions_title: 'Akce uživatele',
userAddedCardToList: '<0>{{user}}0> přidal <2>{{card}}2> do {{list}}',
userAddedThisCardToList: '<0>{{user}}0> přidal kartu do {{list}}',
@@ -442,6 +451,7 @@ export default {
restoreToList: 'Obnovit do {{list}}',
returnToBoard: 'Návrat na nástěnku',
save: 'Uložit',
+ sendTestEmail: null,
showActive: 'Zobrazit aktivní',
showAllAttachments: 'Zozbrazit všechny přílohy ({{hidden}} skryté)',
showCardsWithThisUser: 'Zobrazit karty tohoto uživatele',
diff --git a/client/src/locales/da-DK/core.js b/client/src/locales/da-DK/core.js
index fce02de7..a6573252 100644
--- a/client/src/locales/da-DK/core.js
+++ b/client/src/locales/da-DK/core.js
@@ -115,6 +115,7 @@ export default {
'Kort på denne liste er afsluttede og klar til at blive arkiveret.',
cardsOnThisListAreReadyToBeWorkedOn: 'Kort på denne liste er klar til at blive arbejdet på.',
clickHereOrRefreshPageToUpdate: '<0>Klik her0> eller opdater siden for at opdatere.',
+ clientHostnameInEhlo: null,
closed: 'Lukket',
color: 'Farve',
comments: 'Kommentarer',
@@ -138,6 +139,7 @@ export default {
date: 'Dato',
deactivateUser_title: 'Deaktiver bruger',
defaultCardType_title: 'Standard korttype',
+ defaultFrom: null,
defaultView_title: 'Standard visning',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Slet alle tavler for at kunne slette dette projekt.',
@@ -202,6 +204,7 @@ export default {
grid: 'Gitter',
hideCompletedTasks: null,
hideFromProjectListAndFavorites: 'Skjul fra projektliste og favoritter',
+ host: null,
hours: 'Timer',
importBoard_title: 'Importer tavle',
invalidCurrentPassword: 'Nuværende adgangskode er ugyldig',
@@ -245,9 +248,11 @@ export default {
optional_inline: 'valgfri',
organization: 'Organisation',
others: 'Andre',
+ passwordIsSet: null,
phone: 'Telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA bruger <1><0>Apprise0>1> til at sende notifikationer til over 100 populære tjenester.',
+ port: null,
preferences: 'Præferencer',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: Tryk Ctrl-V (Cmd-V på Mac) for at vedhæfte direkte fra udklipsholder.',
@@ -256,6 +261,7 @@ export default {
projectNotFound_title: 'Projekt ikke fundet',
projectOwner: 'Projektejer',
referenceDataAndKnowledgeStorage: 'Reference data og vidensopbevaring',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Fjern projektleder',
removeMember_title: 'Fjern medlem',
role: 'Rolle',
@@ -282,6 +288,7 @@ export default {
shared: 'Delt',
sharedWithMe_title: 'Delt med mig',
showOnFrontOfCard: 'Vis på forsiden af kortet',
+ smtp: null,
sortList_title: 'Sortér liste',
stopwatch: 'Stopur',
story: 'Story',
@@ -293,6 +300,7 @@ export default {
taskList_title: 'Opgaveliste',
team: 'Team',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
'Der er ingen forhåndsvisning tilgængelig for denne vedhæftning.',
time: 'Tid',
@@ -307,6 +315,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Uploadede billeder',
url: null,
+ useSecureConnection: null,
userActions_title: 'Brugerhandlinger',
userAddedCardToList: '<0>{{user}}0> tilføjede <2>{{card}}2> til {{list}}',
userAddedThisCardToList: '<0>{{user}}0> tilføjede kortet til {{list}}',
@@ -447,6 +456,7 @@ export default {
restoreToList: 'Gendan til {{list}}',
returnToBoard: 'Tilbage til tavle',
save: 'Gem ændringer',
+ sendTestEmail: null,
showActive: 'Vis aktive',
showAllAttachments: 'Vis alle vedhæftninger ({{hidden}} skjulte)',
showCardsWithThisUser: 'Vis kort med denne bruger',
diff --git a/client/src/locales/de-DE/core.js b/client/src/locales/de-DE/core.js
index bcabd8f4..3d6ba762 100644
--- a/client/src/locales/de-DE/core.js
+++ b/client/src/locales/de-DE/core.js
@@ -125,6 +125,7 @@ export default {
cardsOnThisListAreReadyToBeWorkedOn: 'Karten in dieser Liste sind bereit zur Bearbeitung.',
clickHereOrRefreshPageToUpdate:
'<0>Hier klicken0> oder Seite aktualisieren, um zu aktualisieren.',
+ clientHostnameInEhlo: null,
closed: 'Geschlossen',
color: 'Farbe',
comments: 'Kommentare',
@@ -147,6 +148,7 @@ export default {
date: 'Datum',
deactivateUser_title: 'Benutzer deaktivieren',
defaultCardType_title: 'Standard-Kartentyp',
+ defaultFrom: null,
defaultView_title: 'Standardansicht',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Löschen Sie alle Arbeitsbereiche, um dieses Projekt löschen zu können',
@@ -211,6 +213,7 @@ export default {
grid: 'Raster',
hideCompletedTasks: null,
hideFromProjectListAndFavorites: 'Aus Projektliste und Favoriten ausblenden',
+ host: null,
hours: 'Stunden',
importBoard_title: 'Board importieren',
invalidCurrentPassword: 'Das aktuelle Passwort ist falsch',
@@ -254,9 +257,11 @@ export default {
optional_inline: 'Optional',
organization: 'Organisation',
others: 'Andere',
+ passwordIsSet: null,
phone: 'Telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA verwendet <1><0>Apprise0>1>, um Benachrichtigungen an über 100 beliebte Dienste zu senden.',
+ port: null,
preferences: 'Voreinstellungen',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tipp: Drücken Sie STRG-V (Cmd-V auf Mac), um einen Anhang aus der Zwischenablage hinzuzufügen.',
@@ -265,6 +270,7 @@ export default {
projectNotFound_title: 'Projekt nicht gefunden',
projectOwner: 'Projektleitung',
referenceDataAndKnowledgeStorage: 'Speichern von Wissen und Referenzen.',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Projektleiter entfernen',
removeMember_title: 'Mitglied entfernen',
role: 'Rolle',
@@ -291,6 +297,7 @@ export default {
shared: 'Geteilt',
sharedWithMe_title: 'Mit mir geteilt',
showOnFrontOfCard: 'Auf der Vorderseite der Karte anzeigen',
+ smtp: null,
sortList_title: 'Liste sortieren',
stopwatch: 'Stoppuhr',
story: 'Wissen',
@@ -302,6 +309,7 @@ export default {
taskList_title: 'Aufgaben',
team: 'Team',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Für diesen Anhang ist keine Vorschau verfügbar.',
time: 'Zeit',
title: 'Titel',
@@ -315,6 +323,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Hochgeladene Bilder',
url: null,
+ useSecureConnection: null,
userActions_title: 'Benutzeraktionen',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> hat diese Karte hinzugefügt zu {{list}}',
@@ -452,6 +461,7 @@ export default {
restoreToList: 'Wiederherstellen in {{list}}',
returnToBoard: 'Zurück zum Arbeitsbereich',
save: 'Speichern',
+ sendTestEmail: null,
showActive: 'Aktive anzeigen',
showAllAttachments: 'Alle Anhänge anzeigen ({{hidden}} versteckt)',
showCardsWithThisUser: 'Karten mit diesem Benutzer zeigen',
diff --git a/client/src/locales/el-GR/core.js b/client/src/locales/el-GR/core.js
index 77766ead..a41e2ea0 100644
--- a/client/src/locales/el-GR/core.js
+++ b/client/src/locales/el-GR/core.js
@@ -125,6 +125,7 @@ export default {
cardsOnThisListAreReadyToBeWorkedOn:
'Οι κάρτες σε αυτήν τη λίστα είναι έτοιμες για επεξεργασία.',
clickHereOrRefreshPageToUpdate: '<0>Κάντε κλικ εδώ0> ή ανανεώστε τη σελίδα για ενημέρωση.',
+ clientHostnameInEhlo: null,
closed: 'Κλειστό',
color: 'Χρώμα',
comments: 'Σχόλια',
@@ -148,6 +149,7 @@ export default {
date: 'Ημερομηνία',
deactivateUser_title: 'Απενεργοποίηση χρήστη',
defaultCardType_title: 'Προεπιλεγμένος τύπος κάρτας',
+ defaultFrom: null,
defaultView_title: 'Προεπιλεγμένη προβολή',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Διαγράψτε όλους τους πίνακες για να μπορέσετε να διαγράψετε αυτό το έργο',
@@ -212,6 +214,7 @@ export default {
grid: 'Πλέγμα',
hideCompletedTasks: null,
hideFromProjectListAndFavorites: 'Απόκρυψη από τη λίστα έργων και τα αγαπημένα',
+ host: null,
hours: 'Ώρες',
importBoard_title: 'Εισαγωγή πίνακα',
invalidCurrentPassword: 'Μη έγκυρος τρέχων κωδικός',
@@ -255,9 +258,11 @@ export default {
optional_inline: 'προαιρετικό',
organization: 'Οργάνωση',
others: 'Άλλοι',
+ passwordIsSet: null,
phone: 'Τηλέφωνο',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'Το PLANKA χρησιμοποιεί το <1><0>Apprise0>1> για να στέλνει ειδοποιήσεις σε πάνω από 100 δημοφιλείς υπηρεσίες.',
+ port: null,
preferences: 'Προτιμήσεις',
pressPasteShortcutToAddAttachmentFromClipboard:
'Συμβουλή: πατήστε Ctrl-V (Cmd-V σε Mac) για να προσθέσετε συνημμένο από το πρόχειρο.',
@@ -266,6 +271,7 @@ export default {
projectNotFound_title: 'Το έργο δεν βρέθηκε',
projectOwner: 'Ιδιοκτήτης έργου',
referenceDataAndKnowledgeStorage: 'Αποθήκευση δεδομένων και γνώσης αναφοράς.',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Αφαίρεση διαχειριστή',
removeMember_title: 'Αφαίρεση μέλους',
role: 'Ρόλος',
@@ -292,6 +298,7 @@ export default {
shared: 'Κοινόχρηστο',
sharedWithMe_title: 'Κοινόχρηστο με εμένα',
showOnFrontOfCard: 'Εμφάνιση στο μπροστινό μέρος της κάρτας',
+ smtp: null,
sortList_title: 'Ταξινόμηση λίστας',
stopwatch: 'Χρονόμετρο',
story: 'Ιστορία',
@@ -303,6 +310,7 @@ export default {
taskList_title: 'Λίστα εργασιών',
team: 'Ομάδα',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
'Δεν υπάρχει διαθέσιμη προεπισκόπηση για αυτό το συνημμένο.',
time: 'Ώρα',
@@ -317,6 +325,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Μεταφορτωμένες εικόνες',
url: null,
+ useSecureConnection: null,
userActions_title: 'Ενέργειες χρήστη',
userAddedCardToList: '<0>{{user}}0> πρόσθεσε <2>{{card}}2> στη λίστα {{list}}',
userAddedThisCardToList: '<0>{{user}}0> πρόσθεσε αυτήν την κάρτα στη λίστα {{list}}',
@@ -463,6 +472,7 @@ export default {
restoreToList: 'Επαναφορά στη {{list}}',
returnToBoard: 'Επιστροφή στον πίνακα',
save: 'Αποθήκευση',
+ sendTestEmail: null,
showActive: 'Εμφάνιση ενεργών',
showAllAttachments: 'Εμφάνιση όλων των συνημμένων ({{hidden}} κρυφά)',
showCardsWithThisUser: 'Εμφάνιση καρτών με αυτόν τον χρήστη',
diff --git a/client/src/locales/en-GB/core.js b/client/src/locales/en-GB/core.js
index fbd9c1ce..7029887a 100644
--- a/client/src/locales/en-GB/core.js
+++ b/client/src/locales/en-GB/core.js
@@ -115,6 +115,7 @@ export default {
'Cards on this list are complete and ready to be archived.',
cardsOnThisListAreReadyToBeWorkedOn: 'Cards on this list are ready to be worked on.',
clickHereOrRefreshPageToUpdate: '<0>Click here0> or refresh the page to update.',
+ clientHostnameInEhlo: 'Client hostname in EHLO',
closed: 'Closed',
color: 'Color',
comments: 'Comments',
@@ -137,6 +138,7 @@ export default {
date: 'Date',
deactivateUser_title: 'Deactivate User',
defaultCardType_title: 'Default Card Type',
+ defaultFrom: 'Default "from"',
defaultView_title: 'Default View',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Delete all boards to be able to delete this project',
@@ -201,6 +203,7 @@ export default {
grid: 'Grid',
hideCompletedTasks: 'Hide completed tasks',
hideFromProjectListAndFavorites: 'Hide from project list and favorites',
+ host: 'Host',
hours: 'Hours',
importBoard_title: 'Import Board',
invalidCurrentPassword: 'Invalid current password',
@@ -245,9 +248,11 @@ export default {
optional_inline: 'optional',
organization: 'Organization',
others: 'Others',
+ passwordIsSet: 'Password is set',
phone: 'Phone',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA uses <1><0>Apprise0>1> to send notifications to over 100 popular services.',
+ port: 'Port',
preferences: 'Preferences',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: press Ctrl-V (Cmd-V on Mac) to add an attachment from the clipboard.',
@@ -256,6 +261,7 @@ export default {
projectNotFound_title: 'Project Not Found',
projectOwner: 'Project owner',
referenceDataAndKnowledgeStorage: 'Reference data and knowledge storage.',
+ rejectUnauthorizedTlsCertificates: 'Reject unauthorized TLS certificates',
removeManager_title: 'Remove Manager',
removeMember_title: 'Remove Member',
role: 'Role',
@@ -282,6 +288,7 @@ export default {
shared: 'Shared',
sharedWithMe_title: 'Shared With Me',
showOnFrontOfCard: 'Show on front of card',
+ smtp: 'SMTP',
sortList_title: 'Sort List',
stopwatch: 'Stopwatch',
story: 'Story',
@@ -293,6 +300,7 @@ export default {
taskList_title: 'Task List',
team: 'Team',
terms: 'Terms',
+ testLog_title: 'Test Log',
thereIsNoPreviewAvailableForThisAttachment:
'There is no preview available for this attachment.',
time: 'Time',
@@ -307,6 +315,7 @@ export default {
uploadFailedNotEnoughStorageSpace: 'Upload failed: Not enough storage space.',
uploadedImages: 'Uploaded images',
url: 'URL',
+ useSecureConnection: 'Use secure connection',
userActions_title: 'User Actions',
userAddedCardToList: '<0>{{user}}0> added <2>{{card}}2> to {{list}}',
userAddedThisCardToList: '<0>{{user}}0> added this card to {{list}}',
@@ -446,6 +455,7 @@ export default {
restoreToList: 'Restore to {{list}}',
returnToBoard: 'Return to board',
save: 'Save',
+ sendTestEmail: 'Send test email',
showActive: 'Show active',
showAllAttachments: 'Show all attachments ({{hidden}} hidden)',
showCardsWithThisUser: 'Show cards with this user',
diff --git a/client/src/locales/en-US/core.js b/client/src/locales/en-US/core.js
index 4953f5cb..7caa7655 100644
--- a/client/src/locales/en-US/core.js
+++ b/client/src/locales/en-US/core.js
@@ -110,6 +110,7 @@ export default {
'Cards on this list are complete and ready to be archived.',
cardsOnThisListAreReadyToBeWorkedOn: 'Cards on this list are ready to be worked on.',
clickHereOrRefreshPageToUpdate: '<0>Click here0> or refresh the page to update.',
+ clientHostnameInEhlo: 'Client hostname in EHLO',
closed: 'Closed',
color: 'Color',
comments: 'Comments',
@@ -132,6 +133,7 @@ export default {
date: 'Date',
deactivateUser_title: 'Deactivate User',
defaultCardType_title: 'Default Card Type',
+ defaultFrom: 'Default "from"',
defaultView_title: 'Default View',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Delete all boards to be able to delete this project',
@@ -196,6 +198,7 @@ export default {
grid: 'Grid',
hideCompletedTasks: 'Hide completed tasks',
hideFromProjectListAndFavorites: 'Hide from project list and favorites',
+ host: 'Host',
hours: 'Hours',
importBoard_title: 'Import Board',
invalidCurrentPassword: 'Invalid current password',
@@ -240,9 +243,11 @@ export default {
optional_inline: 'optional',
organization: 'Organization',
others: 'Others',
+ passwordIsSet: 'Password is set',
phone: 'Phone',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA uses <1><0>Apprise0>1> to send notifications to over 100 popular services.',
+ port: 'Port',
preferences: 'Preferences',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: press Ctrl-V (Cmd-V on Mac) to add an attachment from the clipboard.',
@@ -251,6 +256,7 @@ export default {
projectNotFound_title: 'Project Not Found',
projectOwner: 'Project owner',
referenceDataAndKnowledgeStorage: 'Reference data and knowledge storage.',
+ rejectUnauthorizedTlsCertificates: 'Reject unauthorized TLS certificates',
removeManager_title: 'Remove Manager',
removeMember_title: 'Remove Member',
role: 'Role',
@@ -277,6 +283,7 @@ export default {
shared: 'Shared',
sharedWithMe_title: 'Shared With Me',
showOnFrontOfCard: 'Show on front of card',
+ smtp: 'SMTP',
sortList_title: 'Sort List',
stopwatch: 'Stopwatch',
story: 'Story',
@@ -288,6 +295,7 @@ export default {
taskList_title: 'Task List',
team: 'Team',
terms: 'Terms',
+ testLog_title: 'Test Log',
thereIsNoPreviewAvailableForThisAttachment:
'There is no preview available for this attachment.',
time: 'Time',
@@ -302,6 +310,7 @@ export default {
uploadFailedNotEnoughStorageSpace: 'Upload failed: Not enough storage space.',
uploadedImages: 'Uploaded images',
url: 'URL',
+ useSecureConnection: 'Use secure connection',
userActions_title: 'User Actions',
userAddedCardToList: '<0>{{user}}0> added <2>{{card}}2> to {{list}}',
userAddedThisCardToList: '<0>{{user}}0> added this card to {{list}}',
@@ -441,6 +450,7 @@ export default {
restoreToList: 'Restore to {{list}}',
returnToBoard: 'Return to board',
save: 'Save',
+ sendTestEmail: 'Send test email',
showActive: 'Show active',
showAllAttachments: 'Show all attachments ({{hidden}} hidden)',
showCardsWithThisUser: 'Show cards with this user',
diff --git a/client/src/locales/es-ES/core.js b/client/src/locales/es-ES/core.js
index 17a6ba97..1e30d626 100644
--- a/client/src/locales/es-ES/core.js
+++ b/client/src/locales/es-ES/core.js
@@ -116,6 +116,7 @@ export default {
cardsOnThisListAreReadyToBeWorkedOn:
'Las tarjetas en esta lista están listas para ser trabajadas.',
clickHereOrRefreshPageToUpdate: '<0>Haz clic aquí0> o actualiza la página para actualizar.',
+ clientHostnameInEhlo: null,
closed: 'Cerrado',
color: 'Color',
comments: 'Comentarios',
@@ -138,6 +139,7 @@ export default {
date: 'Fecha',
deactivateUser_title: 'Desactivar usuario',
defaultCardType_title: 'Tipo de tarjeta por defecto',
+ defaultFrom: null,
defaultView_title: 'Vista por defecto',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Elimina todos los tableros para poder eliminar este proyecto.',
@@ -202,6 +204,7 @@ export default {
grid: 'Cuadrícula',
hideCompletedTasks: null,
hideFromProjectListAndFavorites: 'Ocultar de la lista de proyectos y favoritos',
+ host: null,
hours: 'Horas',
importBoard_title: 'Importar tablero',
invalidCurrentPassword: 'Contraseña actual inválida',
@@ -245,9 +248,11 @@ export default {
optional_inline: 'opcional',
organization: 'Organización',
others: 'Otros',
+ passwordIsSet: null,
phone: 'Teléfono',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA utiliza <1><0>Apprise0>1> para enviar notificaciones a más de 100 servicios populares.',
+ port: null,
preferences: 'Preferencias',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: presiona Ctrl-V (Cmd-V en Mac) para añadir adjuntos desde el portapapeles.',
@@ -256,6 +261,7 @@ export default {
projectNotFound_title: 'Proyecto no encontrado',
projectOwner: 'Propietario del proyecto',
referenceDataAndKnowledgeStorage: 'Datos de referencia y almacenamiento de conocimiento',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: null,
removeMember_title: 'Remover Miembro',
role: 'Rol',
@@ -282,6 +288,7 @@ export default {
shared: 'Compartido',
sharedWithMe_title: 'Compartido conmigo',
showOnFrontOfCard: 'Mostrar en el frente de la tarjeta',
+ smtp: null,
sortList_title: 'Ordenar',
stopwatch: 'Temporizador',
story: 'Historia',
@@ -293,6 +300,7 @@ export default {
taskList_title: 'Lista de tareas',
team: 'Equipo',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
'No hay vista previa disponible para este adjunto.',
time: 'Tiempo',
@@ -307,6 +315,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Imágenes subidas',
url: null,
+ useSecureConnection: null,
userActions_title: 'Acciones de Usuario',
userAddedCardToList: '<0>{{user}}0> añadió <2>{{card}}2> a {{list}}',
userAddedThisCardToList: '<0>{{user}}0> añadido a esta tarjeta en {{list}}',
@@ -446,6 +455,7 @@ export default {
restoreToList: 'Restaurar a {{list}}',
returnToBoard: 'Volver al tablero',
save: 'Guardar',
+ sendTestEmail: null,
showActive: 'Mostrar activos',
showAllAttachments: 'Mostrar todos los adjuntos ({{hidden}} ocultos)',
showCardsWithThisUser: 'Mostrar tarjetas con este usuario',
diff --git a/client/src/locales/et-EE/core.js b/client/src/locales/et-EE/core.js
index 79634ef3..c3f91371 100644
--- a/client/src/locales/et-EE/core.js
+++ b/client/src/locales/et-EE/core.js
@@ -115,6 +115,7 @@ export default {
'Kaardid sellel nimekirjal on täidetud ja valmis arhiveerimiseks.',
cardsOnThisListAreReadyToBeWorkedOn: 'Kaardid sellel nimekirjal on valmis tööle.',
clickHereOrRefreshPageToUpdate: '<0>Klõpsa siia0> või uuendage lehte.',
+ clientHostnameInEhlo: null,
closed: 'Suletud',
color: 'Värv',
comments: 'Kommentaarid',
@@ -137,6 +138,7 @@ export default {
date: 'Kuupäev',
deactivateUser_title: 'Deaktiveeri kasutaja',
defaultCardType_title: 'Vaikimisi kaardi tüüp',
+ defaultFrom: null,
defaultView_title: 'Vaikimisi vaade',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Kustuta kõik tahvlid, et seda projekti kustutada',
@@ -201,6 +203,7 @@ export default {
grid: 'Grill',
hideCompletedTasks: null,
hideFromProjectListAndFavorites: 'Peida projektiloendist ja lemmikutest',
+ host: null,
hours: 'Tunnid',
importBoard_title: 'Impordi tahvel',
invalidCurrentPassword: 'Vale praegune parool',
@@ -244,9 +247,11 @@ export default {
optional_inline: 'valikuline',
organization: 'Organisatsioon',
others: 'Teised',
+ passwordIsSet: null,
phone: 'Telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA kasutab <1><0>Apprise0>1> teavitusteenuse, et teavitada üle 100 populaarset teenust.',
+ port: null,
preferences: 'Eelistused',
pressPasteShortcutToAddAttachmentFromClipboard:
"Näpunäide: vajutage Ctrl-V (Cmd-V Mac'il) manuse lisamiseks kleebist.",
@@ -255,6 +260,7 @@ export default {
projectNotFound_title: 'Projekt ei leitud',
projectOwner: 'Projekti omanik',
referenceDataAndKnowledgeStorage: 'Viideandmete ja teadmise salvestamiseks.',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Eemalda haldur',
removeMember_title: 'Eemalda liige',
role: 'Roll',
@@ -281,6 +287,7 @@ export default {
shared: 'Jagatud',
sharedWithMe_title: 'Jagatud minuga',
showOnFrontOfCard: 'Kuva kaardi ees',
+ smtp: null,
sortList_title: 'Nimekiri sorteerimine',
stopwatch: 'Stopper',
story: 'Kirjeldus',
@@ -292,6 +299,7 @@ export default {
taskList_title: 'Ülesanne nimekiri',
team: 'Töögrupp',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Selle manusi eelvaadet pole saadaval.',
time: 'Aeg',
title: 'Pealkiri',
@@ -305,6 +313,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Laaditud pildid',
url: null,
+ useSecureConnection: null,
userActions_title: 'Kasutaja tegevused',
userAddedCardToList: '<0>{{user}}0> lisas <2>{{card}}2> nimekirjaan {{list}}',
userAddedThisCardToList: '<0>{{user}}0> lisas selle kaardi nimekirjaan {{list}}',
@@ -445,6 +454,7 @@ export default {
restoreToList: 'Taasta nimekirja {{list}}',
returnToBoard: 'Tagasi tahvlile',
save: 'Salvesta',
+ sendTestEmail: null,
showActive: 'Näita aktiivseid',
showAllAttachments: 'Näita kõiki manuseid ({{hidden}} peidetud)',
showCardsWithThisUser: 'Näita selle kasutajaga kaarte',
diff --git a/client/src/locales/fa-IR/core.js b/client/src/locales/fa-IR/core.js
index 0265000a..c13777d4 100644
--- a/client/src/locales/fa-IR/core.js
+++ b/client/src/locales/fa-IR/core.js
@@ -99,6 +99,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'رنگ',
comments: null,
@@ -121,6 +122,7 @@ export default {
date: 'تاریخ',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'حذف پیوست',
@@ -184,6 +186,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'ساعتها',
importBoard_title: 'وارد کردن برد',
invalidCurrentPassword: 'رمز عبور فعلی نامعتبر است',
@@ -227,8 +230,10 @@ export default {
optional_inline: 'اختیاری',
organization: 'سازمان',
others: null,
+ passwordIsSet: null,
phone: 'تلفن',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'ترجیحات',
pressPasteShortcutToAddAttachmentFromClipboard:
'نکته: با فشردن Ctrl-V (Cmd-V در مک) میتوانید پیوست را از کلیپ بورد اضافه کنید.',
@@ -237,6 +242,7 @@ export default {
projectNotFound_title: 'پروژه یافت نشد',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'حذف مدیر',
removeMember_title: 'حذف عضو',
role: null,
@@ -263,6 +269,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: 'مرتبسازی لیست',
stopwatch: 'کرنومتر',
story: null,
@@ -274,6 +281,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'پیش نمایشی برای این پیوست موجود نیست.',
time: 'زمان',
title: 'عنوان',
@@ -287,6 +295,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'اقدامات کاربر',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> این کارت را به {{list}} اضافه کرد',
@@ -424,6 +433,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'ذخیره',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'نمایش همه پیوستها ({{hidden}} مخفی)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/fi-FI/core.js b/client/src/locales/fi-FI/core.js
index 40b5d16a..2789a3b6 100644
--- a/client/src/locales/fi-FI/core.js
+++ b/client/src/locales/fi-FI/core.js
@@ -111,6 +111,7 @@ export default {
'Tämän listan kortit ovat valmiita ja voidaan arkistoida.',
cardsOnThisListAreReadyToBeWorkedOn: 'Tämän listan kortit ovat valmiita työstettäväksi.',
clickHereOrRefreshPageToUpdate: '<0>Päivitä tästä0> tai lataa sivu uudelleen.',
+ clientHostnameInEhlo: null,
closed: 'Suljettu',
color: 'Väri',
comments: 'Kommentit',
@@ -133,6 +134,7 @@ export default {
date: 'Päivämäärä',
deactivateUser_title: 'Poista käyttäjä käytöstä',
defaultCardType_title: 'Oletuskorttityyppi',
+ defaultFrom: null,
defaultView_title: 'Oletusnäkymä',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Poista kaikki taulut, jotta voit poistaa tämän projektin',
@@ -197,6 +199,7 @@ export default {
grid: 'Ruudukko',
hideCompletedTasks: null,
hideFromProjectListAndFavorites: 'Piilota projektilistasta ja suosikeista',
+ host: null,
hours: 'Tunnit',
importBoard_title: 'Tuo taulu',
invalidCurrentPassword: 'Virheellinen nykyinen salasana',
@@ -240,9 +243,11 @@ export default {
optional_inline: 'valinnainen',
organization: 'Organisaatio',
others: 'Muut',
+ passwordIsSet: null,
phone: 'Puhelin',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA käyttää <1><0>Apprise0>1> lähettääkseen ilmoituksia yli 100 suosittuun palveluun.',
+ port: null,
preferences: 'Asetukset',
pressPasteShortcutToAddAttachmentFromClipboard:
'Vinkki: paina Ctrl-V (tai Cmd-V Macilla) lisätäksesi liitteen leikepöydältä.',
@@ -251,6 +256,7 @@ export default {
projectNotFound_title: 'Projektia ei löytynyt',
projectOwner: 'Projektin omistaja',
referenceDataAndKnowledgeStorage: 'Viitetiedot ja tietovarasto.',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Poista ylläpitäjä',
removeMember_title: 'Poista jäsen',
role: 'Rooli',
@@ -277,6 +283,7 @@ export default {
shared: 'Jaettu',
sharedWithMe_title: 'Jaettu kanssani',
showOnFrontOfCard: 'Näytä kortin etupuolella',
+ smtp: null,
sortList_title: 'Lajittele lista',
stopwatch: 'Ajastin',
story: 'Tarina',
@@ -288,6 +295,7 @@ export default {
taskList_title: 'Tehtävälista',
team: 'Tiimi',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Tälle liitteelle ei ole esikatselua saatavilla.',
time: 'Aika',
title: 'Otsikko',
@@ -301,6 +309,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Ladatut kuvat',
url: null,
+ useSecureConnection: null,
userActions_title: 'Käyttäjän toiminnot',
userAddedCardToList: '<0>{{user}}0> lisäsi <2>{{card}}2> listaan {{list}}',
userAddedThisCardToList: '<0>{{user}}0> lisäsi tämän kortin listaan {{list}}',
@@ -445,6 +454,7 @@ export default {
restoreToList: 'Palauta listaan {{list}}',
returnToBoard: 'Palaa tauluun',
save: 'Tallenna',
+ sendTestEmail: null,
showActive: 'Näytä aktiiviset',
showAllAttachments: 'Näytä kaikki liitteet ({{hidden}} piilotettu)',
showCardsWithThisUser: 'Näytä kortit, joissa tämä käyttäjä',
diff --git a/client/src/locales/fr-FR/core.js b/client/src/locales/fr-FR/core.js
index b1c426a4..9d6652bb 100644
--- a/client/src/locales/fr-FR/core.js
+++ b/client/src/locales/fr-FR/core.js
@@ -118,6 +118,7 @@ export default {
cardsOnThisListAreReadyToBeWorkedOn: 'Les cartes de cette liste sont prêtes à être traitées.',
clickHereOrRefreshPageToUpdate:
'<0>Cliquez ici0> ou rafraîchissez la page pour mettre à jour.',
+ clientHostnameInEhlo: null,
closed: 'Fermé',
color: 'Couleur',
comments: 'Commentaires',
@@ -141,6 +142,7 @@ export default {
date: 'Date',
deactivateUser_title: 'Désactiver l’utilisateur',
defaultCardType_title: 'Type de carte par défaut',
+ defaultFrom: null,
defaultView_title: 'Vue par défaut',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Supprimer tous les tableaux pour pouvoir supprimer ce projet.',
@@ -205,6 +207,7 @@ export default {
grid: 'Grille',
hideCompletedTasks: null,
hideFromProjectListAndFavorites: 'Masquer de la liste des projets et des favoris',
+ host: null,
hours: 'Heures',
importBoard_title: 'Importer un tableau',
invalidCurrentPassword: 'Mot de passe actuel invalide',
@@ -248,9 +251,11 @@ export default {
optional_inline: 'optionnel',
organization: 'Organisation',
others: 'Autres',
+ passwordIsSet: null,
phone: 'Téléphone',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA utilise <1><0>Apprise0>1> pour envoyer des notifications vers plus de 100 services populaires.',
+ port: null,
preferences: 'Préférences',
pressPasteShortcutToAddAttachmentFromClipboard:
'Conseil: appuyer sur Ctrl-V (Cmd-V sur Mac) pour ajouter une pièce jointe depuis le presse-papiers',
@@ -259,6 +264,7 @@ export default {
projectNotFound_title: 'Projet introuvable',
projectOwner: 'Propriétaire de projet',
referenceDataAndKnowledgeStorage: 'Stockage de données de référence et de connaissances.',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Supprimer le responsable',
removeMember_title: 'Supprimer le membre',
role: 'Rôle',
@@ -285,6 +291,7 @@ export default {
shared: 'Partagé',
sharedWithMe_title: 'Partagé avec moi',
showOnFrontOfCard: 'Afficher sur le devant de la carte',
+ smtp: null,
sortList_title: 'Trier la liste',
stopwatch: 'Minuteur',
story: 'Story',
@@ -296,6 +303,7 @@ export default {
taskList_title: 'Liste de tâches',
team: "Mes projets d'équipe",
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
"Il n'y a pas d'aperçu disponible pour cette pièce jointe.",
time: 'Temps',
@@ -310,6 +318,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Images téléchargées',
url: 'URL',
+ useSecureConnection: null,
userActions_title: "Actions de l'utilisateur",
userAddedCardToList: '<0>{{user}}0> a ajouté <2>{{card}}2> à {{list}}',
userAddedThisCardToList: '<0>{{user}}0> a ajouté cette carte à {{list}}',
@@ -449,6 +458,7 @@ export default {
restoreToList: 'Restauré dans {{list}}',
returnToBoard: 'Retourner au tableau',
save: 'Sauvegarder',
+ sendTestEmail: null,
showActive: 'Voir les actifs',
showAllAttachments: 'Afficher toutes les pièces jointes ({{hidden}} masquées)',
showCardsWithThisUser: 'Voir les cartes avec cet utilisateur',
diff --git a/client/src/locales/hu-HU/core.js b/client/src/locales/hu-HU/core.js
index 58dd674e..e6481d34 100644
--- a/client/src/locales/hu-HU/core.js
+++ b/client/src/locales/hu-HU/core.js
@@ -108,6 +108,7 @@ export default {
cardsOnThisListAreReadyToBeWorkedOn: 'A listán lévő kártyák készen állnak a munkára.',
clickHereOrRefreshPageToUpdate:
'<0>Kattintson ide0> vagy frissítse az oldalt a frissítéshez.',
+ clientHostnameInEhlo: null,
closed: 'Lezárt',
color: 'Szín',
comments: 'Megjegyzések',
@@ -130,6 +131,7 @@ export default {
date: 'Dátum',
deactivateUser_title: 'Felhasználó inaktiválása',
defaultCardType_title: 'Alapértelmezett kártyatípus',
+ defaultFrom: null,
defaultView_title: 'Alapértelmezett nézet',
deleteAllBoardsToBeAbleToDeleteThisProject:
'A projekt törléséhez törölni kell az összes táblát.',
@@ -194,6 +196,7 @@ export default {
grid: 'Rács',
hideCompletedTasks: 'Befejezett feladatok elrejtése',
hideFromProjectListAndFavorites: 'Elrejtés a projektlistából és a kedvencekből',
+ host: null,
hours: 'Órák',
importBoard_title: 'Tábla importálása',
invalidCurrentPassword: 'Érvénytelen jelenlegi jelszó',
@@ -238,9 +241,11 @@ export default {
optional_inline: 'opcionális',
organization: 'Szervezet',
others: 'Egyebek',
+ passwordIsSet: null,
phone: 'Telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'A PLANKA az Apprise szolgáltatást használja több mint 100 népszerű szolgáltatás értesítéseinek küldésére.',
+ port: null,
preferences: 'Beállítások',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tipp: nyomja meg a Ctrl-V (Cmd-V a Mac-en) billentyűkombinációt a vágólapról történő melléklet hozzáadásához.',
@@ -249,6 +254,7 @@ export default {
projectNotFound_title: 'Projekt nem található',
projectOwner: 'Projekt tulajdonos',
referenceDataAndKnowledgeStorage: 'Referenciaadatok és tudástár.',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Menedzser eltávolítása',
removeMember_title: 'Tag eltávolítása',
role: 'Szerepkör',
@@ -275,6 +281,7 @@ export default {
shared: 'Megosztott',
sharedWithMe_title: 'Velem megosztva',
showOnFrontOfCard: 'Megjelenítés a kártya borítóján',
+ smtp: null,
sortList_title: 'Rendezés listában',
stopwatch: 'Stopper',
story: 'Story',
@@ -286,6 +293,7 @@ export default {
taskList_title: 'Feladatlista',
team: 'Csapat',
terms: 'Felhasználási feltételek',
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Nincs elérhető előnézet ehhez a melléklethez.',
time: 'Idő',
title: 'Cím',
@@ -299,6 +307,7 @@ export default {
uploadFailedNotEnoughStorageSpace: 'Feltöltési hiba: nincs elég szabad tárhely',
uploadedImages: 'Feltöltött képek',
url: 'URL',
+ useSecureConnection: null,
userActions_title: 'Felhasználói műveletek',
userAddedCardToList:
'<0>{{user}}0> hozzáadta a(z) <2>{{card}}2> kártyát ehhez a listához: {{list}}',
@@ -446,6 +455,7 @@ export default {
restoreToList: 'Visszaállítás ide: {{list}}',
returnToBoard: 'Vissza a táblához',
save: 'Mentés',
+ sendTestEmail: null,
showActive: 'Aktívak megjelenítése',
showAllAttachments: 'Összes melléklet megjelenítése ({{hidden}} rejtve)',
showCardsWithThisUser: 'Kártyák megjelenítése ezzel a felhasználóval',
diff --git a/client/src/locales/id-ID/core.js b/client/src/locales/id-ID/core.js
index 8c0cd38b..56409dc5 100644
--- a/client/src/locales/id-ID/core.js
+++ b/client/src/locales/id-ID/core.js
@@ -101,6 +101,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'Warna',
comments: null,
@@ -123,6 +124,7 @@ export default {
date: 'Tanggal',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Hapus Lampiran',
@@ -186,6 +188,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'Jam',
importBoard_title: 'Impor Papan',
invalidCurrentPassword: 'Kata sandi saat ini tidak valid',
@@ -229,8 +232,10 @@ export default {
optional_inline: 'opsional',
organization: 'Organisasi',
others: null,
+ passwordIsSet: null,
phone: 'Ponsel',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Preferensi',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: tekan Ctrl-V (Cmd-V di Mac) untuk menambahkan lampiran dari papan klip.',
@@ -239,6 +244,7 @@ export default {
projectNotFound_title: 'Proyek Tidak Ditemukan',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Hapus Manager',
removeMember_title: 'Hapus Anggota',
role: null,
@@ -265,6 +271,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: null,
stopwatch: 'Stopwatch',
story: null,
@@ -276,6 +283,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
'Tidak ada pratinjau yang tersedia untuk lampiran ini.',
time: 'Waktu',
@@ -290,6 +298,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Aksi Pengguna',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> menambahkan kartu ini ke {{list}}',
@@ -426,6 +435,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Simpan',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'Tampilkan semua lampiran ({{hidden}} tersembunyi)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/it-IT/core.js b/client/src/locales/it-IT/core.js
index f6636d6a..2e7e9d75 100644
--- a/client/src/locales/it-IT/core.js
+++ b/client/src/locales/it-IT/core.js
@@ -116,6 +116,7 @@ export default {
cardsOnThisListAreReadyToBeWorkedOn:
'Le schede in questa lista sono pronte per essere lavorate.',
clickHereOrRefreshPageToUpdate: '<0>Clicca qui0> o ricarica la pagina per aggiornare.',
+ clientHostnameInEhlo: null,
closed: 'Chiuso',
color: 'Colore',
comments: 'Commenti',
@@ -139,6 +140,7 @@ export default {
date: 'Data',
deactivateUser_title: 'Disattiva utente',
defaultCardType_title: 'Tipo di scheda predefinito',
+ defaultFrom: null,
defaultView_title: 'Vista predefinita',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Elimina tutte le bacheche per poter eliminare questo progetto.',
@@ -203,6 +205,7 @@ export default {
grid: 'Griglia',
hideCompletedTasks: 'Nascondi task completate',
hideFromProjectListAndFavorites: 'Nascondi dalla lista dei progetti e dai preferiti',
+ host: null,
hours: 'Ore',
importBoard_title: 'Importa board',
invalidCurrentPassword: 'Password corrente non valida',
@@ -247,9 +250,11 @@ export default {
optional_inline: 'opzionale',
organization: 'Organizazzione',
others: 'Altri',
+ passwordIsSet: null,
phone: 'Telefono',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA utilizza <1><0>Apprise0>1> per inviare notifiche a oltre 100 servizi popolari.',
+ port: null,
preferences: 'Preferenze',
pressPasteShortcutToAddAttachmentFromClipboard:
'Consiglio: premi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard.',
@@ -258,6 +263,7 @@ export default {
projectNotFound_title: 'Progetto non trovato',
projectOwner: 'Proprietario del progetto',
referenceDataAndKnowledgeStorage: 'Dati di riferimento e di archiviazione',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Rimuovi manager',
removeMember_title: 'Rimuovi membro',
role: 'Ruolo',
@@ -284,6 +290,7 @@ export default {
shared: 'Condiviso',
sharedWithMe_title: 'Condiviso con me',
showOnFrontOfCard: 'Mostra davanti alla scheda',
+ smtp: null,
sortList_title: 'Ordina',
stopwatch: 'Timer',
story: 'Storia',
@@ -295,6 +302,7 @@ export default {
taskList_title: 'Lista di task',
team: 'Team',
terms: 'Ho letto e accetto i termini e condizioni.',
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
'Non è disponibile alcuna anteprima per questo allegato.',
time: 'Tempo',
@@ -310,6 +318,7 @@ export default {
'Caricamento fallito: spazio di archiviazione insufficiente.',
uploadedImages: 'Immagini caricate',
url: 'URL',
+ useSecureConnection: null,
userActions_title: 'Azioni utente',
userAddedCardToList: '<0>{{user}}0> ha aggiunto <2>{{card}}2> a {{list}}',
userAddedThisCardToList: '<0>{{user}}0> ha aggiunto questa task a {{list}}',
@@ -450,6 +459,7 @@ export default {
restoreToList: 'Ripristina a {{list}}',
returnToBoard: 'Torna alla bacheca',
save: 'Salva',
+ sendTestEmail: null,
showActive: 'Mostra attivi',
showAllAttachments: 'Mostra tutti gli allegati ({{hidden}} nascosti)',
showCardsWithThisUser: 'Mostra schede con questo utente',
diff --git a/client/src/locales/ja-JP/core.js b/client/src/locales/ja-JP/core.js
index 354cd6c3..29692950 100644
--- a/client/src/locales/ja-JP/core.js
+++ b/client/src/locales/ja-JP/core.js
@@ -101,6 +101,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: '色',
comments: null,
@@ -123,6 +124,7 @@ export default {
date: '日付',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: '添付ファイルを削除',
@@ -186,6 +188,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: '時間',
importBoard_title: 'インポートボード',
invalidCurrentPassword: '現在のパスワードが無効',
@@ -229,8 +232,10 @@ export default {
optional_inline: '任意',
organization: '組織',
others: null,
+ passwordIsSet: null,
phone: '電話番号',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: '環境設定',
pressPasteShortcutToAddAttachmentFromClipboard:
'ヒント: Ctrl-V(MacではCmd-V)を押して、クリップボードから添付ファイルを追加します。',
@@ -239,6 +244,7 @@ export default {
projectNotFound_title: 'プロジェクトがありません',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'マネージャーを削除',
removeMember_title: 'メンバーを削除',
role: null,
@@ -265,6 +271,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: null,
stopwatch: 'タイマー',
story: null,
@@ -276,6 +283,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'この添付ファイルにはプレビューがありません。',
time: '時間',
title: 'タイトル',
@@ -289,6 +297,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'ユーザーのアクション',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> 様が {{list}} をこのカードに追加しました',
@@ -426,6 +435,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: '保存',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: '全ての添付ファイルを表示する({{hidden}} 非表示)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/ko-KR/core.js b/client/src/locales/ko-KR/core.js
index b88fddde..11e1c0d9 100644
--- a/client/src/locales/ko-KR/core.js
+++ b/client/src/locales/ko-KR/core.js
@@ -99,6 +99,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: '색상',
comments: null,
@@ -121,6 +122,7 @@ export default {
date: '날짜',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: '첨부 파일 삭제',
@@ -184,6 +186,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: '시간',
importBoard_title: '보드 가져오기',
invalidCurrentPassword: '잘못된 현재 비밀번호',
@@ -227,8 +230,10 @@ export default {
optional_inline: '선택 사항',
organization: '조직',
others: null,
+ passwordIsSet: null,
phone: '전화',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: '환경 설정',
pressPasteShortcutToAddAttachmentFromClipboard:
'팁: Ctrl-V (Mac에서는 Cmd-V)를 눌러 클립보드에서 첨부 파일을 추가하세요.',
@@ -237,6 +242,7 @@ export default {
projectNotFound_title: '프로젝트를 찾을 수 없음',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: '관리자 제거',
removeMember_title: '멤버 제거',
role: null,
@@ -263,6 +269,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: '목록 정렬',
stopwatch: '스톱워치',
story: null,
@@ -274,6 +281,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
'이 첨부 파일에 대한 미리보기를 사용할 수 없습니다.',
time: '시간',
@@ -288,6 +296,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: '사용자 작업',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0>님이 이 카드를 {{list}}에 추가했습니다',
@@ -425,6 +434,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: '저장',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: '모든 첨부 파일 보기 ({{hidden}} 숨김)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/nl-NL/core.js b/client/src/locales/nl-NL/core.js
index a304039e..537ddbc4 100644
--- a/client/src/locales/nl-NL/core.js
+++ b/client/src/locales/nl-NL/core.js
@@ -101,6 +101,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'Kleur',
comments: null,
@@ -123,6 +124,7 @@ export default {
date: 'Datum',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Bijlage verwijderen',
@@ -186,6 +188,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'Uren',
importBoard_title: 'Bord importeren',
invalidCurrentPassword: 'Ongeldig huidig wachtwoord',
@@ -229,8 +232,10 @@ export default {
optional_inline: 'optioneel',
organization: 'Organisatie',
others: null,
+ passwordIsSet: null,
phone: 'Telefoon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Voorkeuren',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: druk op Ctrl-V (Cmd-V op Mac) om een bijlage van het klembord toe te voegen.',
@@ -239,6 +244,7 @@ export default {
projectNotFound_title: 'Project niet gevonden',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Manager verwijderen',
removeMember_title: 'Lid verwijderen',
role: null,
@@ -265,6 +271,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: null,
stopwatch: 'Stopwatch',
story: null,
@@ -276,6 +283,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
'Er is geen voorbeeld beschikbaar voor deze bijlage.',
time: 'Tijd',
@@ -290,6 +298,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Gebruikersacties',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> heeft deze kaart toegevoegd aan {{list}}',
@@ -427,6 +436,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Opslaan',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'Alle bijlagen weergeven ({{hidden}} verbergen)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/pl-PL/core.js b/client/src/locales/pl-PL/core.js
index 79db2651..a1a1f364 100644
--- a/client/src/locales/pl-PL/core.js
+++ b/client/src/locales/pl-PL/core.js
@@ -108,6 +108,7 @@ export default {
'Karty na tej liście są ukończone i gotowe do zarchiwizowania.',
cardsOnThisListAreReadyToBeWorkedOn: 'Karty na tej liście są gotowe do pracy nad nimi.',
clickHereOrRefreshPageToUpdate: '<0>Naciśnij tutaj0> lub odśwież stronę, by zaktualizować.',
+ clientHostnameInEhlo: null,
closed: 'Zamknięte',
color: 'Kolor',
comments: 'Komentarze',
@@ -130,6 +131,7 @@ export default {
date: 'Data',
deactivateUser_title: 'Dezaktywuj Użytkownika',
defaultCardType_title: 'Domyślny Typ Karty',
+ defaultFrom: null,
defaultView_title: 'Domyślny Widok',
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Usuń Załącznik',
@@ -193,6 +195,7 @@ export default {
grid: 'Siatka',
hideCompletedTasks: null,
hideFromProjectListAndFavorites: 'Ukryj z listy projektów i ulubionych',
+ host: null,
hours: 'Godzin',
importBoard_title: 'Importuj Tablicę',
invalidCurrentPassword: 'Błędne obecne hasło',
@@ -236,9 +239,11 @@ export default {
optional_inline: 'opcjonalny',
organization: 'Organizacja',
others: 'Inne',
+ passwordIsSet: null,
phone: 'Telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA używa <1><0>Apprise0>1> do wysyłania powiadomień do ponad 100 popularnych serwisów.',
+ port: null,
preferences: 'Preferencje',
pressPasteShortcutToAddAttachmentFromClipboard:
'Podpowiedź: naciśnij Ctrl-V (Cmd-V na Macu) aby dodać załącznik ze schowka.',
@@ -247,6 +252,7 @@ export default {
projectNotFound_title: 'Projektu Nie Znaleziono',
projectOwner: 'Właściciel projektu',
referenceDataAndKnowledgeStorage: 'Odnoś się do danych i przechowuj wiedzę',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Usuń Zarządcę',
removeMember_title: 'Usuń Członka',
role: 'Rola',
@@ -273,6 +279,7 @@ export default {
shared: 'Udostępniane',
sharedWithMe_title: 'Udostępniane Dla Mnie',
showOnFrontOfCard: 'Pokazuj na przodzie karty',
+ smtp: null,
sortList_title: 'Sortowanie Listy',
stopwatch: 'Stoper',
story: 'Scenorys',
@@ -284,6 +291,7 @@ export default {
taskList_title: 'Lista Zadań',
team: 'Zespół',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Brak podglądu dostępnego dla tego załącznika.',
time: 'Czas',
title: 'Tytuł',
@@ -297,6 +305,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Wgrane obrazy',
url: null,
+ useSecureConnection: null,
userActions_title: 'Akcje Użytkownika',
userAddedCardToList: '<0>{{user}}0> dodał <2>{{card}}2> do {{list}}',
userAddedThisCardToList: '<0>{{user}}0> dodał tę kartę do {{list}}',
@@ -437,6 +446,7 @@ export default {
restoreToList: 'Przywróć na {{list}}',
returnToBoard: 'Przywróć do tablicy',
save: 'Zapisz',
+ sendTestEmail: null,
showActive: 'Pokaż aktywne',
showAllAttachments: 'Pokaż wszystkie załączniki ({{hidden}} są ukryte)',
showCardsWithThisUser: 'Pokaż karty z tym użytkownikiem',
diff --git a/client/src/locales/pt-BR/core.js b/client/src/locales/pt-BR/core.js
index daeb7379..7f4d55f6 100644
--- a/client/src/locales/pt-BR/core.js
+++ b/client/src/locales/pt-BR/core.js
@@ -116,6 +116,7 @@ export default {
cardsOnThisListAreReadyToBeWorkedOn:
'Os cartões nesta lista estão prontos para serem trabalhados.',
clickHereOrRefreshPageToUpdate: '<0>Clique aqui0> ou atualize a página para atualizar.',
+ clientHostnameInEhlo: null,
closed: 'Fechado',
color: 'Cor',
comments: 'Comentários',
@@ -139,6 +140,7 @@ export default {
date: 'Data',
deactivateUser_title: 'Desativar Usuário',
defaultCardType_title: 'Tipo de Cartão Padrão',
+ defaultFrom: null,
defaultView_title: 'Visualização Padrão',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Excluir todos os quadros para poder excluir este projeto',
@@ -203,6 +205,7 @@ export default {
grid: 'Grade',
hideCompletedTasks: 'Ocultar tarefas concluídas',
hideFromProjectListAndFavorites: 'Ocultar da lista de projetos e favoritos',
+ host: null,
hours: 'Horas',
importBoard_title: 'Importar Quadro',
invalidCurrentPassword: 'Senha atual inválida',
@@ -247,9 +250,11 @@ export default {
optional_inline: 'opcional',
organization: 'Organização',
others: 'Outros',
+ passwordIsSet: null,
phone: 'Telefone',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA usa <1><0>Apprise0>1> para enviar notificações para mais de 100 serviços populares.',
+ port: null,
preferences: 'Preferências',
pressPasteShortcutToAddAttachmentFromClipboard:
'Dica: pressione Ctrl-V (Cmd-V no Mac) para adicionar um anexo da área de transferência.',
@@ -258,6 +263,7 @@ export default {
projectNotFound_title: 'Projeto não encontrado',
projectOwner: 'Proprietário do projeto',
referenceDataAndKnowledgeStorage: 'Armazenamento de dados de referência e conhecimento.',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Remover Gerente',
removeMember_title: 'Remover Membro',
role: 'Função',
@@ -284,6 +290,7 @@ export default {
shared: 'Compartilhado',
sharedWithMe_title: 'Compartilhado Comigo',
showOnFrontOfCard: 'Mostrar na frente do cartão',
+ smtp: null,
sortList_title: 'Ordenar Lista',
stopwatch: 'Cronômetro',
story: 'História',
@@ -295,6 +302,7 @@ export default {
taskList_title: 'Lista de Tarefas',
team: 'Equipe',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
'Não há pré-visualização disponível para este anexo.',
time: 'Tempo',
@@ -309,6 +317,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Imagens enviadas',
url: 'URL',
+ useSecureConnection: null,
userActions_title: 'Ações do Usuário',
userAddedCardToList: '<0>{{user}}0> adicionou <2>{{card}}2> à {{list}}',
userAddedThisCardToList: '<0>{{user}}0> adicionou este cartão a {{list}}',
@@ -449,6 +458,7 @@ export default {
restoreToList: 'Restaurar para {{list}}',
returnToBoard: 'Voltar ao quadro',
save: 'Salvar',
+ sendTestEmail: null,
showActive: 'Mostrar ativos',
showAllAttachments: 'Mostrar todos os anexos ({{hidden}} ocultos)',
showCardsWithThisUser: 'Mostrar cartões com este usuário',
diff --git a/client/src/locales/pt-PT/core.js b/client/src/locales/pt-PT/core.js
index 80859f50..10b52760 100644
--- a/client/src/locales/pt-PT/core.js
+++ b/client/src/locales/pt-PT/core.js
@@ -102,6 +102,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'Cor',
comments: null,
@@ -124,6 +125,7 @@ export default {
date: 'Data',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Eliminar Anexo',
@@ -187,6 +189,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'Horas',
importBoard_title: 'Importar Quadro',
invalidCurrentPassword: 'Palavra-passe atual inválida',
@@ -230,8 +233,10 @@ export default {
optional_inline: 'opcional',
organization: 'Organização',
others: null,
+ passwordIsSet: null,
phone: 'Telefone',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Preferências',
pressPasteShortcutToAddAttachmentFromClipboard:
'Dica: prima Ctrl-V (Cmd-V no Mac) para adicionar um anexo da área de transferência.',
@@ -240,6 +245,7 @@ export default {
projectNotFound_title: 'Projeto não encontrado',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Remover Gestor',
removeMember_title: 'Remover Membro',
role: null,
@@ -266,6 +272,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: null,
stopwatch: 'Cronómetro',
story: null,
@@ -277,6 +284,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
'Não há pré-visualização disponível para este anexo.',
time: 'Tempo',
@@ -291,6 +299,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Ações do Utilizador',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> adicionou este cartão à {{list}}',
@@ -428,6 +437,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Guardar',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'Mostrar todos os anexos ({{hidden}} ocultos)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/ro-RO/core.js b/client/src/locales/ro-RO/core.js
index 4d8213f1..4e85d53d 100644
--- a/client/src/locales/ro-RO/core.js
+++ b/client/src/locales/ro-RO/core.js
@@ -101,6 +101,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'Culoarea',
comments: null,
@@ -123,6 +124,7 @@ export default {
date: 'Data',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Ștergeți atașamentul',
@@ -186,6 +188,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'Ore',
importBoard_title: 'Import Tabla',
invalidCurrentPassword: 'Parolă actuală nevalidă',
@@ -229,8 +232,10 @@ export default {
optional_inline: 'optional',
organization: 'Organizatia',
others: null,
+ passwordIsSet: null,
phone: 'Telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Preferințe',
pressPasteShortcutToAddAttachmentFromClipboard:
'Sfat: apăsați Ctrl-V (Cmd-V pe Mac) pentru a adăuga un atașament din clipboard.',
@@ -239,6 +244,7 @@ export default {
projectNotFound_title: 'Proiectul nu a fost găsit',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Eliminați Manager',
removeMember_title: 'Eliminați membru',
role: null,
@@ -265,6 +271,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: null,
stopwatch: 'Cronometru',
story: null,
@@ -276,6 +283,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment:
'Nu există nicio previzualizare disponibilă pentru acest atașament.',
time: 'Timp',
@@ -290,6 +298,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Acțiunile utilizatorului',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> a adăugat acest card în {{list}}',
@@ -427,6 +436,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Salveaza',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'Afișați toate atașamentele ({{hidden}} ascunse)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/ru-RU/core.js b/client/src/locales/ru-RU/core.js
index 01e25dfe..62b946ed 100644
--- a/client/src/locales/ru-RU/core.js
+++ b/client/src/locales/ru-RU/core.js
@@ -114,6 +114,7 @@ export default {
'Карточки в этом списке завершены и готовы к архивированию.',
cardsOnThisListAreReadyToBeWorkedOn: 'Карточки в этом списке готовы к работе.',
clickHereOrRefreshPageToUpdate: '<0>Нажмите здесь0> или обновите страницу для обновления.',
+ clientHostnameInEhlo: null,
closed: 'Закрыто',
color: 'Цвет',
comments: 'Комментарии',
@@ -136,6 +137,7 @@ export default {
date: 'Дата',
deactivateUser_title: 'Деактивировать пользователя',
defaultCardType_title: 'Тип карточки по умолчанию',
+ defaultFrom: null,
defaultView_title: 'Вид по умолчанию',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Удалите все доски, чтобы иметь возможность удалить этот проект',
@@ -200,6 +202,7 @@ export default {
grid: 'Сетка',
hideCompletedTasks: null,
hideFromProjectListAndFavorites: 'Скрыть из списка проектов и избранного',
+ host: null,
hours: 'Часы',
importBoard_title: 'Импорт доски',
invalidCurrentPassword: 'Неверный текущий пароль',
@@ -243,9 +246,11 @@ export default {
optional_inline: 'необязательно',
organization: 'Организация',
others: 'Другие',
+ passwordIsSet: null,
phone: 'Телефон',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA использует <1><0>Apprise0>1> для отправки уведомлений в более чем 100 популярных сервисов.',
+ port: null,
preferences: 'Предпочтения',
pressPasteShortcutToAddAttachmentFromClipboard:
'Совет: нажмите Ctrl-V (Cmd-V на Mac), чтобы добавить вложение из буфера обмена.',
@@ -254,6 +259,7 @@ export default {
projectNotFound_title: 'Проект не найден',
projectOwner: 'Владелец проекта',
referenceDataAndKnowledgeStorage: 'Хранение справочных данных и знаний',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Удалить менеджера',
removeMember_title: 'Удаление участника',
role: 'Роль',
@@ -280,6 +286,7 @@ export default {
shared: 'Общий',
sharedWithMe_title: 'Общий со мной',
showOnFrontOfCard: 'Показать на лицевой стороне карточки',
+ smtp: null,
sortList_title: 'Сортировка списка',
stopwatch: 'Секундомер',
story: 'История',
@@ -291,6 +298,7 @@ export default {
taskList_title: 'Список задач',
team: 'Команда',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Предпросмотр для этого вложения недоступен.',
time: 'Время',
title: 'Название',
@@ -304,6 +312,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Загруженные изображения',
url: null,
+ useSecureConnection: null,
userActions_title: 'Действия с пользователем',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> добавил(а) эту карточку в {{list}}',
@@ -441,6 +450,7 @@ export default {
restoreToList: 'Восстановить в {{list}}',
returnToBoard: 'Вернуться на доску',
save: 'Сохранить',
+ sendTestEmail: null,
showActive: 'Показать активные',
showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',
showCardsWithThisUser: 'Показать карточки с этим пользователем',
diff --git a/client/src/locales/sk-SK/core.js b/client/src/locales/sk-SK/core.js
index 0fab1f5b..de45ee96 100644
--- a/client/src/locales/sk-SK/core.js
+++ b/client/src/locales/sk-SK/core.js
@@ -101,6 +101,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'Farba',
comments: null,
@@ -123,6 +124,7 @@ export default {
date: 'Dátum',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Zmazať prílohu',
@@ -186,6 +188,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'Hodiny',
importBoard_title: null,
invalidCurrentPassword: 'Neplatné aktuálne heslo',
@@ -229,8 +232,10 @@ export default {
optional_inline: 'voliteľné',
organization: 'Spoločnosť',
others: null,
+ passwordIsSet: null,
phone: 'Telefón',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Voľby',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: stlačte Ctrl-V (Cmd-V na Mac) pre vloženie prílohy zo schránky.',
@@ -239,6 +244,7 @@ export default {
projectNotFound_title: 'Projekt neexistuje',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Odstrániť správcu',
removeMember_title: 'Odstrániť člena',
role: null,
@@ -265,6 +271,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: null,
stopwatch: 'Časovač',
story: null,
@@ -276,6 +283,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: null,
time: 'Čas',
title: 'Názov',
@@ -289,6 +297,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Akcie na používateľovi',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> pridal kartu do {{list}}',
@@ -426,6 +435,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Uložiť',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'Zozbraziť všetky prílohy ({{hidden}} skryté)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/sr-Cyrl-RS/core.js b/client/src/locales/sr-Cyrl-RS/core.js
index c5544ad7..3677bc05 100644
--- a/client/src/locales/sr-Cyrl-RS/core.js
+++ b/client/src/locales/sr-Cyrl-RS/core.js
@@ -101,6 +101,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'Боја',
comments: null,
@@ -123,6 +124,7 @@ export default {
date: 'Датум',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Обриши прилог',
@@ -186,6 +188,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'Сати',
importBoard_title: 'Увези таблу',
invalidCurrentPassword: 'Неисправна тренутна лозинка',
@@ -229,8 +232,10 @@ export default {
optional_inline: 'опционо',
organization: 'Организација',
others: null,
+ passwordIsSet: null,
phone: 'Телефон',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Својства',
pressPasteShortcutToAddAttachmentFromClipboard:
'Савет: притисни Ctrl-V (Cmd-V на Меку) да би додао прилог са бележнице.',
@@ -239,6 +244,7 @@ export default {
projectNotFound_title: 'Пројекат није пронађен',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Уклони руководиоца',
removeMember_title: 'Уклони члана',
role: null,
@@ -265,6 +271,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: 'Сложи списак',
stopwatch: 'Штоперица',
story: null,
@@ -276,6 +283,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Нема прегледа доступног за овај прилог.',
time: 'Време',
title: 'Наслов',
@@ -289,6 +297,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Корисничке радње',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> је додао ову картицу на {{list}}',
@@ -426,6 +435,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Сачувај',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'Прикажи све ({{hidden}} сакривене прилоге)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/sr-Latn-RS/core.js b/client/src/locales/sr-Latn-RS/core.js
index 81e21469..8e1a316e 100644
--- a/client/src/locales/sr-Latn-RS/core.js
+++ b/client/src/locales/sr-Latn-RS/core.js
@@ -98,6 +98,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'Boja',
comments: null,
@@ -120,6 +121,7 @@ export default {
date: 'Datum',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Obriši prilog',
@@ -183,6 +185,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'Sati',
importBoard_title: 'Uvezi tablu',
invalidCurrentPassword: 'Neispravna trenutna lozinka',
@@ -226,8 +229,10 @@ export default {
optional_inline: 'opciono',
organization: 'Organizacija',
others: null,
+ passwordIsSet: null,
phone: 'Telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Svojstva',
pressPasteShortcutToAddAttachmentFromClipboard:
'Savet: pritisni Ctrl-V (Cmd-V na Meku) da bi dodao prilog sa beležnice.',
@@ -236,6 +241,7 @@ export default {
projectNotFound_title: 'Projekat nije pronađen',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Ukloni rukovodioca',
removeMember_title: 'Ukloni člana',
role: null,
@@ -262,6 +268,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: 'Složi spisak',
stopwatch: 'Štoperica',
story: null,
@@ -273,6 +280,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Nema pregleda dostupnog za ovaj prilog.',
time: 'Vreme',
title: 'Naslov',
@@ -286,6 +294,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Korisničke radnje',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> je dodao ovu karticu na {{list}}',
@@ -423,6 +432,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Sačuvaj',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'Prikaži sve ({{hidden}} sakrivene priloge)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/sv-SE/core.js b/client/src/locales/sv-SE/core.js
index cf207e67..5d91193c 100644
--- a/client/src/locales/sv-SE/core.js
+++ b/client/src/locales/sv-SE/core.js
@@ -100,6 +100,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'Färg',
comments: null,
@@ -122,6 +123,7 @@ export default {
date: 'Datum',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Ta bort bilaga',
@@ -185,6 +187,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'Timmar',
importBoard_title: null,
invalidCurrentPassword: 'Ogiltigt nuvarande lösenord',
@@ -228,8 +231,10 @@ export default {
optional_inline: 'valfri',
organization: 'Organisation',
others: null,
+ passwordIsSet: null,
phone: 'Telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Preferenser',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tips: tryck på Ctrl-V (Cmd-V på Mac) för att lägga till en bilaga från urklipp.',
@@ -238,6 +243,7 @@ export default {
projectNotFound_title: 'Projekt hittades inte',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Ta bort projektledare',
removeMember_title: 'Ta bort medlem',
role: null,
@@ -264,6 +270,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: null,
stopwatch: 'Timer',
story: null,
@@ -275,6 +282,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: null,
time: 'Tid',
title: 'Titel',
@@ -288,6 +296,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Användaråtgärder',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> lade till detta kort i {{list}}',
@@ -425,6 +434,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Spara',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'Visa alla bilagor ({{hidden}} dolda)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/tr-TR/core.js b/client/src/locales/tr-TR/core.js
index df740bfa..81d0da6e 100644
--- a/client/src/locales/tr-TR/core.js
+++ b/client/src/locales/tr-TR/core.js
@@ -98,6 +98,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'renk',
comments: null,
@@ -120,6 +121,7 @@ export default {
date: 'tarih',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: 'Eki Sil',
@@ -183,6 +185,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'saat',
importBoard_title: null,
invalidCurrentPassword: 'Mevcut şifre yanlış',
@@ -226,8 +229,10 @@ export default {
optional_inline: 'İsteğe bağlı',
organization: 'Organizasyon',
others: null,
+ passwordIsSet: null,
phone: 'telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Tercihler',
pressPasteShortcutToAddAttachmentFromClipboard:
'İpucu: Panodan bir ek eklemek için CTRL-V ye (Macte Cmd-V) basın.',
@@ -236,6 +241,7 @@ export default {
projectNotFound_title: 'Proje bulunamadı',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Yöneticiyi Kaldır',
removeMember_title: 'Üyeyi Kaldır',
role: null,
@@ -262,6 +268,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: null,
stopwatch: 'kronometre',
story: null,
@@ -273,6 +280,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Bu ek için önizleme mevcut değil.',
time: 'zaman',
title: 'başlık',
@@ -286,6 +294,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Kullanıcı İşlemleri',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> bu kartı {{list}} listesine ekledi',
@@ -423,6 +432,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Kaydet',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: 'Tüm ekleri göster ({{hidden}} gizli)',
showCardsWithThisUser: null,
diff --git a/client/src/locales/uk-UA/core.js b/client/src/locales/uk-UA/core.js
index f7ab3d8b..791236a4 100644
--- a/client/src/locales/uk-UA/core.js
+++ b/client/src/locales/uk-UA/core.js
@@ -111,6 +111,7 @@ export default {
'Картки з цього списку завершені і готові до архівування.',
cardsOnThisListAreReadyToBeWorkedOn: 'Картки з цього списку готові до роботи.',
clickHereOrRefreshPageToUpdate: '<0>Натисніть тут0> або оновіть сторінку для оновлення.',
+ clientHostnameInEhlo: null,
closed: 'Закрито',
color: 'Колір',
comments: 'Коментарі',
@@ -134,6 +135,7 @@ export default {
date: 'Дата',
deactivateUser_title: 'Деактивувати користувача',
defaultCardType_title: 'Тип картки за замовчуванням',
+ defaultFrom: null,
defaultView_title: 'Вигляд за замовчуванням',
deleteAllBoardsToBeAbleToDeleteThisProject:
'Видаліть усі дошки, щоб мати змогу видалити цей проект',
@@ -198,6 +200,7 @@ export default {
grid: 'Сітка',
hideCompletedTasks: 'Приховати виконані завдання',
hideFromProjectListAndFavorites: 'Приховати зі списку проектів та обраного',
+ host: null,
hours: 'Години',
importBoard_title: 'Імпортувати Дошку',
invalidCurrentPassword: 'Невірний поточний пароль',
@@ -242,9 +245,11 @@ export default {
optional_inline: 'опціонально',
organization: 'Організація',
others: 'Інші',
+ passwordIsSet: null,
phone: 'Телефон',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA використовує <1><0>Apprise0>1> для надсилання сповіщень на понад 100 популярних сервісів.',
+ port: null,
preferences: 'Уподобання',
pressPasteShortcutToAddAttachmentFromClipboard:
'Порада: натисніть Ctrl-V (⌘V на Mac), щоб додати вкладення з буфера обміну.',
@@ -253,6 +258,7 @@ export default {
projectNotFound_title: 'Проект не знайдено',
projectOwner: 'Власник проекту',
referenceDataAndKnowledgeStorage: 'Довідкові дані та сховище знань.',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: 'Видалити Менеджера',
removeMember_title: 'Видалити Учасника',
role: 'Роль',
@@ -279,6 +285,7 @@ export default {
shared: 'Спільне',
sharedWithMe_title: 'Поділіться зі мною',
showOnFrontOfCard: 'Показати на лицьовій стороні картки',
+ smtp: null,
sortList_title: 'Сортування списку',
stopwatch: 'Секундомір',
story: 'Історія',
@@ -290,6 +297,7 @@ export default {
taskList_title: 'Список завдань',
team: 'Команда',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: 'Для цього вкладення немає доступного перегляду.',
time: 'Час',
title: 'Назва',
@@ -303,6 +311,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: 'Завантажені зображення',
url: 'Посилання',
+ useSecureConnection: null,
userActions_title: 'Дії користувача',
userAddedCardToList: '<0>{{user}}0> додав(ла) <2>{{card}}2> до {{list}}',
userAddedThisCardToList: '<0>{{user}}0> додав(ла) цю картку до {{list}}',
@@ -443,6 +452,7 @@ export default {
restoreToList: 'Відновити до {{list}}',
returnToBoard: 'Повернутися до дошки',
save: 'Зберегти',
+ sendTestEmail: null,
showActive: 'Показати активний',
showAllAttachments: 'Показати всі вкладення ({{hidden}} приховані)',
showCardsWithThisUser: 'Показати картки з цим користувачем',
diff --git a/client/src/locales/uz-UZ/core.js b/client/src/locales/uz-UZ/core.js
index 1310660e..8f753b38 100644
--- a/client/src/locales/uz-UZ/core.js
+++ b/client/src/locales/uz-UZ/core.js
@@ -97,6 +97,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: 'Rang',
comments: null,
@@ -119,6 +120,7 @@ export default {
date: 'Sana',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: "Ilovani O'chirish",
@@ -182,6 +184,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: 'Soat',
importBoard_title: null,
invalidCurrentPassword: 'Hozirgi parol xato',
@@ -225,8 +228,10 @@ export default {
optional_inline: 'ixtiyoriy',
organization: 'Tashkilot',
others: null,
+ passwordIsSet: null,
phone: 'Telefon',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: 'Afzalliklar',
pressPasteShortcutToAddAttachmentFromClipboard:
"Tip: Buferdan ilova qo'shish uchun Ctrl-V (Mac da Cmd-V) ni bosing.",
@@ -235,6 +240,7 @@ export default {
projectNotFound_title: 'Loyiha Topilmadi',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: "Boshqaruvchini O'chirish",
removeMember_title: "A'zoni O'chirish",
role: null,
@@ -261,6 +267,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: null,
stopwatch: 'Taymer',
story: null,
@@ -272,6 +279,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: null,
time: 'Vaqt',
title: 'Sarlavha',
@@ -285,6 +293,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: 'Foydalanuvchi Amallari',
userAddedCardToList: null,
userAddedThisCardToList: "Ushbu kartani {{list}} ga<0>{{user}}0> qo'shdi",
@@ -422,6 +431,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: 'Saqlash',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: "Barcha ilovalarni ko'rsatish ({{hidden}} yashirilgan)",
showCardsWithThisUser: null,
diff --git a/client/src/locales/zh-CN/core.js b/client/src/locales/zh-CN/core.js
index d946f916..5b75f221 100644
--- a/client/src/locales/zh-CN/core.js
+++ b/client/src/locales/zh-CN/core.js
@@ -98,6 +98,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: '此列表中的卡片已完成并准备归档',
cardsOnThisListAreReadyToBeWorkedOn: '此列表中的卡片已准备就绪可开始工作',
clickHereOrRefreshPageToUpdate: '<0>点击此处0>或刷新页面更新',
+ clientHostnameInEhlo: null,
closed: '已关闭',
color: '颜色',
comments: '评论',
@@ -120,6 +121,7 @@ export default {
date: '日期',
deactivateUser_title: '停用用户',
defaultCardType_title: '默认卡片类型',
+ defaultFrom: null,
defaultView_title: '默认视图',
deleteAllBoardsToBeAbleToDeleteThisProject: '删除所有面板后方可删除此项目',
deleteAttachment_title: '删除附件',
@@ -183,6 +185,7 @@ export default {
grid: '网格',
hideCompletedTasks: null,
hideFromProjectListAndFavorites: '从项目列表和收藏中隐藏',
+ host: null,
hours: '小时',
importBoard_title: '导入面板',
invalidCurrentPassword: '当前密码错误',
@@ -226,9 +229,11 @@ export default {
optional_inline: '可选的',
organization: '组织机构',
others: '其他',
+ passwordIsSet: null,
phone: '电话',
plankaUsesAppriseToSendNotificationsToOver100PopularServices:
'PLANKA使用<1><0>Apprise0>1>向100多个流行服务发送通知',
+ port: null,
preferences: '偏好',
pressPasteShortcutToAddAttachmentFromClipboard:
'提示: 按下 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件',
@@ -237,6 +242,7 @@ export default {
projectNotFound_title: '项目未找到',
projectOwner: '项目所有者',
referenceDataAndKnowledgeStorage: '参考数据和知识存储',
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: '删除管理员',
removeMember_title: '删除成员',
role: '角色',
@@ -263,6 +269,7 @@ export default {
shared: '共享',
sharedWithMe_title: '与我共享',
showOnFrontOfCard: '在卡片正面显示',
+ smtp: null,
sortList_title: '排序列表',
stopwatch: '计时器',
story: '故事',
@@ -274,6 +281,7 @@ export default {
taskList_title: '任务列表',
team: '团队',
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: '此附件无法预览',
time: '时间',
title: '标题',
@@ -287,6 +295,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: '已上传图片',
url: '网址',
+ useSecureConnection: null,
userActions_title: '用户操作',
userAddedCardToList: '<0>{{user}}0> 将 <2>{{card}}2> 添加到 {{list}}',
userAddedThisCardToList: '<0>{{user}}0> 向列表 {{list}} 添加了该卡片',
@@ -424,6 +433,7 @@ export default {
restoreToList: '恢复到 {{list}}',
returnToBoard: '返回面板',
save: '保存',
+ sendTestEmail: null,
showActive: '显示活跃',
showAllAttachments: '显示所有的附件 ({{hidden}} 隐藏)',
showCardsWithThisUser: '显示包含此用户的卡片',
diff --git a/client/src/locales/zh-TW/core.js b/client/src/locales/zh-TW/core.js
index 3e78e243..80f80186 100644
--- a/client/src/locales/zh-TW/core.js
+++ b/client/src/locales/zh-TW/core.js
@@ -95,6 +95,7 @@ export default {
cardsOnThisListAreCompleteAndReadyToBeArchived: null,
cardsOnThisListAreReadyToBeWorkedOn: null,
clickHereOrRefreshPageToUpdate: null,
+ clientHostnameInEhlo: null,
closed: null,
color: '顏色',
comments: null,
@@ -117,6 +118,7 @@ export default {
date: '日期',
deactivateUser_title: null,
defaultCardType_title: null,
+ defaultFrom: null,
defaultView_title: null,
deleteAllBoardsToBeAbleToDeleteThisProject: null,
deleteAttachment_title: '刪除附件',
@@ -180,6 +182,7 @@ export default {
grid: null,
hideCompletedTasks: null,
hideFromProjectListAndFavorites: null,
+ host: null,
hours: '小時',
importBoard_title: '導入看板',
invalidCurrentPassword: '當前密碼錯誤',
@@ -223,8 +226,10 @@ export default {
optional_inline: '可選的',
organization: '組織機構',
others: null,
+ passwordIsSet: null,
phone: '電話',
plankaUsesAppriseToSendNotificationsToOver100PopularServices: null,
+ port: null,
preferences: '偏好設定',
pressPasteShortcutToAddAttachmentFromClipboard:
'提示: 按下 Ctrl-V (Mac: Cmd-V) 從剪貼簿添加附件',
@@ -233,6 +238,7 @@ export default {
projectNotFound_title: '專案未找到',
projectOwner: null,
referenceDataAndKnowledgeStorage: null,
+ rejectUnauthorizedTlsCertificates: null,
removeManager_title: '刪除管理員',
removeMember_title: '刪除成員',
role: null,
@@ -259,6 +265,7 @@ export default {
shared: null,
sharedWithMe_title: null,
showOnFrontOfCard: null,
+ smtp: null,
sortList_title: null,
stopwatch: '碼表',
story: null,
@@ -270,6 +277,7 @@ export default {
taskList_title: null,
team: null,
terms: null,
+ testLog_title: null,
thereIsNoPreviewAvailableForThisAttachment: '此附件無法預覽',
time: '時間',
title: '標題',
@@ -283,6 +291,7 @@ export default {
uploadFailedNotEnoughStorageSpace: null,
uploadedImages: null,
url: null,
+ useSecureConnection: null,
userActions_title: '使用者操作',
userAddedCardToList: null,
userAddedThisCardToList: '<0>{{user}}0> 向列表 {{list}} 添加了該卡片',
@@ -419,6 +428,7 @@ export default {
restoreToList: null,
returnToBoard: null,
save: '保存',
+ sendTestEmail: null,
showActive: null,
showAllAttachments: '顯示所有附件 ({{hidden}} 隱藏)',
showCardsWithThisUser: null,
diff --git a/client/src/reducers/common.js b/client/src/reducers/common.js
index 44e62e73..2554117b 100644
--- a/client/src/reducers/common.js
+++ b/client/src/reducers/common.js
@@ -7,7 +7,7 @@ import ActionTypes from '../constants/ActionTypes';
const initialState = {
isInitializing: true,
- config: null,
+ bootstrap: null,
};
// eslint-disable-next-line default-param-last
@@ -16,13 +16,13 @@ export default (state = initialState, { type, payload }) => {
case ActionTypes.SOCKET_RECONNECT_HANDLE:
return {
...state,
- config: payload.config,
+ bootstrap: payload.bootstrap,
};
case ActionTypes.LOGIN_INITIALIZE:
return {
...state,
isInitializing: false,
- config: payload.config,
+ bootstrap: payload.bootstrap,
};
case ActionTypes.AUTHENTICATE__SUCCESS:
case ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS:
@@ -36,16 +36,16 @@ export default (state = initialState, { type, payload }) => {
...state,
isInitializing: false,
};
- case ActionTypes.CORE_INITIALIZE__CONFIG_FETCH:
+ case ActionTypes.CORE_INITIALIZE__BOOTSTRAP_FETCH:
return {
...state,
- config: payload.config,
+ bootstrap: payload.bootstrap,
};
case ActionTypes.USER_UPDATE_HANDLE:
- if (payload.config) {
+ if (payload.bootstrap) {
return {
...state,
- config: payload.config,
+ bootstrap: payload.bootstrap,
};
}
diff --git a/client/src/reducers/core.js b/client/src/reducers/core.js
index 9257afb3..1c18bbeb 100755
--- a/client/src/reducers/core.js
+++ b/client/src/reducers/core.js
@@ -15,6 +15,7 @@ const initialState = {
isFavoritesEnabled: false,
isEditModeEnabled: false,
modal: null,
+ config: null,
boardId: null,
cardId: null,
recentCardId: null,
@@ -70,13 +71,30 @@ export default (state = initialState, { type, payload }) => {
...state,
isContentFetching: true,
};
- case ActionTypes.CORE_INITIALIZE:
- return {
+ case ActionTypes.SOCKET_RECONNECT_HANDLE:
+ case ActionTypes.USER_UPDATE_HANDLE:
+ if (payload.config) {
+ return {
+ ...state,
+ config: payload.config,
+ };
+ }
+
+ return state;
+ case ActionTypes.CORE_INITIALIZE: {
+ const nextState = {
...state,
isFavoritesEnabled: payload.user.enableFavoritesByDefault,
homeView: payload.user.defaultHomeView,
projectsOrder: payload.user.defaultProjectsOrder,
};
+
+ if (payload.config) {
+ nextState.config = payload.config;
+ }
+
+ return nextState;
+ }
case ActionTypes.FAVORITES_TOGGLE:
return {
...state,
@@ -102,6 +120,27 @@ export default (state = initialState, { type, payload }) => {
...state,
modal: payload,
};
+ case ActionTypes.CONFIG_UPDATE:
+ return {
+ ...state,
+ config: {
+ ...state.config,
+ ...payload.data,
+ },
+ };
+ case ActionTypes.CONFIG_UPDATE__SUCCESS:
+ return {
+ ...state,
+ config: {
+ ...state.config,
+ ...payload.config,
+ },
+ };
+ case ActionTypes.CONFIG_UPDATE_HANDLE:
+ return {
+ ...state,
+ config: payload.config,
+ };
case ActionTypes.PROJECTS_SEARCH:
return {
...state,
diff --git a/client/src/reducers/ui/index.js b/client/src/reducers/ui/index.js
index b8815403..943fc994 100644
--- a/client/src/reducers/ui/index.js
+++ b/client/src/reducers/ui/index.js
@@ -8,9 +8,11 @@ import { combineReducers } from 'redux';
import authenticateForm from './authenticate-form';
import userCreateForm from './user-create-form';
import projectCreateForm from './project-create-form';
+import smtpTest from './smtp-test';
export default combineReducers({
authenticateForm,
userCreateForm,
projectCreateForm,
+ smtpTest,
});
diff --git a/client/src/reducers/ui/smtp-test.js b/client/src/reducers/ui/smtp-test.js
new file mode 100644
index 00000000..ec9749d1
--- /dev/null
+++ b/client/src/reducers/ui/smtp-test.js
@@ -0,0 +1,35 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+import ActionTypes from '../../constants/ActionTypes';
+
+const initialState = {
+ isLoading: false,
+ logs: null,
+ error: null,
+};
+
+// eslint-disable-next-line default-param-last
+export default (state = initialState, { type, payload }) => {
+ switch (type) {
+ case ActionTypes.SMTP_CONFIG_TEST:
+ return {
+ ...state,
+ isLoading: true,
+ };
+ case ActionTypes.SMTP_CONFIG_TEST__SUCCESS:
+ return {
+ ...initialState,
+ logs: payload.logs,
+ };
+ case ActionTypes.SMTP_CONFIG_TEST__FAILURE:
+ return {
+ ...initialState,
+ error: payload.error,
+ };
+ default:
+ return state;
+ }
+};
diff --git a/client/src/sagas/core/index.js b/client/src/sagas/core/index.js
index 2084e07d..d5d01755 100755
--- a/client/src/sagas/core/index.js
+++ b/client/src/sagas/core/index.js
@@ -21,14 +21,14 @@ export default function* coreSaga() {
yield take(ActionTypes.LOGOUT);
- const oidcConfig = yield select(selectors.selectOidcConfig);
+ const oidcBootstrap = yield select(selectors.selectOidcBootstrap);
- if (oidcConfig && oidcConfig.endSessionUrl !== null) {
+ if (oidcBootstrap && oidcBootstrap.endSessionUrl !== null) {
const currentUser = yield select(selectors.selectCurrentUser);
if (!currentUser || currentUser.isSsoUser) {
// Redirect the user to the IDP to log out.
- window.location.href = oidcConfig.endSessionUrl;
+ window.location.href = oidcBootstrap.endSessionUrl;
return;
}
}
diff --git a/client/src/sagas/core/requests/core.js b/client/src/sagas/core/requests/core.js
index 0a0c2fa5..a783d126 100644
--- a/client/src/sagas/core/requests/core.js
+++ b/client/src/sagas/core/requests/core.js
@@ -18,8 +18,11 @@ export function* fetchCore() {
included: { notificationServices: notificationServices1 },
} = yield call(request, api.getCurrentUser, true);
+ let config;
let webhooks;
+
if (user.role === UserRoles.ADMIN) {
+ ({ item: config } = yield call(request, api.getConfig));
({ items: webhooks } = yield call(request, api.getWebhooks));
}
@@ -105,6 +108,7 @@ export function* fetchCore() {
}
return {
+ config,
user,
board,
webhooks,
diff --git a/client/src/sagas/core/services/config.js b/client/src/sagas/core/services/config.js
new file mode 100644
index 00000000..57ccb9b9
--- /dev/null
+++ b/client/src/sagas/core/services/config.js
@@ -0,0 +1,49 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+import { call, put } from 'redux-saga/effects';
+
+import request from '../request';
+import actions from '../../../actions';
+import api from '../../../api';
+
+export function* updateConfig(data) {
+ yield put(actions.updateConfig(data));
+
+ let config;
+ try {
+ ({ item: config } = yield call(request, api.updateConfig, data));
+ } catch (error) {
+ yield put(actions.updateConfig.failure(error));
+ return;
+ }
+
+ yield put(actions.updateConfig.success(config));
+}
+
+export function* handleConfigUpdate(config) {
+ yield put(actions.handleConfigUpdate(config));
+}
+
+export function* testSmtpConfig() {
+ yield put(actions.testSmtpConfig());
+
+ let logs;
+ try {
+ ({
+ included: { logs },
+ } = yield call(request, api.testSmtpConfig));
+ } catch (error) {
+ yield put(actions.testSmtpConfig.failure(error));
+ }
+
+ yield put(actions.testSmtpConfig.success(logs));
+}
+
+export default {
+ updateConfig,
+ handleConfigUpdate,
+ testSmtpConfig,
+};
diff --git a/client/src/sagas/core/services/core.js b/client/src/sagas/core/services/core.js
index b9bd7643..39a6c800 100644
--- a/client/src/sagas/core/services/core.js
+++ b/client/src/sagas/core/services/core.js
@@ -14,11 +14,12 @@ import i18n from '../../../i18n';
import { removeAccessToken } from '../../../utils/access-token-storage';
export function* initializeCore() {
- const { item: config } = yield call(request, api.getConfig); // TODO: handle error
+ const { item: bootstrap } = yield call(request, api.getBootstrap); // TODO: handle error
- yield put(actions.initializeCore.fetchConfig(config));
+ yield put(actions.initializeCore.fetchBootstrap(bootstrap));
const {
+ config,
user,
board,
webhooks,
@@ -49,6 +50,7 @@ export function* initializeCore() {
yield put(
actions.initializeCore(
+ config,
user,
board,
webhooks,
diff --git a/client/src/sagas/core/services/index.js b/client/src/sagas/core/services/index.js
index f371841d..09d95ec4 100644
--- a/client/src/sagas/core/services/index.js
+++ b/client/src/sagas/core/services/index.js
@@ -7,6 +7,7 @@ import router from './router';
import socket from './socket';
import core from './core';
import modals from './modals';
+import config from './config';
import webhooks from './webhooks';
import users from './users';
import projects from './projects';
@@ -34,6 +35,7 @@ export default {
...socket,
...core,
...modals,
+ ...config,
...webhooks,
...users,
...projects,
diff --git a/client/src/sagas/core/services/socket.js b/client/src/sagas/core/services/socket.js
index a9b6f703..d27a9c7e 100644
--- a/client/src/sagas/core/services/socket.js
+++ b/client/src/sagas/core/services/socket.js
@@ -21,6 +21,7 @@ export function* handleSocketReconnect() {
yield put(actions.handleSocketReconnect.fetchCore(currentUserId, boardId));
+ let bootstrap;
let config;
let user;
let board;
@@ -47,7 +48,7 @@ export function* handleSocketReconnect() {
let notificationServices;
try {
- ({ item: config } = yield call(request, api.getConfig));
+ ({ item: bootstrap } = yield call(request, api.getBootstrap));
({
user,
@@ -80,6 +81,7 @@ export function* handleSocketReconnect() {
yield put(
actions.handleSocketReconnect(
+ bootstrap,
config,
user,
board,
diff --git a/client/src/sagas/core/services/users.js b/client/src/sagas/core/services/users.js
index 7a7dcacc..8c32fceb 100644
--- a/client/src/sagas/core/services/users.js
+++ b/client/src/sagas/core/services/users.js
@@ -68,6 +68,7 @@ export function* handleUserUpdate(user) {
const currentUser = yield select(selectors.selectCurrentUser);
const isCurrentUser = user.id === currentUser.id;
+ let bootstrap;
let config;
let board;
let webhooks;
@@ -102,6 +103,7 @@ export function* handleUserUpdate(user) {
({ items: users1 } = yield call(request, api.getUsers));
if (user.role === UserRoles.ADMIN) {
+ ({ item: bootstrap } = yield call(request, api.getBootstrap));
({ item: config } = yield call(request, api.getConfig));
({ items: webhooks } = yield call(request, api.getWebhooks));
@@ -164,6 +166,7 @@ export function* handleUserUpdate(user) {
user,
projectIds,
boardIds,
+ bootstrap,
config,
board,
webhooks,
@@ -248,10 +251,10 @@ export function* updateUserPassword(id, data) {
yield put(actions.updateUserPassword(id, data));
let user;
- let accessTokens;
+ let accessToken;
try {
- ({ item: user, included: { accessTokens } = {} } = yield call(
+ ({ item: user, included: { accessToken } = {} } = yield call(
request,
api.updateUserPassword,
id,
@@ -262,8 +265,6 @@ export function* updateUserPassword(id, data) {
return;
}
- const accessToken = accessTokens && accessTokens[0];
-
if (accessToken) {
yield call(setAccessToken, accessToken);
}
diff --git a/client/src/sagas/core/watchers/config.js b/client/src/sagas/core/watchers/config.js
new file mode 100644
index 00000000..9fce4c51
--- /dev/null
+++ b/client/src/sagas/core/watchers/config.js
@@ -0,0 +1,21 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+import { all, takeEvery } from 'redux-saga/effects';
+
+import services from '../services';
+import EntryActionTypes from '../../../constants/EntryActionTypes';
+
+export default function* configWatchers() {
+ yield all([
+ takeEvery(EntryActionTypes.CONFIG_UPDATE, ({ payload: { data } }) =>
+ services.updateConfig(data),
+ ),
+ takeEvery(EntryActionTypes.CONFIG_UPDATE_HANDLE, ({ payload: { config } }) =>
+ services.handleConfigUpdate(config),
+ ),
+ takeEvery(EntryActionTypes.SMTP_CONFIG_TEST, () => services.testSmtpConfig()),
+ ]);
+}
diff --git a/client/src/sagas/core/watchers/index.js b/client/src/sagas/core/watchers/index.js
index a6fd8fb3..3663c0a1 100755
--- a/client/src/sagas/core/watchers/index.js
+++ b/client/src/sagas/core/watchers/index.js
@@ -7,6 +7,7 @@ import router from './router';
import socket from './socket';
import core from './core';
import modals from './modals';
+import config from './config';
import webhooks from './webhooks';
import users from './users';
import projects from './projects';
@@ -34,6 +35,7 @@ export default [
socket,
core,
modals,
+ config,
webhooks,
users,
projects,
diff --git a/client/src/sagas/core/watchers/socket.js b/client/src/sagas/core/watchers/socket.js
index 4dd0b270..3f7b1f5b 100644
--- a/client/src/sagas/core/watchers/socket.js
+++ b/client/src/sagas/core/watchers/socket.js
@@ -29,6 +29,18 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleConfigUpdate(item));
};
+ const handleWebhookCreate = ({ item }) => {
+ emit(entryActions.handleWebhookCreate(item));
+ };
+
+ const handleWebhookUpdate = ({ item }) => {
+ emit(entryActions.handleWebhookUpdate(item));
+ };
+
+ const handleWebhookDelete = ({ item }) => {
+ emit(entryActions.handleWebhookDelete(item));
+ };
+
const handleUserCreate = ({ item }) => {
emit(entryActions.handleUserCreate(item));
};
@@ -280,6 +292,10 @@ const createSocketEventsChannel = () =>
socket.on('configUpdate', handleConfigUpdate);
+ socket.on('webhookCreate', handleWebhookCreate);
+ socket.on('webhookUpdate', handleWebhookUpdate);
+ socket.on('webhookDelete', handleWebhookDelete);
+
socket.on('userCreate', handleUserCreate);
socket.on('userUpdate', handleUserUpdate);
socket.on('userDelete', handleUserDelete);
@@ -370,6 +386,10 @@ const createSocketEventsChannel = () =>
socket.off('configUpdate', handleConfigUpdate);
+ socket.off('webhookCreate', handleWebhookCreate);
+ socket.off('webhookUpdate', handleWebhookUpdate);
+ socket.off('webhookDelete', handleWebhookDelete);
+
socket.off('userCreate', handleUserCreate);
socket.off('userUpdate', handleUserUpdate);
socket.off('userDelete', handleUserDelete);
diff --git a/client/src/sagas/login/services/login.js b/client/src/sagas/login/services/login.js
index 7a1561da..1dbdbf2e 100644
--- a/client/src/sagas/login/services/login.js
+++ b/client/src/sagas/login/services/login.js
@@ -16,9 +16,9 @@ import Paths from '../../../constants/Paths';
import AccessTokenSteps from '../../../constants/AccessTokenSteps';
export function* initializeLogin() {
- const { item: config } = yield call(api.getConfig); // TODO: handle error
+ const { item: bootstrap } = yield call(api.getBootstrap); // TODO: handle error
- yield put(actions.initializeLogin(config));
+ yield put(actions.initializeLogin(bootstrap));
}
export function* authenticate(data) {
@@ -42,7 +42,7 @@ export function* authenticate(data) {
}
export function* authenticateWithOidc() {
- const oidcConfig = yield select(selectors.selectOidcConfig);
+ const oidcBootstrap = yield select(selectors.selectOidcBootstrap);
const state = nanoid();
window.localStorage.setItem('oidc-state', state);
@@ -50,7 +50,7 @@ export function* authenticateWithOidc() {
const nonce = nanoid();
window.localStorage.setItem('oidc-nonce', nonce);
- let redirectUrl = `${oidcConfig.authorizationUrl}`;
+ let redirectUrl = `${oidcBootstrap.authorizationUrl}`;
redirectUrl += `&state=${encodeURIComponent(state)}`;
redirectUrl += `&nonce=${encodeURIComponent(nonce)}`;
diff --git a/client/src/sagas/login/services/router.js b/client/src/sagas/login/services/router.js
index 8f75e9b6..69667451 100644
--- a/client/src/sagas/login/services/router.js
+++ b/client/src/sagas/login/services/router.js
@@ -49,9 +49,9 @@ export function* handleLocationChange() {
switch (pathsMatch.pattern.path) {
case Paths.LOGIN: {
- const oidcConfig = yield select(selectors.selectOidcConfig);
+ const oidcBootstrap = yield select(selectors.selectOidcBootstrap);
- if (oidcConfig) {
+ if (oidcBootstrap) {
const params = new URLSearchParams(window.location.search);
if (params.has('authenticateWithOidc')) {
diff --git a/client/src/selectors/common.js b/client/src/selectors/common.js
index bd8e960a..90b0cd22 100644
--- a/client/src/selectors/common.js
+++ b/client/src/selectors/common.js
@@ -7,11 +7,11 @@ export const selectIsSocketDisconnected = ({ socket: { isDisconnected } }) => is
export const selectIsInitializing = ({ common: { isInitializing } }) => isInitializing;
-export const selectConfig = ({ common: { config } }) => config;
+export const selectBootstrap = ({ common: { bootstrap } }) => bootstrap;
-export const selectOidcConfig = (state) => selectConfig(state).oidc;
+export const selectOidcBootstrap = (state) => selectBootstrap(state).oidc;
-export const selectActiveUsersLimit = (state) => selectConfig(state).activeUsersLimit;
+export const selectActiveUsersLimit = (state) => selectBootstrap(state).activeUsersLimit;
export const selectAccessToken = ({ auth: { accessToken } }) => accessToken;
@@ -21,14 +21,17 @@ export const selectUserCreateForm = ({ ui: { userCreateForm } }) => userCreateFo
export const selectProjectCreateForm = ({ ui: { projectCreateForm } }) => projectCreateForm;
+export const selectSmtpTest = ({ ui: { smtpTest } }) => smtpTest;
+
export default {
selectIsSocketDisconnected,
selectIsInitializing,
- selectConfig,
- selectOidcConfig,
+ selectBootstrap,
+ selectOidcBootstrap,
selectActiveUsersLimit,
selectAccessToken,
selectAuthenticateForm,
selectUserCreateForm,
selectProjectCreateForm,
+ selectSmtpTest,
};
diff --git a/client/src/selectors/core.js b/client/src/selectors/core.js
index 39189a25..90b4f994 100644
--- a/client/src/selectors/core.js
+++ b/client/src/selectors/core.js
@@ -11,6 +11,8 @@ export const selectIsFavoritesEnabled = ({ core: { isFavoritesEnabled } }) => is
export const selectIsEditModeEnabled = ({ core: { isEditModeEnabled } }) => isEditModeEnabled;
+export const selectConfig = ({ core: { config } }) => config;
+
export const selectRecentCardId = ({ core: { recentCardId } }) => recentCardId;
export const selectPrevCardId = ({ core: { prevCardIds } }) => prevCardIds.at(-1);
@@ -29,6 +31,7 @@ export default {
selectIsLogouting,
selectIsFavoritesEnabled,
selectIsEditModeEnabled,
+ selectConfig,
selectRecentCardId,
selectPrevCardId,
selectHomeView,
diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml
index e9870f74..e462b281 100644
--- a/docker-compose-dev.yml
+++ b/docker-compose-dev.yml
@@ -76,14 +76,15 @@ services:
# - OIDC_ENFORCED=true
# Email Notifications (https://nodemailer.com/smtp/)
+ # These values override and disable configuration in the UI if set.
# - SMTP_HOST=
# - SMTP_PORT=587
# - SMTP_NAME=
# - SMTP_SECURE=true
+ # - SMTP_TLS_REJECT_UNAUTHORIZED=false
# - SMTP_USER=
# - SMTP_PASSWORD=
# - SMTP_FROM="Demo Demo"
- # - SMTP_TLS_REJECT_UNAUTHORIZED=false
# Using Gravatar directly exposes user IPs and hashed emails to a third party (GDPR risk).
# Use a proxy you control for privacy, or leave commented out or empty to disable.
diff --git a/docker-compose.yml b/docker-compose.yml
index f032ea4a..7bbe3079 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -94,16 +94,17 @@ services:
# - OIDC_ENFORCED=true
# Email Notifications (https://nodemailer.com/smtp/)
+ # These values override and disable configuration in the UI if set.
# - SMTP_HOST=
# - SMTP_PORT=587
# - SMTP_NAME=
# - SMTP_SECURE=true
+ # - SMTP_TLS_REJECT_UNAUTHORIZED=false
# - SMTP_USER=
# - SMTP_PASSWORD=
# Optionally store in secrets - then SMTP_PASSWORD should not be set
# - SMTP_PASSWORD__FILE=/run/secrets/smtp_password
# - SMTP_FROM="Demo Demo"
- # - SMTP_TLS_REJECT_UNAUTHORIZED=false
# Using Gravatar directly exposes user IPs and hashed emails to a third party (GDPR risk).
# Use a proxy you control for privacy, or leave commented out or empty to disable.
diff --git a/server/.env.sample b/server/.env.sample
index 0c9f3b86..9caafaa0 100644
--- a/server/.env.sample
+++ b/server/.env.sample
@@ -67,14 +67,15 @@ SECRET_KEY=notsecretkey
# OIDC_ENFORCED=true
# Email Notifications (https://nodemailer.com/smtp/)
+# These values override and disable configuration in the UI if set.
# SMTP_HOST=
# SMTP_PORT=587
# SMTP_NAME=
# SMTP_SECURE=true
+# SMTP_TLS_REJECT_UNAUTHORIZED=false
# SMTP_USER=
# SMTP_PASSWORD=
# SMTP_FROM="Demo Demo"
-# SMTP_TLS_REJECT_UNAUTHORIZED=false
# Using Gravatar directly exposes user IPs and hashed emails to a third party (GDPR risk).
# Use a proxy you control for privacy, or leave commented out or empty to disable.
diff --git a/server/api/controllers/bootstrap/show.js b/server/api/controllers/bootstrap/show.js
new file mode 100644
index 00000000..1a277dd5
--- /dev/null
+++ b/server/api/controllers/bootstrap/show.js
@@ -0,0 +1,72 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+/**
+ * @swagger
+ * /bootstrap:
+ * get:
+ * summary: Get application bootstrap
+ * description: Retrieves the application bootstrap.
+ * tags:
+ * - Bootstrap
+ * operationId: getBootstrap
+ * responses:
+ * 200:
+ * description: Bootstrap retrieved successfully
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required:
+ * - oidc
+ * - version
+ * properties:
+ * oidc:
+ * type: object
+ * required:
+ * - authorizationUrl
+ * - endSessionUrl
+ * - isEnforced
+ * nullable: true
+ * description: OpenID Connect configuration (null if not configured)
+ * properties:
+ * authorizationUrl:
+ * type: string
+ * format: uri
+ * description: OIDC authorization URL for initiating authentication
+ * example: https://oidc.example.com/auth
+ * endSessionUrl:
+ * type: string
+ * format: uri
+ * nullable: true
+ * description: OIDC end session URL for logout (null if not supported by provider)
+ * example: https://oidc.example.com/logout
+ * isEnforced:
+ * type: boolean
+ * description: Whether OIDC authentication is enforced (users must use OIDC to login)
+ * example: false
+ * activeUsersLimit:
+ * type: number
+ * nullable: true
+ * description: Maximum number of active users allowed (conditionally added for admins if configured)
+ * example: 100
+ * version:
+ * type: string
+ * description: Current version of the PLANKA application
+ * example: 2.0.0
+ * security: []
+ */
+
+module.exports = {
+ async fn() {
+ const { currentUser } = this.req;
+
+ const oidc = await sails.hooks.oidc.getBootstrap();
+
+ return {
+ item: sails.helpers.bootstrap.presentOne(oidc, currentUser),
+ };
+ },
+};
diff --git a/server/api/controllers/config/show.js b/server/api/controllers/config/show.js
index 93a02939..4dd2ba13 100644
--- a/server/api/controllers/config/show.js
+++ b/server/api/controllers/config/show.js
@@ -8,7 +8,7 @@
* /config:
* get:
* summary: Get application configuration
- * description: Retrieves the application configuration.
+ * description: Retrieves the application configuration. Requires admin privileges.
* tags:
* - Config
* operationId: getConfig
@@ -24,39 +24,14 @@
* properties:
* item:
* $ref: '#/components/schemas/Config'
- * security: []
*/
module.exports = {
async fn() {
- const { currentUser } = this.req;
-
- const oidcClient = await sails.hooks.oidc.getClient();
-
- let oidc = null;
- if (oidcClient) {
- const authorizationUrlParams = {
- scope: sails.config.custom.oidcScopes,
- };
-
- if (!sails.config.custom.oidcUseDefaultResponseMode) {
- authorizationUrlParams.response_mode = sails.config.custom.oidcResponseMode;
- }
-
- oidc = {
- authorizationUrl: oidcClient.authorizationUrl(authorizationUrlParams),
- endSessionUrl: oidcClient.issuer.end_session_endpoint ? oidcClient.endSessionUrl({}) : null,
- isEnforced: sails.config.custom.oidcEnforced,
- };
- }
+ const config = await Config.qm.getOneMain();
return {
- item: sails.helpers.config.presentOne(
- {
- oidc,
- },
- currentUser,
- ),
+ item: sails.helpers.config.presentOne(config),
};
},
};
diff --git a/server/api/controllers/config/test-smtp.js b/server/api/controllers/config/test-smtp.js
new file mode 100644
index 00000000..8e626d5a
--- /dev/null
+++ b/server/api/controllers/config/test-smtp.js
@@ -0,0 +1,117 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+/**
+ * @swagger
+ * /config/test-smtp:
+ * post:
+ * summary: Test SMTP configuration
+ * description: Sends a test email to verify the SMTP is configured correctly. Only available when SMTP is configured via the UI.
+ * tags:
+ * - Config
+ * operationId: testSmtpConfig
+ * responses:
+ * 200:
+ * description: Test email sent successfully
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required:
+ * - item
+ * properties:
+ * item:
+ * $ref: '#/components/schemas/Config'
+ * 401:
+ * $ref: '#/components/responses/Unauthorized'
+ * 403:
+ * $ref: '#/components/responses/Forbidden'
+ */
+
+const Errors = {
+ NOT_AVAILABLE: {
+ notAvailable: 'Not available',
+ },
+};
+
+module.exports = {
+ exits: {
+ notAvailable: {
+ responseType: 'forbidden',
+ },
+ },
+
+ async fn() {
+ const { currentUser } = this.req;
+
+ if (sails.config.custom.smtpHost) {
+ return Errors.NOT_AVAILABLE;
+ }
+
+ const { transporter, config } = await sails.helpers.utils.makeSmtpTransporter({
+ connectionTimeout: 5000,
+ greetingTimeout: 5000,
+ socketTimeout: 10000,
+ dnsTimeout: 3000,
+ });
+
+ if (!transporter) {
+ return Errors.NOT_AVAILABLE;
+ }
+
+ const logs = [];
+ try {
+ logs.push('📧 Sending test email...');
+ /* eslint-disable no-underscore-dangle */
+ const info = await transporter.sendMail({
+ to: currentUser.email,
+ subject: this.req.i18n.__('Test Title'),
+ text: this.req.i18n.__('This is a test text message!'),
+ html: this.req.i18n.__('This is a test html message!'),
+ });
+ /* eslint-enable no-underscore-dangle */
+ logs.push('✅ Email sent successfully!', '');
+
+ logs.push(`📬 Message ID: ${info.messageId}`);
+ if (info.response) {
+ logs.push(`📤 Server response: ${info.response.trim()}`);
+ }
+
+ logs.push('', '🎉 Your configuration is working correctly!');
+ } catch (error) {
+ logs.push('❌ Failed to send email!', '');
+
+ if (error.code) {
+ logs.push(`⚠️ Error code: ${error.code}`);
+ }
+ logs.push(`💬 Reason: ${error.message.trim()}`);
+
+ if (error.code === 'EDNS') {
+ logs.push('', '💡 Hint: Check your host setting.');
+ } else if (error.code === 'ETIMEDOUT') {
+ logs.push('', '💡 Hint: Check your host and port settings.');
+ } else if (error.code === 'EAUTH') {
+ logs.push('', '💡 Hint: Check your username and password.');
+ } else if (error.code === 'ESOCKET') {
+ if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) {
+ logs.push('', '💡 Hint: Check your host and port settings.');
+ } else if (error.message.includes('wrong version number')) {
+ logs.push('', '💡 Hint: Try toggling "Use secure connection".');
+ } else if (error.message.includes('certificate')) {
+ logs.push('', '💡 Hint: Try toggling "Reject unauthorized TLS certificates".');
+ }
+ }
+ } finally {
+ transporter.close();
+ }
+
+ return {
+ item: sails.helpers.config.presentOne(config),
+ included: {
+ logs,
+ },
+ };
+ },
+};
diff --git a/server/api/controllers/config/update.js b/server/api/controllers/config/update.js
new file mode 100644
index 00000000..48a9dcf0
--- /dev/null
+++ b/server/api/controllers/config/update.js
@@ -0,0 +1,151 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+/**
+ * @swagger
+ * /config:
+ * patch:
+ * summary: Update application configuration
+ * description: Updates the application configuration. Requires admin privileges.
+ * tags:
+ * - Config
+ * operationId: updateConfig
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * smtpHost:
+ * type: string
+ * maxLength: 256
+ * nullable: true
+ * description: Hostname or IP address of the SMTP server
+ * example: smtp.example.com
+ * smtpHost:
+ * type: number
+ * minimum: 0
+ * maximum: 65535
+ * nullable: true
+ * description: Port number of the SMTP server
+ * example: 587
+ * smtpName:
+ * type: string
+ * maxLength: 256
+ * nullable: true
+ * description: Client hostname used in the EHLO command for SMTP
+ * example: localhost
+ * smtpSecure:
+ * type: boolean
+ * description: Whether to use a secure connection for SMTP
+ * example: false
+ * smtpTlsRejectUnauthorized:
+ * type: boolean
+ * description: Whether to reject unauthorized or self-signed TLS certificates for SMTP connections
+ * example: true
+ * smtpUser:
+ * type: string
+ * maxLength: 256
+ * nullable: true
+ * description: Username for authenticating with the SMTP server
+ * example: no-reply@example.com
+ * smtpPassword:
+ * type: string
+ * maxLength: 256
+ * nullable: true
+ * description: Password for authenticating with the SMTP server
+ * example: SecurePassword123!
+ * smtpFrom:
+ * type: string
+ * maxLength: 256
+ * nullable: true
+ * description: Default "from" used for outgoing SMTP emails
+ * example: no-reply@example.com
+ * responses:
+ * 200:
+ * description: Configuration updated successfully
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required:
+ * - item
+ * properties:
+ * item:
+ * $ref: '#/components/schemas/Config'
+ */
+
+module.exports = {
+ inputs: {
+ smtpHost: {
+ type: 'string',
+ isNotEmptyString: true,
+ maxLength: 256,
+ allowNull: true,
+ },
+ smtpPort: {
+ type: 'number',
+ min: 0,
+ max: 65535,
+ allowNull: true,
+ },
+ smtpName: {
+ type: 'string',
+ isNotEmptyString: true,
+ maxLength: 256,
+ allowNull: true,
+ },
+ smtpSecure: {
+ type: 'boolean',
+ },
+ smtpTlsRejectUnauthorized: {
+ type: 'boolean',
+ },
+ smtpUser: {
+ type: 'string',
+ isNotEmptyString: true,
+ maxLength: 256,
+ allowNull: true,
+ },
+ smtpPassword: {
+ type: 'string',
+ isNotEmptyString: true,
+ maxLength: 256,
+ allowNull: true,
+ },
+ smtpFrom: {
+ type: 'string',
+ isNotEmptyString: true,
+ maxLength: 256,
+ allowNull: true,
+ },
+ },
+
+ async fn(inputs) {
+ const { currentUser } = this.req;
+
+ const values = _.pick(inputs, [
+ 'smtpHost',
+ 'smtpPort',
+ 'smtpName',
+ 'smtpSecure',
+ 'smtpTlsRejectUnauthorized',
+ 'smtpUser',
+ 'smtpPassword',
+ 'smtpFrom',
+ ]);
+
+ const config = await sails.helpers.config.updateMain.with({
+ values,
+ actorUser: currentUser,
+ request: this.req,
+ });
+
+ return {
+ item: sails.helpers.config.presentOne(config),
+ };
+ },
+};
diff --git a/server/api/controllers/notification-services/test.js b/server/api/controllers/notification-services/test.js
index 2361e73e..ab9e25cb 100644
--- a/server/api/controllers/notification-services/test.js
+++ b/server/api/controllers/notification-services/test.js
@@ -84,10 +84,17 @@ module.exports = {
}
}
- await sails.helpers.notificationServices.testOne.with({
- record: notificationService,
- i18n: this.req.i18n,
+ /* eslint-disable no-underscore-dangle */
+ await sails.helpers.utils.sendNotifications.with({
+ services: [_.pick(notificationService, ['url', 'format'])],
+ title: this.req.i18n.__('Test Title'),
+ bodyByFormat: {
+ text: this.req.i18n.__('This is a test text message!'),
+ markdown: this.req.i18n.__('This is a *test* **markdown** `message`!'),
+ html: this.req.i18n.__('This is a test html message!'),
+ },
});
+ /* eslint-enable no-underscore-dangle */
return {
item: notificationService,
diff --git a/server/api/controllers/users/update-password.js b/server/api/controllers/users/update-password.js
index e84b6715..f138e1a9 100644
--- a/server/api/controllers/users/update-password.js
+++ b/server/api/controllers/users/update-password.js
@@ -54,14 +54,12 @@
* included:
* type: object
* required:
- * - accessTokens
+ * - accessToken
* properties:
- * accessTokens:
- * type: array
+ * accessToken:
+ * type: string
* description: New acces tokens (when updating own password)
- * items:
- * type: string
- * example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ4...
+ * example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ4...
* 400:
* $ref: '#/components/responses/ValidationError'
* 401:
@@ -180,7 +178,7 @@ module.exports = {
return {
item: sails.helpers.users.presentOne(user, currentUser),
included: {
- accessTokens: [accessToken],
+ accessToken,
},
};
}
diff --git a/server/api/helpers/actions/create-one.js b/server/api/helpers/actions/create-one.js
index 12bda543..bf7bff92 100644
--- a/server/api/helpers/actions/create-one.js
+++ b/server/api/helpers/actions/create-one.js
@@ -154,9 +154,9 @@ module.exports = {
await sails.helpers.notifications.createOne.with({
values: {
action,
+ userId: action.data.user.id,
type: action.type,
data: action.data,
- userId: action.data.user.id,
creatorUser: values.user,
card: values.card,
},
@@ -179,24 +179,20 @@ module.exports = {
const notifiableUserIds = _.union(cardSubscriptionUserIds, boardSubscriptionUserIds);
- await Promise.all(
- notifiableUserIds.map((userId) =>
- sails.helpers.notifications.createOne.with({
- values: {
- userId,
- action,
- type: action.type,
- data: action.data,
- creatorUser: values.user,
- card: values.card,
- },
- project: inputs.project,
- board: inputs.board,
- list: inputs.list,
- webhooks: inputs.webhooks,
- }),
- ),
- );
+ await sails.helpers.notifications.createMany.with({
+ arrayOfValues: notifiableUserIds.map((userId) => ({
+ userId,
+ action,
+ type: action.type,
+ data: action.data,
+ creatorUser: values.user,
+ card: values.card,
+ })),
+ project: inputs.project,
+ board: inputs.board,
+ list: inputs.list,
+ webhooks: inputs.webhooks,
+ });
}
}
diff --git a/server/api/helpers/bootstrap/present-one.js b/server/api/helpers/bootstrap/present-one.js
new file mode 100644
index 00000000..a06269e2
--- /dev/null
+++ b/server/api/helpers/bootstrap/present-one.js
@@ -0,0 +1,29 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+module.exports = {
+ sync: true,
+
+ inputs: {
+ oidc: {
+ type: 'ref',
+ },
+ user: {
+ type: 'ref',
+ },
+ },
+
+ fn(inputs) {
+ const data = {
+ oidc: inputs.oidc,
+ version: sails.config.custom.version,
+ };
+ if (inputs.user && inputs.user.role === User.Roles.ADMIN) {
+ data.activeUsersLimit = sails.config.custom.activeUsersLimit;
+ }
+
+ return data;
+ },
+};
diff --git a/server/api/helpers/comments/create-one.js b/server/api/helpers/comments/create-one.js
index ffef0191..7f341453 100644
--- a/server/api/helpers/comments/create-one.js
+++ b/server/api/helpers/comments/create-one.js
@@ -124,29 +124,25 @@ module.exports = {
boardSubscriptionUserIds,
);
- await Promise.all(
- notifiableUserIds.map((userId) =>
- sails.helpers.notifications.createOne.with({
- webhooks,
- values: {
- userId,
- comment,
- type: mentionUserIdsSet.has(userId)
- ? Notification.Types.MENTION_IN_COMMENT
- : Notification.Types.COMMENT_CARD,
- data: {
- card: _.pick(values.card, ['name']),
- text: comment.text,
- },
- creatorUser: values.user,
- card: values.card,
- },
- project: inputs.project,
- board: inputs.board,
- list: inputs.list,
- }),
- ),
- );
+ await sails.helpers.notifications.createMany.with({
+ webhooks,
+ arrayOfValues: notifiableUserIds.map((userId) => ({
+ userId,
+ comment,
+ type: mentionUserIdsSet.has(userId)
+ ? Notification.Types.MENTION_IN_COMMENT
+ : Notification.Types.COMMENT_CARD,
+ data: {
+ card: _.pick(values.card, ['name']),
+ text: comment.text,
+ },
+ creatorUser: values.user,
+ card: values.card,
+ })),
+ project: inputs.project,
+ board: inputs.board,
+ list: inputs.list,
+ });
if (values.user.subscribeToCardWhenCommenting) {
let cardSubscription;
diff --git a/server/api/helpers/config/present-one.js b/server/api/helpers/config/present-one.js
index 3789ca17..1a9dc6fd 100644
--- a/server/api/helpers/config/present-one.js
+++ b/server/api/helpers/config/present-one.js
@@ -11,20 +11,17 @@ module.exports = {
type: 'ref',
required: true,
},
- user: {
- type: 'ref',
- },
},
fn(inputs) {
- const data = {
- ...inputs.record,
- version: sails.config.custom.version,
- };
- if (inputs.user && inputs.user.role === User.Roles.ADMIN) {
- data.activeUsersLimit = sails.config.custom.activeUsersLimit;
+ if (sails.config.custom.smtpHost) {
+ return _.omit(inputs.record, Config.SMTP_FIELD_NAMES);
}
- return data;
+ if (inputs.record.smtpPassword) {
+ return _.omit(inputs.record, 'smtpPassword');
+ }
+
+ return inputs.record;
},
};
diff --git a/server/api/helpers/config/update-main.js b/server/api/helpers/config/update-main.js
new file mode 100644
index 00000000..db8d755e
--- /dev/null
+++ b/server/api/helpers/config/update-main.js
@@ -0,0 +1,53 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+module.exports = {
+ inputs: {
+ values: {
+ type: 'json',
+ required: true,
+ },
+ actorUser: {
+ type: 'ref',
+ required: true,
+ },
+ request: {
+ type: 'ref',
+ },
+ },
+
+ async fn(inputs) {
+ const { values } = inputs;
+
+ const config = await Config.qm.updateOneMain(values);
+
+ const configRelatedUserIds = await sails.helpers.users.getAllIds(User.Roles.ADMIN);
+
+ configRelatedUserIds.forEach((userId) => {
+ sails.sockets.broadcast(
+ `user:${userId}`,
+ 'configUpdate',
+ {
+ item: sails.helpers.config.presentOne(config),
+ },
+ inputs.request,
+ );
+ });
+
+ const webhooks = await Webhook.qm.getAll();
+
+ // TODO: with prevData?
+ sails.helpers.utils.sendWebhooks.with({
+ webhooks,
+ event: Webhook.Events.CONFIG_UPDATE,
+ buildData: () => ({
+ item: sails.helpers.config.presentOne(config),
+ }),
+ user: inputs.actorUser,
+ });
+
+ return config;
+ },
+};
diff --git a/server/api/helpers/notification-services/test-one.js b/server/api/helpers/notification-services/test-one.js
deleted file mode 100644
index 4ee3f9ac..00000000
--- a/server/api/helpers/notification-services/test-one.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/*!
- * Copyright (c) 2024 PLANKA Software GmbH
- * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
- */
-
-/* eslint-disable no-underscore-dangle */
-
-module.exports = {
- inputs: {
- record: {
- type: 'ref',
- required: true,
- },
- i18n: {
- type: 'ref',
- required: true,
- },
- },
-
- async fn(inputs) {
- const { i18n } = inputs;
-
- await sails.helpers.utils.sendNotifications.with({
- services: [_.pick(inputs.record, ['url', 'format'])],
- title: i18n.__('Test Title'),
- bodyByFormat: {
- text: i18n.__('This is a test text message!'),
- markdown: i18n.__('This is a *test* **markdown** `message`!'),
- html: i18n.__('This is a test html message'),
- },
- });
- },
-};
diff --git a/server/api/helpers/notifications/create-many.js b/server/api/helpers/notifications/create-many.js
new file mode 100644
index 00000000..3af6dd16
--- /dev/null
+++ b/server/api/helpers/notifications/create-many.js
@@ -0,0 +1,358 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+const escapeMarkdown = require('escape-markdown');
+const escapeHtml = require('escape-html');
+
+const { mentionMarkupToText } = require('../../../utils/mentions');
+
+const buildTitle = (notification, t) => {
+ switch (notification.type) {
+ case Notification.Types.MOVE_CARD:
+ return t('Card Moved');
+ case Notification.Types.COMMENT_CARD:
+ return t('New Comment');
+ case Notification.Types.ADD_MEMBER_TO_CARD:
+ return t('You Were Added to Card');
+ case Notification.Types.MENTION_IN_COMMENT:
+ return t('You Were Mentioned in Comment');
+ default:
+ return null;
+ }
+};
+
+const buildBodyByFormat = (board, card, notification, actorUser, t) => {
+ const markdownCardLink = `[${escapeMarkdown(card.name)}](${sails.config.custom.baseUrl}/cards/${card.id})`;
+ const htmlCardLink = `${escapeHtml(card.name)}`;
+
+ switch (notification.type) {
+ case Notification.Types.MOVE_CARD: {
+ const fromListName = sails.helpers.lists.makeName(notification.data.fromList);
+ const toListName = sails.helpers.lists.makeName(notification.data.toList);
+
+ return {
+ text: t(
+ '%s moved %s from %s to %s on %s',
+ actorUser.name,
+ card.name,
+ fromListName,
+ toListName,
+ board.name,
+ ),
+ markdown: t(
+ '%s moved %s from %s to %s on %s',
+ escapeMarkdown(actorUser.name),
+ markdownCardLink,
+ `**${escapeMarkdown(fromListName)}**`,
+ `**${escapeMarkdown(toListName)}**`,
+ escapeMarkdown(board.name),
+ ),
+ html: t(
+ '%s moved %s from %s to %s on %s',
+ escapeHtml(actorUser.name),
+ htmlCardLink,
+ `${escapeHtml(fromListName)}`,
+ `${escapeHtml(toListName)}`,
+ escapeHtml(board.name),
+ ),
+ };
+ }
+ case Notification.Types.COMMENT_CARD: {
+ const commentText = _.truncate(mentionMarkupToText(notification.data.text));
+
+ return {
+ text: `${t(
+ '%s left a new comment to %s on %s',
+ actorUser.name,
+ card.name,
+ board.name,
+ )}:\n${commentText}`,
+ markdown: `${t(
+ '%s left a new comment to %s on %s',
+ escapeMarkdown(actorUser.name),
+ markdownCardLink,
+ escapeMarkdown(board.name),
+ )}:\n\n*${escapeMarkdown(commentText)}*`,
+ html: `${t(
+ '%s left a new comment to %s on %s',
+ escapeHtml(actorUser.name),
+ htmlCardLink,
+ escapeHtml(board.name),
+ )}:\n\n${escapeHtml(commentText)}`,
+ };
+ }
+ case Notification.Types.ADD_MEMBER_TO_CARD:
+ return {
+ text: t('%s added you to %s on %s', actorUser.name, card.name, board.name),
+ markdown: t(
+ '%s added you to %s on %s',
+ escapeMarkdown(actorUser.name),
+ markdownCardLink,
+ escapeMarkdown(board.name),
+ ),
+ html: t(
+ '%s added you to %s on %s',
+ escapeHtml(actorUser.name),
+ htmlCardLink,
+ escapeHtml(board.name),
+ ),
+ };
+ case Notification.Types.MENTION_IN_COMMENT: {
+ const commentText = _.truncate(mentionMarkupToText(notification.data.text));
+
+ return {
+ text: `${t(
+ '%s mentioned you in %s on %s',
+ actorUser.name,
+ card.name,
+ board.name,
+ )}:\n${commentText}`,
+ markdown: `${t(
+ '%s mentioned you in %s on %s',
+ escapeMarkdown(actorUser.name),
+ markdownCardLink,
+ escapeMarkdown(board.name),
+ )}:\n\n*${escapeMarkdown(commentText)}*`,
+ html: `${t(
+ '%s mentioned you in %s on %s',
+ escapeHtml(actorUser.name),
+ htmlCardLink,
+ escapeHtml(board.name),
+ )}:\n\n${escapeHtml(commentText)}`,
+ };
+ }
+ default:
+ return null;
+ }
+};
+
+const buildAndSendNotifications = async (services, board, card, notification, actorUser, t) => {
+ await sails.helpers.utils.sendNotifications(
+ services,
+ buildTitle(notification, t),
+ buildBodyByFormat(board, card, notification, actorUser, t),
+ );
+};
+
+// TODO: use templates (views) to build html
+const buildEmail = (board, card, notification, actorUser, notifiableUser, t) => {
+ const cardLink = `${escapeHtml(card.name)}`;
+ const boardLink = `${escapeHtml(board.name)}`;
+
+ let html;
+ switch (notification.type) {
+ case Notification.Types.MOVE_CARD: {
+ const fromListName = sails.helpers.lists.makeName(notification.data.fromList);
+ const toListName = sails.helpers.lists.makeName(notification.data.toList);
+
+ html = `${t(
+ '%s moved %s from %s to %s on %s',
+ escapeHtml(actorUser.name),
+ cardLink,
+ escapeHtml(fromListName),
+ escapeHtml(toListName),
+ boardLink,
+ )}
`;
+
+ break;
+ }
+ case Notification.Types.COMMENT_CARD:
+ html = `${t(
+ '%s left a new comment to %s on %s',
+ escapeHtml(actorUser.name),
+ cardLink,
+ boardLink,
+ )}
${escapeHtml(mentionMarkupToText(notification.data.text))}
`;
+
+ break;
+ case Notification.Types.ADD_MEMBER_TO_CARD:
+ html = `${t(
+ '%s added you to %s on %s',
+ escapeHtml(actorUser.name),
+ cardLink,
+ boardLink,
+ )}
`;
+
+ break;
+ case Notification.Types.MENTION_IN_COMMENT:
+ html = `${t(
+ '%s mentioned you in %s on %s',
+ escapeHtml(actorUser.name),
+ cardLink,
+ boardLink,
+ )}
${escapeHtml(mentionMarkupToText(notification.data.text))}
`;
+
+ break;
+ default:
+ return null; // TODO: throw error?
+ }
+
+ return {
+ html,
+ to: notifiableUser.email,
+ subject: buildTitle(notification, t),
+ };
+};
+
+const sendEmails = async (transporter, emails) => {
+ await Promise.all(
+ emails.map((email) =>
+ sails.helpers.utils.sendEmail.with({
+ ...email,
+ transporter,
+ }),
+ ),
+ );
+
+ transporter.close();
+};
+
+module.exports = {
+ inputs: {
+ arrayOfValues: {
+ type: 'ref',
+ required: true,
+ },
+ project: {
+ type: 'ref',
+ required: true,
+ },
+ board: {
+ type: 'ref',
+ required: true,
+ },
+ list: {
+ type: 'ref',
+ required: true,
+ },
+ webhooks: {
+ type: 'ref',
+ required: true,
+ },
+ },
+
+ async fn(inputs) {
+ const { arrayOfValues } = inputs;
+
+ const ids = await sails.helpers.utils.generateIds(arrayOfValues.length);
+ const valuesById = {};
+
+ const notifications = await Notification.qm.create(
+ arrayOfValues.map((values) => {
+ const id = ids.shift();
+
+ const isCommentRelated =
+ values.type === Notification.Types.COMMENT_CARD ||
+ values.type === Notification.Types.MENTION_IN_COMMENT;
+
+ const nextValues = {
+ ...values,
+ id,
+ creatorUserId: values.creatorUser.id,
+ boardId: values.card.boardId,
+ cardId: values.card.id,
+ };
+
+ if (isCommentRelated) {
+ nextValues.commentId = values.comment.id;
+ } else {
+ nextValues.actionId = values.action.id;
+ }
+
+ valuesById[id] = { ...nextValues }; // FIXME: hack
+ return nextValues;
+ }),
+ );
+
+ notifications.forEach((notification) => {
+ const values = valuesById[notification.id];
+
+ sails.sockets.broadcast(`user:${notification.userId}`, 'notificationCreate', {
+ item: notification,
+ included: {
+ users: [sails.helpers.users.presentOne(values.creatorUser, {})], // FIXME: hack
+ },
+ });
+
+ sails.helpers.utils.sendWebhooks.with({
+ webhooks: inputs.webhooks,
+ event: Webhook.Events.NOTIFICATION_CREATE,
+ buildData: () => ({
+ item: notification,
+ included: {
+ projects: [inputs.project],
+ boards: [inputs.board],
+ lists: [inputs.list],
+ cards: [values.card],
+ ...(notification.commentId
+ ? {
+ comments: [values.comment],
+ }
+ : {
+ actions: [values.action],
+ }),
+ },
+ }),
+ user: values.creatorUser,
+ });
+ });
+
+ const notificationsByUserId = _.groupBy(notifications, 'userId');
+ const userIds = Object.keys(notificationsByUserId);
+
+ const notificationServices = await NotificationService.qm.getByUserIds(userIds);
+ const { transporter } = await sails.helpers.utils.makeSmtpTransporter();
+
+ if (notificationServices.length > 0 || transporter) {
+ const users = await User.qm.getByIds(userIds);
+ const userById = _.keyBy(users, 'id');
+
+ const notificationServicesByUserId = _.groupBy(notificationServices, 'userId');
+
+ Object.keys(notificationsByUserId).forEach(async (userId) => {
+ const notifiableUser = userById[userId];
+ const t = sails.helpers.utils.makeTranslator(notifiableUser.language);
+
+ const emails = notificationsByUserId[userId].flatMap((notification) => {
+ const values = valuesById[notification.id];
+
+ if (notificationServicesByUserId[userId]) {
+ const services = notificationServicesByUserId[userId].map((notificationService) =>
+ _.pick(notificationService, ['url', 'format']),
+ );
+
+ buildAndSendNotifications(
+ services,
+ inputs.board,
+ values.card,
+ notification,
+ values.creatorUser,
+ t,
+ );
+ }
+
+ if (transporter) {
+ return buildEmail(
+ inputs.board,
+ values.card,
+ notification,
+ values.creatorUser,
+ notifiableUser,
+ t,
+ );
+ }
+
+ return [];
+ });
+
+ if (emails.length > 0) {
+ sendEmails(transporter, emails);
+ }
+ });
+ }
+
+ return notifications;
+ },
+};
diff --git a/server/api/helpers/notifications/create-one.js b/server/api/helpers/notifications/create-one.js
index c273e074..25505492 100644
--- a/server/api/helpers/notifications/create-one.js
+++ b/server/api/helpers/notifications/create-one.js
@@ -137,7 +137,15 @@ const buildAndSendNotifications = async (services, board, card, notification, ac
};
// TODO: use templates (views) to build html
-const buildAndSendEmail = async (board, card, notification, actorUser, notifiableUser, t) => {
+const buildAndSendEmail = async (
+ transporter,
+ board,
+ card,
+ notification,
+ actorUser,
+ notifiableUser,
+ t,
+) => {
const cardLink = `${escapeHtml(card.name)}`;
const boardLink = `${escapeHtml(board.name)}`;
@@ -164,7 +172,7 @@ const buildAndSendEmail = async (board, card, notification, actorUser, notifiabl
escapeHtml(actorUser.name),
cardLink,
boardLink,
- )}
${escapeHtml(notification.data.text)}
`;
+ )}${escapeHtml(mentionMarkupToText(notification.data.text))}
`;
break;
case Notification.Types.ADD_MEMBER_TO_CARD:
@@ -182,7 +190,7 @@ const buildAndSendEmail = async (board, card, notification, actorUser, notifiabl
escapeHtml(actorUser.name),
cardLink,
boardLink,
- )}${escapeHtml(notification.data.text)}
`;
+ )}${escapeHtml(mentionMarkupToText(notification.data.text))}
`;
break;
default:
@@ -190,10 +198,13 @@ const buildAndSendEmail = async (board, card, notification, actorUser, notifiabl
}
await sails.helpers.utils.sendEmail.with({
+ transporter,
html,
to: notifiableUser.email,
subject: buildTitle(notification, t),
});
+
+ transporter.close();
};
module.exports = {
@@ -223,10 +234,6 @@ module.exports = {
async fn(inputs) {
const { values } = inputs;
- if (values.user) {
- values.userId = values.user.id;
- }
-
const isCommentRelated =
values.type === Notification.Types.COMMENT_CARD ||
values.type === Notification.Types.MENTION_IN_COMMENT;
@@ -274,9 +281,10 @@ module.exports = {
});
const notificationServices = await NotificationService.qm.getByUserId(notification.userId);
+ const { transporter } = await sails.helpers.utils.makeSmtpTransporter();
- if (notificationServices.length > 0 || sails.hooks.smtp.isEnabled()) {
- const notifiableUser = values.user || (await User.qm.getOneById(notification.userId));
+ if (notificationServices.length > 0 || transporter) {
+ const notifiableUser = await User.qm.getOneById(notification.userId);
const t = sails.helpers.utils.makeTranslator(notifiableUser.language);
if (notificationServices.length > 0) {
@@ -294,8 +302,9 @@ module.exports = {
);
}
- if (sails.hooks.smtp.isEnabled()) {
+ if (transporter) {
buildAndSendEmail(
+ transporter,
inputs.board,
values.card,
notification,
diff --git a/server/api/helpers/utils/make-smtp-transporter.js b/server/api/helpers/utils/make-smtp-transporter.js
new file mode 100644
index 00000000..86e89aca
--- /dev/null
+++ b/server/api/helpers/utils/make-smtp-transporter.js
@@ -0,0 +1,61 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+const nodemailer = require('nodemailer');
+
+module.exports = {
+ inputs: {
+ defaultOptions: {
+ type: 'json',
+ },
+ },
+
+ async fn(inputs) {
+ let config;
+ let sourceConfig;
+
+ if (sails.config.custom.smtpHost) {
+ sourceConfig = sails.config.custom;
+ } else {
+ config = await Config.qm.getOneMain();
+
+ if (config.smtpHost) {
+ sourceConfig = config;
+ }
+ }
+
+ if (!sourceConfig) {
+ return {
+ config,
+ transporter: null,
+ };
+ }
+
+ const transporter = nodemailer.createTransport(
+ {
+ ...inputs.defaultOptions,
+ host: sourceConfig.smtpHost,
+ port: sourceConfig.smtpPort,
+ name: sourceConfig.smtpName,
+ secure: sourceConfig.smtpSecure,
+ auth: sourceConfig.smtpUser && {
+ user: sourceConfig.smtpUser,
+ pass: sourceConfig.smtpPassword,
+ },
+ tls: {
+ rejectUnauthorized: sourceConfig.smtpTlsRejectUnauthorized,
+ },
+ },
+ {
+ from: sourceConfig.smtpFrom,
+ },
+ );
+
+ return {
+ transporter,
+ config,
+ };
+ },
+};
diff --git a/server/api/helpers/utils/send-email.js b/server/api/helpers/utils/send-email.js
index a8e53f44..4bac9d70 100644
--- a/server/api/helpers/utils/send-email.js
+++ b/server/api/helpers/utils/send-email.js
@@ -5,6 +5,10 @@
module.exports = {
inputs: {
+ transporter: {
+ type: 'ref',
+ required: true,
+ },
to: {
type: 'string',
required: true,
@@ -20,13 +24,8 @@ module.exports = {
},
async fn(inputs) {
- const transporter = sails.hooks.smtp.getTransporter(); // TODO: check if enabled?
-
try {
- const info = await transporter.sendMail({
- ...inputs,
- from: sails.config.custom.smtpFrom,
- });
+ const info = await inputs.transporter.sendMail(inputs);
sails.log.info(`Email sent: ${info.messageId}`);
} catch (error) {
diff --git a/server/api/hooks/oidc/index.js b/server/api/hooks/oidc/index.js
index 37c21e65..accc6737 100644
--- a/server/api/hooks/oidc/index.js
+++ b/server/api/hooks/oidc/index.js
@@ -59,6 +59,28 @@ module.exports = function defineOidcHook(sails) {
return client;
},
+ async getBootstrap() {
+ const instance = await this.getClient();
+
+ if (!instance) {
+ return null;
+ }
+
+ const authorizationUrlParams = {
+ scope: sails.config.custom.oidcScopes,
+ };
+
+ if (!sails.config.custom.oidcUseDefaultResponseMode) {
+ authorizationUrlParams.response_mode = sails.config.custom.oidcResponseMode;
+ }
+
+ return {
+ authorizationUrl: instance.authorizationUrl(authorizationUrlParams),
+ endSessionUrl: instance.issuer.end_session_endpoint ? instance.endSessionUrl({}) : null,
+ isEnforced: sails.config.custom.oidcEnforced,
+ };
+ },
+
isEnabled() {
return !!sails.config.custom.oidcIssuer;
},
diff --git a/server/api/hooks/query-methods/models/Notification.js b/server/api/hooks/query-methods/models/Notification.js
index 0eb54f17..1380feea 100644
--- a/server/api/hooks/query-methods/models/Notification.js
+++ b/server/api/hooks/query-methods/models/Notification.js
@@ -9,6 +9,37 @@ const defaultFind = (criteria) => Notification.find(criteria).sort('id DESC');
/* Query methods */
+const create = (arrayOfValues) =>
+ sails.getDatastore().transaction(async (db) => {
+ const notifications = await Notification.createEach(arrayOfValues).fetch().usingConnection(db);
+ const userIds = sails.helpers.utils.mapRecords(notifications, 'userId', true, true);
+
+ if (userIds.length > 0) {
+ const queryValues = [];
+ const inValues = userIds.map((userId) => {
+ queryValues.push(userId);
+ return `$${queryValues.length}`;
+ });
+
+ queryValues.push(LIMIT);
+
+ const query = `
+ WITH exceeded_notification AS (
+ SELECT id, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY id DESC) AS rank
+ FROM notification
+ WHERE user_id IN (${inValues.join(', ')}) AND is_read = FALSE
+ )
+ UPDATE notification
+ SET is_read = TRUE
+ WHERE id IN (SELECT id FROM exceeded_notification WHERE rank > $${queryValues.length})
+ `;
+
+ await sails.sendNativeQuery(query, queryValues).usingConnection(db);
+ }
+
+ return notifications;
+ });
+
const createOne = (values) => {
if (values.userId) {
return sails.getDatastore().transaction(async (db) => {
@@ -26,7 +57,7 @@ const createOne = (values) => {
)
UPDATE notification
SET is_read = TRUE
- WHERE id in (SELECT id FROM exceeded_notification)
+ WHERE id IN (SELECT id FROM exceeded_notification)
`;
await sails.sendNativeQuery(query, [values.userId, LIMIT]).usingConnection(db);
@@ -66,6 +97,7 @@ const updateOne = (criteria, values) => Notification.updateOne(criteria).set({ .
const delete_ = (criteria) => Notification.destroy(criteria).fetch();
module.exports = {
+ create,
createOne,
getByIds,
getUnreadByUserId,
diff --git a/server/api/hooks/query-methods/models/NotificationService.js b/server/api/hooks/query-methods/models/NotificationService.js
index cbb99644..0b5c2881 100644
--- a/server/api/hooks/query-methods/models/NotificationService.js
+++ b/server/api/hooks/query-methods/models/NotificationService.js
@@ -14,6 +14,11 @@ const getByUserId = (userId) =>
userId,
});
+const getByUserIds = (userIds) =>
+ defaultFind({
+ userId: userIds,
+ });
+
const getByBoardId = (boardId) =>
defaultFind({
boardId,
@@ -36,6 +41,7 @@ const deleteOne = (criteria) => NotificationService.destroyOne(criteria);
module.exports = {
createOne,
getByUserId,
+ getByUserIds,
getByBoardId,
getByBoardIds,
getOneById,
diff --git a/server/api/hooks/smtp/index.js b/server/api/hooks/smtp/index.js
deleted file mode 100644
index 5bb3e8ec..00000000
--- a/server/api/hooks/smtp/index.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/*!
- * Copyright (c) 2024 PLANKA Software GmbH
- * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
- */
-
-/**
- * smtp hook
- *
- * @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,
- * and/or initialization logic.
- * @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
- */
-
-const nodemailer = require('nodemailer');
-
-module.exports = function defineSmtpHook(sails) {
- let transporter = null;
-
- return {
- /**
- * Runs when this Sails app loads/lifts.
- */
-
- async initialize() {
- if (!this.isEnabled()) {
- return;
- }
-
- sails.log.info('Initializing custom hook (`smtp`)');
-
- transporter = nodemailer.createTransport({
- pool: true,
- host: sails.config.custom.smtpHost,
- port: sails.config.custom.smtpPort,
- name: sails.config.custom.smtpName,
- secure: sails.config.custom.smtpSecure,
- auth: sails.config.custom.smtpUser && {
- user: sails.config.custom.smtpUser,
- pass: sails.config.custom.smtpPassword,
- },
- tls: {
- rejectUnauthorized: sails.config.custom.smtpTlsRejectUnauthorized,
- },
- });
- },
-
- getTransporter() {
- return transporter;
- },
-
- isEnabled() {
- return !!sails.config.custom.smtpHost;
- },
- };
-};
diff --git a/server/api/models/Config.js b/server/api/models/Config.js
index 131779ee..1e7f3a20 100644
--- a/server/api/models/Config.js
+++ b/server/api/models/Config.js
@@ -17,54 +17,131 @@
* Config:
* type: object
* required:
- * - version
- * - oidc
+ * - id
+ * - isInitialized
* properties:
- * version:
+ * id:
* type: string
- * description: Current version of the PLANKA application
- * example: 2.0.0
- * activeUsersLimit:
+ * description: Unique identifier for the config (always set to '1')
+ * example: 1
+ * smtpHost:
+ * type: string
+ * nullable: true
+ * description: Hostname or IP address of the SMTP server
+ * example: smtp.example.com
+ * smtpPort:
* type: number
* nullable: true
- * description: Maximum number of active users allowed (conditionally added for admins if configured)
- * example: 100
- * oidc:
- * type: object
- * required:
- * - authorizationUrl
- * - endSessionUrl
- * - isEnforced
+ * description: Port number of the SMTP server
+ * example: 587
+ * smtpName:
+ * type: string
* nullable: true
- * description: OpenID Connect configuration (null if not configured)
- * properties:
- * authorizationUrl:
- * type: string
- * format: uri
- * description: OIDC authorization URL for initiating authentication
- * example: https://oidc.example.com/auth
- * endSessionUrl:
- * type: string
- * format: uri
- * nullable: true
- * description: OIDC end session URL for logout (null if not supported by provider)
- * example: https://oidc.example.com/logout
- * isEnforced:
- * type: boolean
- * description: Whether OIDC authentication is enforced (users must use OIDC to login)
- * example: false
+ * description: Client hostname used in the EHLO command for SMTP
+ * example: localhost
+ * smtpSecure:
+ * type: boolean
+ * description: Whether to use a secure connection for SMTP
+ * example: false
+ * smtpTlsRejectUnauthorized:
+ * type: boolean
+ * description: Whether to reject unauthorized or self-signed TLS certificates for SMTP connections
+ * example: true
+ * smtpUser:
+ * type: string
+ * nullable: true
+ * description: Username for authenticating with the SMTP server
+ * example: no-reply@example.com
+ * smtpPassword:
+ * type: string
+ * nullable: true
+ * description: Password for authenticating with the SMTP server
+ * example: SecurePassword123!
+ * smtpFrom:
+ * type: string
+ * nullable: true
+ * description: Default "from" used for outgoing SMTP emails
+ * example: no-reply@example.com
+ * isInitialized:
+ * type: boolean
+ * description: Whether the PLANKA instance has been initialized
+ * example: true
+ * createdAt:
+ * type: string
+ * format: date-time
+ * nullable: true
+ * description: When the config was created
+ * example: 2024-01-01T00:00:00.000Z
+ * updatedAt:
+ * type: string
+ * format: date-time
+ * nullable: true
+ * description: When the config was last updated
+ * example: 2024-01-01T00:00:00.000Z
*/
const MAIN_ID = '1';
+const SMTP_FIELD_NAMES = [
+ 'smtpHost',
+ 'smtpPort',
+ 'smtpName',
+ 'smtpSecure',
+ 'smtpTlsRejectUnauthorized',
+ 'smtpUser',
+ 'smtpPassword',
+ 'smtpFrom',
+];
+
module.exports = {
MAIN_ID,
+ SMTP_FIELD_NAMES,
attributes: {
// ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
+ smtpHost: {
+ type: 'string',
+ allowNull: true,
+ columnName: 'smtp_host',
+ },
+ smtpPort: {
+ type: 'number',
+ allowNull: true,
+ columnName: 'smtp_port',
+ },
+ smtpName: {
+ type: 'string',
+ allowNull: true,
+ columnName: 'smtp_name',
+ },
+ smtpSecure: {
+ type: 'boolean',
+ required: true,
+ columnName: 'smtp_secure',
+ },
+ smtpTlsRejectUnauthorized: {
+ type: 'boolean',
+ required: true,
+ columnName: 'smtp_tls_reject_unauthorized',
+ },
+ smtpUser: {
+ type: 'string',
+ allowNull: true,
+ columnName: 'smtp_user',
+ },
+ smtpPassword: {
+ type: 'string',
+ allowNull: true,
+ columnName: 'smtp_password',
+ },
+ smtpFrom: {
+ type: 'string',
+ allowNull: true,
+ columnName: 'smtp_from',
+ },
isInitialized: {
type: 'boolean',
required: true,
diff --git a/server/api/models/Webhook.js b/server/api/models/Webhook.js
index 860da9d8..4e73b9a6 100644
--- a/server/api/models/Webhook.js
+++ b/server/api/models/Webhook.js
@@ -107,6 +107,8 @@ const Events = {
COMMENT_UPDATE: 'commentUpdate',
COMMENT_DELETE: 'commentDelete',
+ CONFIG_UPDATE: 'configUpdate',
+
CUSTOM_FIELD_CREATE: 'customFieldCreate',
CUSTOM_FIELD_UPDATE: 'customFieldUpdate',
CUSTOM_FIELD_DELETE: 'customFieldDelete',
diff --git a/server/config/custom.js b/server/config/custom.js
index 29e6533d..5380d97b 100644
--- a/server/config/custom.js
+++ b/server/config/custom.js
@@ -102,10 +102,10 @@ module.exports.custom = {
smtpPort: process.env.SMTP_PORT || 587,
smtpName: process.env.SMTP_NAME,
smtpSecure: process.env.SMTP_SECURE === 'true',
+ smtpTlsRejectUnauthorized: process.env.SMTP_TLS_REJECT_UNAUTHORIZED !== 'false',
smtpUser: process.env.SMTP_USER,
smtpPassword: process.env.SMTP_PASSWORD,
smtpFrom: process.env.SMTP_FROM,
- smtpTlsRejectUnauthorized: process.env.SMTP_TLS_REJECT_UNAUTHORIZED !== 'false',
gravatarBaseUrl: process.env.GRAVATAR_BASE_URL,
};
diff --git a/server/config/locales/de-DE.json b/server/config/locales/de-DE.json
index ceb546df..d54252ef 100644
--- a/server/config/locales/de-DE.json
+++ b/server/config/locales/de-DE.json
@@ -5,7 +5,7 @@
"Test Title": "Testtitel",
"This is a test text message!": "Dies ist eine Test-Textnachricht!",
"This is a *test* **markdown** `message`!": "Dies ist eine *Test*-**Markdown**-`Nachricht`!",
- "This is a test html message": "Dies ist eine Test-HTML-Nachricht",
+ "This is a test html message!": "Dies ist eine Test-HTML-Nachricht!",
"You Were Added to Card": "Sie wurden zur Karte hinzugefügt",
"You Were Mentioned in Comment": "Sie wurden in einem Kommentar erwähnt",
"%s added you to %s on %s": "%s hat Sie zu %s am %s hinzugefügt",
diff --git a/server/config/locales/el-GR.json b/server/config/locales/el-GR.json
index 1293356b..a47964a2 100644
--- a/server/config/locales/el-GR.json
+++ b/server/config/locales/el-GR.json
@@ -5,7 +5,7 @@
"Test Title": "Τίτλος δοκιμής",
"This is a test text message!": "Αυτό είναι ένα δοκιμαστικό μήνυμα!",
"This is a *test* **markdown** `message`!": "Αυτό είναι ένα *δοκιμαστικό* **markdown** `μήνυμα`!",
- "This is a test html message": "Αυτό είναι ένα δοκιμαστικό html μήνυμα",
+ "This is a test html message!": "Αυτό είναι ένα δοκιμαστικό html μήνυμα!",
"You Were Added to Card": "Προστέθηκες στην κάρτα",
"You Were Mentioned in Comment": "Αναφέρθηκες σε σχόλιο",
"%s added you to %s on %s": "%s σε πρόσθεσε στο %s στο %s",
diff --git a/server/config/locales/en-GB.json b/server/config/locales/en-GB.json
index 441064ca..935cf7a9 100644
--- a/server/config/locales/en-GB.json
+++ b/server/config/locales/en-GB.json
@@ -5,7 +5,7 @@
"Test Title": "Test Title",
"This is a test text message!": "This is a test text message!",
"This is a *test* **markdown** `message`!": "This is a *test* **markdown** `message`!",
- "This is a test html message": "This is a test html message",
+ "This is a test html message!": "This is a test html message!",
"You Were Added to Card": "You Were Added to Card",
"You Were Mentioned in Comment": "You Were Mentioned in Comment",
"%s added you to %s on %s": "%s added you to %s on %s",
diff --git a/server/config/locales/en-US.json b/server/config/locales/en-US.json
index 6e1f2026..8c2e8d73 100644
--- a/server/config/locales/en-US.json
+++ b/server/config/locales/en-US.json
@@ -5,7 +5,7 @@
"Test Title": "Test Title",
"This is a test text message!": "This is a test text message!",
"This is a *test* **markdown** `message`!": "This is a *test* **markdown** `message`!",
- "This is a test html message": "This is a test html message",
+ "This is a test html message!": "This is a test html message!",
"You Were Added to Card": "You Were Added to Card",
"You Were Mentioned in Comment": "You Were Mentioned in Comment",
"%s added you to %s on %s": "%s added you to %s on %s",
diff --git a/server/config/locales/es-ES.json b/server/config/locales/es-ES.json
index 5aa353de..0004be75 100644
--- a/server/config/locales/es-ES.json
+++ b/server/config/locales/es-ES.json
@@ -5,7 +5,7 @@
"Test Title": "Título de prueba",
"This is a test text message!": "¡Este es un mensaje de texto de prueba!",
"This is a *test* **markdown** `message`!": "¡Este es un *mensaje* **markdown** `de prueba`!",
- "This is a test html message": "Este es un mensaje html de prueba",
+ "This is a test html message!": "Este es un mensaje html de prueba!",
"You Were Added to Card": "Fuiste añadido a la tarjeta",
"You Were Mentioned in Comment": "Fuiste mencionado en un comentario",
"%s added you to %s on %s": "%s te añadió a %s en %s",
diff --git a/server/config/locales/fi-FI.json b/server/config/locales/fi-FI.json
index 0d74017b..0f3be176 100644
--- a/server/config/locales/fi-FI.json
+++ b/server/config/locales/fi-FI.json
@@ -5,7 +5,7 @@
"Test Title": "Testin otsikko",
"This is a test text message!": "Tämä on testiviesti!",
"This is a *test* **markdown** `message`!": "Tämä on *testi* **markdown** `viesti`!",
- "This is a test html message": "Tämä on testi html viesti",
+ "This is a test html message!": "Tämä on testi html viesti!",
"You Were Added to Card": "Sinut lisättiin korttiin",
"You Were Mentioned in Comment": "Sinut mainittiin kommentissa",
"%s added you to %s on %s": "%s lisäsi sinut kohteeseen %s kohteessa %s",
diff --git a/server/config/locales/fr-FR.json b/server/config/locales/fr-FR.json
index f7accd8e..0c4cb9e2 100644
--- a/server/config/locales/fr-FR.json
+++ b/server/config/locales/fr-FR.json
@@ -5,7 +5,7 @@
"Test Title": "Titre de test",
"This is a test text message!": "Ceci est un message texte de test !",
"This is a *test* **markdown** `message`!": "Ceci est un *message* **markdown** `de test` !",
- "This is a test html message": "Ceci est un test html message",
+ "This is a test html message!": "Ceci est un test html message!",
"You Were Added to Card": "Vous avez été ajouté à la carte",
"You Were Mentioned in Comment": "Vous avez été mentionné dans un commentaire",
"%s added you to %s on %s": "%s vous a ajouté à %s le %s",
diff --git a/server/config/locales/it-IT.json b/server/config/locales/it-IT.json
index ec13f54e..2e82564c 100644
--- a/server/config/locales/it-IT.json
+++ b/server/config/locales/it-IT.json
@@ -5,7 +5,7 @@
"Test Title": "Titolo di test",
"This is a test text message!": "Questo è un messaggio di testo di test!",
"This is a *test* **markdown** `message`!": "Questo è un *test* **markdown** `messaggio`!",
- "This is a test html message": "Questo è un test html messaggio",
+ "This is a test html message!": "Questo è un test html messaggio!",
"You Were Added to Card": "Sei stato aggiunto alla task",
"You Were Mentioned in Comment": "Sei stato menzionato nel commento",
"%s created %s in %s on %s": "%s ha creato %s in %s in %s",
diff --git a/server/config/locales/ru-RU.json b/server/config/locales/ru-RU.json
index fbd67b79..0467f4f0 100644
--- a/server/config/locales/ru-RU.json
+++ b/server/config/locales/ru-RU.json
@@ -5,7 +5,7 @@
"Test Title": "Тестовый заголовок",
"This is a test text message!": "Это тестовое сообщение!",
"This is a *test* **markdown** `message`!": "Это *тестовое* **markdown** `сообщение`!",
- "This is a test html message": "Это тестовое html сообщение",
+ "This is a test html message!": "Это тестовое html сообщение!",
"You Were Added to Card": "Вы были добавлены к карточке",
"You Were Mentioned in Comment": "Вы были упомянуты в комментарии",
"%s added you to %s on %s": "%s добавил(а) вас к %s на %s",
diff --git a/server/config/locales/tr-TR.json b/server/config/locales/tr-TR.json
index 53999cf9..2ee799d3 100644
--- a/server/config/locales/tr-TR.json
+++ b/server/config/locales/tr-TR.json
@@ -5,7 +5,7 @@
"Test Title": "Test başlığı",
"This is a test text message!": "Bu bir test metin mesajıdır!",
"This is a *test* **markdown** `message`!": "Bu bir *test* **markdown** `mesajı`!",
- "This is a test html message": "Bu bir test html mesajı",
+ "This is a test html message!": "Bu bir test html mesajı!",
"You Were Added to Card": "Karta eklendiniz",
"You Were Mentioned in Comment": "Bir yorumda bahsedildiniz",
"%s added you to %s on %s": "%s sizi %s'ye %s tarihinde ekledi",
diff --git a/server/config/locales/uk-UA.json b/server/config/locales/uk-UA.json
index 4d10d7c2..3f30267c 100644
--- a/server/config/locales/uk-UA.json
+++ b/server/config/locales/uk-UA.json
@@ -5,7 +5,7 @@
"Test Title": "Тестовий заголовок",
"This is a test text message!": "Це нове повідомлення!",
"This is a *test* **markdown** `message`!": "Це *тестове* **markdown** `повідомлення`!",
- "This is a test html message": "Це тестове html повідомлення",
+ "This is a test html message!": "Це тестове html повідомлення!",
"You Were Added to Card": "Вас було додано до картки",
"You Were Mentioned in Comment": "Вас було згадано у коментарі",
"%s added you to %s on %s": "%s додав(ла) вас до %s на %s",
diff --git a/server/config/policies.js b/server/config/policies.js
index 61e6bfce..3d290a30 100644
--- a/server/config/policies.js
+++ b/server/config/policies.js
@@ -18,6 +18,10 @@ module.exports.policies = {
'*': ['is-authenticated', 'is-external'],
+ 'config/show': ['is-authenticated', 'is-admin'],
+ 'config/update': ['is-authenticated', 'is-admin'],
+ 'config/test-smtp': ['is-authenticated', 'is-admin'],
+
'webhooks/index': ['is-authenticated', 'is-external', 'is-admin'],
'webhooks/create': ['is-authenticated', 'is-external', 'is-admin'],
'webhooks/update': ['is-authenticated', 'is-external', 'is-admin'],
@@ -35,7 +39,7 @@ module.exports.policies = {
'projects/create': ['is-authenticated', 'is-external', 'is-admin-or-project-owner'],
- 'config/show': true,
+ 'bootstrap/show': true,
'terms/show': true,
'access-tokens/create': true,
'access-tokens/exchange-with-oidc': true,
diff --git a/server/config/routes.js b/server/config/routes.js
index fb683e6b..39fd7974 100644
--- a/server/config/routes.js
+++ b/server/config/routes.js
@@ -62,10 +62,14 @@ function staticDirServer(prefix, dirFn) {
}
module.exports.routes = {
- 'GET /api/config': 'config/show',
+ 'GET /api/bootstrap': 'bootstrap/show',
'GET /api/terms/:type': 'terms/show',
+ 'GET /api/config': 'config/show',
+ 'PATCH /api/config': 'config/update',
+ 'POST /api/config/test-smtp': 'config/test-smtp',
+
'GET /api/webhooks': 'webhooks/index',
'POST /api/webhooks': 'webhooks/create',
'PATCH /api/webhooks/:id': 'webhooks/update',
diff --git a/server/db/migrations/20250917123048_add_ability_to_configure_smtp_via_ui.js b/server/db/migrations/20250917123048_add_ability_to_configure_smtp_via_ui.js
new file mode 100644
index 00000000..625f46ec
--- /dev/null
+++ b/server/db/migrations/20250917123048_add_ability_to_configure_smtp_via_ui.js
@@ -0,0 +1,36 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+exports.up = async (knex) => {
+ await knex.schema.alterTable('config', (table) => {
+ /* Columns */
+
+ table.text('smtp_host');
+ table.integer('smtp_port');
+ table.text('smtp_name');
+ table.boolean('smtp_secure').notNullable().defaultTo(false);
+ table.boolean('smtp_tls_reject_unauthorized').notNullable().defaultTo(true);
+ table.text('smtp_user');
+ table.text('smtp_password');
+ table.text('smtp_from');
+ });
+
+ return knex.schema.alterTable('config', (table) => {
+ table.boolean('smtp_secure').notNullable().alter();
+ table.boolean('smtp_tls_reject_unauthorized').notNullable().alter();
+ });
+};
+
+exports.down = (knex) =>
+ knex.schema.alterTable('config', (table) => {
+ table.dropColumn('smtp_host');
+ table.dropColumn('smtp_port');
+ table.dropColumn('smtp_name');
+ table.dropColumn('smtp_secure');
+ table.dropColumn('smtp_tls_reject_unauthorized');
+ table.dropColumn('smtp_user');
+ table.dropColumn('smtp_password');
+ table.dropColumn('smtp_from');
+ });