Compare commits

...
53 Commits
Author SHA1 Message Date
Maksim Eltyshev 16499052f7 chore: Update version 2024-04-10 15:57:15 +02:00
HannesOberreiter cc32daa009 feat: Display clickable links in tasks (#694)
Closes #330
2024-04-10 15:53:05 +02:00
Stan M ea1b3b7f92 feat: Ability to run with read-only root filesystem in Helm (#695) 2024-04-10 12:56:21 +02:00
Maksim Eltyshev 4e2863faa7 feat: Automatic logout when session expires
Closes #693
2024-04-09 15:12:46 +02:00
Maksim Eltyshev b46fb43e6f chore: Cleanup 2024-04-08 01:47:24 +02:00
Maksim Eltyshev bcacfa8fe2 Merge branch 'GlitchSecure-slack-bot-integration' 2024-04-08 01:29:28 +02:00
Niccolò Pedrini 4df2becd6f fix: Update Italian translation (#688) 2024-04-08 01:27:19 +02:00
Matthieu Bollot 2990ea593a feat: Slack bot notifications (#676) 2024-04-08 01:27:10 +02:00
Blyamur b976673826 fix: Update Russian translation (#686) 2024-04-08 01:24:50 +02:00
leroyloren 16e034efcb fix: Update Czech translation (#679) 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 6712625c3f chore: Update version 2024-04-08 01:24:50 +02:00
Matthieu Bollot b5bbf6a6a4 feat: Add ability to duplicate card (#668) 2024-04-08 01:24:50 +02:00
Brian Stinson 3d3e7761f5 fix: Allow specifying external dburl in Helm (#672) 2024-04-08 01:24:50 +02:00
piero fa56b9c01f fix: Update HorizontalPodAutoscaler version and spec in chart (#666)
Closes #667
2024-04-08 01:24:50 +02:00
Maksim Eltyshev 882213784f chore: Update version 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 251bd793f3 fix: Fix OIDC authentication error when redirecting from another tab
Closes #650
2024-04-08 01:24:50 +02:00
Maksim Eltyshev 088b0e760b fix: Fix scrollbar styles 2024-04-08 01:24:50 +02:00
Maksim Eltyshev cc1fa1cc3b chore: Update version 2024-04-08 01:24:50 +02:00
Edouard bcd3ea86e8 feat: SMTP integration and email notifications (#631) 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 0176650f67 fix: Add date-fns locale to Portuguese translation 2024-04-08 01:24:50 +02:00
Jeffrey f3963aded3 feat: Add Dutch translation (#644) 2024-04-08 01:24:50 +02:00
Reuben Hu bbe720481a feat: Add Indonesian translation (#643) 2024-04-08 01:24:50 +02:00
IV16SL 04f9030474 fix: Fix Chinese translation (#641) 2024-04-08 01:24:50 +02:00
Chris 6d135be859 feat: Add Portuguese translation (#640) 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 63cbe7063e docs: Update features, add contributors 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 7985bea2b8 chore: Update version 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 733abccaa9 fix: Fix case sensitivity of default admin environment variables 2024-04-08 01:24:50 +02:00
Daniel Hiller 119cce82b4 fix: Container build windows 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 988e70ec40 fix: Fix nullable boolean inputs 2024-04-08 01:24:50 +02:00
Daniel Hiller 5e3fca3771 Update build-and-push-docker-image-dev.yml 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 6d9ce56f4a fix: Include due date when importing from Trello
Closes #598
2024-04-08 01:24:50 +02:00
Daniel Hiller 3c8c04fd37 ci: update workflow 2024-04-08 01:24:50 +02:00
Daniel Hiller 790a1d5cc9 feat: Add healthcheck for docker deployments
fix: Missing healthcheck.js

fix: missing healthcheck.js

fix: HEALTHCHECK command
2024-04-08 01:24:50 +02:00
Maksim Eltyshev 4b352fd878 fix: Remove overflow of attachment dropzone 2024-04-08 01:24:50 +02:00
Maksim Eltyshev a196f2ef11 chore: Update version 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 6c65d135f7 feat: Add ability to enforce SSO
Closes #543, closes #545
2024-04-08 01:24:10 +02:00
Maksim Eltyshev a1a1e9a86a chore: Update version 2024-04-08 01:18:26 +02:00
Maksim Eltyshev 0d39a7567f feat: Add ability to map OIDC attributes and ignore username
Closes #554
2024-04-08 01:18:26 +02:00
Maksim Eltyshev 76a18d06d6 chore: Update version 2024-04-08 01:18:26 +02:00
Maksim Eltyshev ce52fc5af7 fix: Fix images becoming black and white when resizing
Closes #574, closes #585
2024-04-08 01:18:26 +02:00
Maksim Eltyshev 0a2863a79c chore: Bump sharp version 2024-04-08 01:18:26 +02:00
Niccolò Pedrini 39e320fe4b fix: Update Italian translation (#688) 2024-04-08 00:50:52 +02:00
Matthieu Bollot 2f3dfe775e feat: Slack bot notifications (#676) 2024-04-08 00:33:29 +02:00
Blyamur 22964cb375 fix: Update Russian translation (#686) 2024-04-07 22:05:22 +02:00
leroyloren fd89cf7f2b fix: Update Czech translation (#679) 2024-04-07 21:58:27 +02:00
GlitchWitch 273d994750 Remove unused code and comments 2023-12-28 12:01:38 -06:00
Brad Bahls 86d21d6abd updated to use currentUser.name for messages 2023-12-28 09:03:41 -07:00
GlitchWitch a9c200cc3b Merge branch 'master' into slack-bot-integration 2023-12-23 14:41:54 -06:00
GlitchWitch 3fedc14062 Rename custom.js -> slack.js 2023-12-23 10:52:15 -06:00
GlitchWitch c42e1fb5dd Add slack variables to docker-compose 2023-12-23 10:49:02 -06:00
GlitchWitch 9961340118 Replace plankaProdUrl with one set by environment. Remove hardcoded channel and unused variables. 2023-12-23 10:48:50 -06:00
Brad Bahls f54bd22757 removed test webhook url 2023-12-23 08:58:22 -07:00
Brad Bahls 1043dacd67 added new custom service with slack integration helper functions; added axios package; added notifications for card create, delete, and update (move); added notifications for comment create 2023-12-23 08:52:07 -07:00
38 changed files with 390 additions and 86 deletions
+2 -2
View File
@@ -15,13 +15,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.23 version: 0.1.25
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "1.16.2" appVersion: "1.16.3"
dependencies: dependencies:
- alias: postgresql - alias: postgresql
+9
View File
@@ -55,6 +55,11 @@ spec:
- mountPath: /app/private/attachments - mountPath: /app/private/attachments
subPath: attachments subPath: attachments
name: planka name: planka
{{- if .Values.securityContext.readOnlyRootFilesystem }}
- mountPath: /app/logs
subPath: app-logs
name: emptydir
{{- end }}
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
env: env:
@@ -139,3 +144,7 @@ spec:
{{- else }} {{- else }}
emptyDir: {} emptyDir: {}
{{- end }} {{- end }}
{{- if .Values.securityContext.readOnlyRootFilesystem }}
- name: emptydir
emptyDir: {}
{{- end }}
+1 -1
View File
@@ -1 +1 @@
REACT_APP_VERSION=1.16.2 REACT_APP_VERSION=1.16.3
+16
View File
@@ -17,6 +17,8 @@
"initials": "^3.1.2", "initials": "^3.1.2",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"linkify-react": "^4.1.3",
"linkifyjs": "^4.1.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nanoid": "^5.0.3", "nanoid": "^5.0.3",
"node-sass": "^9.0.0", "node-sass": "^9.0.0",
@@ -11911,6 +11913,20 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
}, },
"node_modules/linkify-react": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.1.3.tgz",
"integrity": "sha512-rhI3zM/fxn5BfRPHfi4r9N7zgac4vOIxub1wHIWXLA5ENTMs+BGaIaFO1D1PhmxgwhIKmJz3H7uCP0Dg5JwSlA==",
"peerDependencies": {
"linkifyjs": "^4.0.0",
"react": ">= 15.0.0"
}
},
"node_modules/linkifyjs": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.3.tgz",
"integrity": "sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg=="
},
"node_modules/loader-runner": { "node_modules/loader-runner": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
+2
View File
@@ -70,6 +70,8 @@
"initials": "^3.1.2", "initials": "^3.1.2",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"linkify-react": "^4.1.3",
"linkifyjs": "^4.1.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nanoid": "^5.0.3", "nanoid": "^5.0.3",
"node-sass": "^9.0.0", "node-sass": "^9.0.0",
+4 -2
View File
@@ -47,9 +47,11 @@ initializeCore.fetchConfig = (config) => ({
}, },
}); });
const logout = () => ({ const logout = (invalidateAccessToken) => ({
type: ActionTypes.LOGOUT, type: ActionTypes.LOGOUT,
payload: {}, payload: {
invalidateAccessToken,
},
}); });
logout.invalidateAccessToken = () => ({ logout.invalidateAccessToken = () => ({
+3 -1
View File
@@ -4,6 +4,8 @@ import classNames from 'classnames';
import { Progress } from 'semantic-ui-react'; import { Progress } from 'semantic-ui-react';
import { useToggle } from '../../lib/hooks'; import { useToggle } from '../../lib/hooks';
import Linkify from '../Linkify';
import styles from './Tasks.module.scss'; import styles from './Tasks.module.scss';
const Tasks = React.memo(({ items }) => { const Tasks = React.memo(({ items }) => {
@@ -48,7 +50,7 @@ const Tasks = React.memo(({ items }) => {
key={item.id} key={item.id}
className={classNames(styles.task, item.isCompleted && styles.taskCompleted)} className={classNames(styles.task, item.isCompleted && styles.taskCompleted)}
> >
{item.name} <Linkify linkStopPropagation>{item.name}</Linkify>
</li> </li>
))} ))}
</ul> </ul>
@@ -55,8 +55,10 @@
display: block; display: block;
font-size: 12px; font-size: 12px;
line-height: 14px; line-height: 14px;
overflow: hidden;
padding-bottom: 6px; padding-bottom: 6px;
padding-left: 14px; padding-left: 14px;
text-overflow: ellipsis;
&:before { &:before {
content: "–"; content: "–";
@@ -8,6 +8,7 @@ import { usePopup } from '../../../lib/popup';
import NameEdit from './NameEdit'; import NameEdit from './NameEdit';
import ActionsStep from './ActionsStep'; import ActionsStep from './ActionsStep';
import Linkify from '../../Linkify';
import styles from './Item.module.scss'; import styles from './Item.module.scss';
@@ -65,7 +66,7 @@ const Item = React.memo(
onClick={handleClick} onClick={handleClick}
> >
<span className={classNames(styles.task, isCompleted && styles.taskCompleted)}> <span className={classNames(styles.task, isCompleted && styles.taskCompleted)}>
{name} <Linkify linkStopPropagation>{name}</Linkify>
</span> </span>
</span> </span>
{isPersisted && canEdit && ( {isPersisted && canEdit && (
+68
View File
@@ -0,0 +1,68 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import LinkifyReact from 'linkify-react';
import history from '../history';
const Linkify = React.memo(({ children, linkStopPropagation, ...props }) => {
const handleLinkClick = useCallback(
(event) => {
if (linkStopPropagation) {
event.stopPropagation();
}
if (!event.target.getAttribute('target')) {
event.preventDefault();
history.push(event.target.href);
}
},
[linkStopPropagation],
);
const linkRenderer = useCallback(
({ attributes: { href, ...linkProps }, content }) => {
let url;
try {
url = new URL(href, window.location);
} catch (error) {} // eslint-disable-line no-empty
const isSameSite = !!url && url.origin === window.location.origin;
return (
<a
{...linkProps} // eslint-disable-line react/jsx-props-no-spreading
href={href}
target={isSameSite ? undefined : '_blank'}
rel={isSameSite ? undefined : 'noreferrer'}
onClick={handleLinkClick}
>
{isSameSite ? url.pathname : content}
</a>
);
},
[handleLinkClick],
);
return (
<LinkifyReact
{...props} // eslint-disable-line react/jsx-props-no-spreading
options={{
defaultProtocol: 'https',
render: linkRenderer,
}}
>
{children}
</LinkifyReact>
);
});
Linkify.propTypes = {
children: PropTypes.string.isRequired,
linkStopPropagation: PropTypes.bool,
};
Linkify.defaultProps = {
linkStopPropagation: false,
};
export default Linkify;
+4 -4
View File
@@ -38,7 +38,7 @@ const handleCardUpdate = (card) => ({
}, },
}); });
const moveCard = (id, listId, index = 0) => ({ const moveCard = (id, listId, index) => ({
type: EntryActionTypes.CARD_MOVE, type: EntryActionTypes.CARD_MOVE,
payload: { payload: {
id, id,
@@ -47,7 +47,7 @@ const moveCard = (id, listId, index = 0) => ({
}, },
}); });
const moveCurrentCard = (listId, index = 0) => ({ const moveCurrentCard = (listId, index) => ({
type: EntryActionTypes.CURRENT_CARD_MOVE, type: EntryActionTypes.CURRENT_CARD_MOVE,
payload: { payload: {
listId, listId,
@@ -55,7 +55,7 @@ const moveCurrentCard = (listId, index = 0) => ({
}, },
}); });
const transferCard = (id, boardId, listId, index = 0) => ({ const transferCard = (id, boardId, listId, index) => ({
type: EntryActionTypes.CARD_TRANSFER, type: EntryActionTypes.CARD_TRANSFER,
payload: { payload: {
id, id,
@@ -65,7 +65,7 @@ const transferCard = (id, boardId, listId, index = 0) => ({
}, },
}); });
const transferCurrentCard = (boardId, listId, index = 0) => ({ const transferCurrentCard = (boardId, listId, index) => ({
type: EntryActionTypes.CURRENT_CARD_TRANSFER, type: EntryActionTypes.CURRENT_CARD_TRANSFER,
payload: { payload: {
boardId, boardId,
+4 -2
View File
@@ -1,8 +1,10 @@
import EntryActionTypes from '../constants/EntryActionTypes'; import EntryActionTypes from '../constants/EntryActionTypes';
const logout = () => ({ const logout = (invalidateAccessToken) => ({
type: EntryActionTypes.LOGOUT, type: EntryActionTypes.LOGOUT,
payload: {}, payload: {
invalidateAccessToken,
},
}); });
export default { export default {
+8 -5
View File
@@ -39,9 +39,9 @@ export default {
areYouSureYouWantToLeaveBoard: 'Opravdu chcete opustit tuto tabuli?', areYouSureYouWantToLeaveBoard: 'Opravdu chcete opustit tuto tabuli?',
areYouSureYouWantToLeaveProject: 'Opravdu chcete opustit projekt?', areYouSureYouWantToLeaveProject: 'Opravdu chcete opustit projekt?',
areYouSureYouWantToRemoveThisManagerFromProject: areYouSureYouWantToRemoveThisManagerFromProject:
'Are you sure you want to remove this manager from the project?', 'Jste si jisti, že chcete tohoto správce z projektu odebrat?',
areYouSureYouWantToRemoveThisMemberFromBoard: areYouSureYouWantToRemoveThisMemberFromBoard:
'Opravdu chcete odstranit tohoto člena z tabule?', 'Jste si jisti, že chcete tohoto člena odebrat z tabule?',
attachment: 'Příloha', attachment: 'Příloha',
attachments: 'Přílohy', attachments: 'Přílohy',
authentication: 'Ověření', authentication: 'Ověření',
@@ -55,6 +55,7 @@ export default {
cardNotFound_title: 'Karta nenalezena', cardNotFound_title: 'Karta nenalezena',
cardOrActionAreDeleted: 'Karta nebo akce je smazána.', cardOrActionAreDeleted: 'Karta nebo akce je smazána.',
color: 'Barva', color: 'Barva',
copy_inline: 'copy',
createBoard_title: 'Vytvořit tabuli', createBoard_title: 'Vytvořit tabuli',
createLabel_title: 'Vytvořit štítek', createLabel_title: 'Vytvořit štítek',
createNewOneOrSelectExistingOne: 'Vytvořit nový nebo vyberte<br />již existující.', createNewOneOrSelectExistingOne: 'Vytvořit nový nebo vyberte<br />již existující.',
@@ -130,7 +131,7 @@ export default {
phone: 'Telefon', phone: 'Telefon',
preferences: 'Volby', preferences: 'Volby',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: dejte Ctrl-V (Cmd-V na Mac) pro vložení přílohy ze schránky.', 'Tip: stisknutím Ctrl-V (Cmd-V na Mac) přidáte přílohu ze schránky.',
project: 'Projekt', project: 'Projekt',
projectNotFound_title: 'Projekt nenalezen', projectNotFound_title: 'Projekt nenalezen',
removeManager_title: 'Odstranit vedoucího', removeManager_title: 'Odstranit vedoucího',
@@ -199,6 +200,8 @@ export default {
deleteTask: 'Smazat úkol', deleteTask: 'Smazat úkol',
deleteTask_title: 'Smazat úkol', deleteTask_title: 'Smazat úkol',
deleteUser: 'Smazat uživatele', deleteUser: 'Smazat uživatele',
duplicate: 'Duplikovat',
duplicateCard_title: 'Duplikovat kartu',
edit: 'Upravit', edit: 'Upravit',
editDueDate_title: 'Upravit termín do', editDueDate_title: 'Upravit termín do',
editDescription_title: 'Upravit popis', editDescription_title: 'Upravit popis',
@@ -226,13 +229,13 @@ export default {
removeMember: 'Odstranit člena', removeMember: 'Odstranit člena',
save: 'Uložit', save: 'Uložit',
showAllAttachments: 'Zozbrazit všechny přílohy ({{hidden}} skryté)', showAllAttachments: 'Zozbrazit všechny přílohy ({{hidden}} skryté)',
showFewerAttachments: 'Zobrazit méně příloh',
showDetails: 'Zobrazit detaily', showDetails: 'Zobrazit detaily',
showFewerAttachments: 'Zobrazit méně příloh',
start: 'Start', start: 'Start',
stop: 'Stop', stop: 'Stop',
subscribe: 'Odebírat', subscribe: 'Odebírat',
unsubscribe: 'Neodebírat', unsubscribe: 'Neodebírat',
uploadNewAvatar: 'Nahrát nového avatara', uploadNewAvatar: 'Nahrát nový avatar',
uploadNewImage: 'Nahrát nový obrázek', uploadNewImage: 'Nahrát nový obrázek',
}, },
}, },
+13 -7
View File
@@ -3,10 +3,10 @@ export default {
date: 'd/M/yyyy', date: 'd/M/yyyy',
time: 'p', time: 'p',
dateTime: '$t(format:date) $t(format:time)', dateTime: '$t(format:date) $t(format:time)',
longDate: 'MMM d', longDate: 'd MMM',
longDateTime: "MMMM d 'at' p", longDateTime: "d MMMM 'alle' p",
fullDate: 'MMM d, y', fullDate: 'd MMM, y',
fullDateTime: "MMMM d, y 'at' p", fullDateTime: "d MMMM, y 'alle' p",
}, },
translation: { translation: {
@@ -21,7 +21,7 @@ export default {
administrator: 'Amministratore', administrator: 'Amministratore',
all: 'tutto', all: 'tutto',
allChangesWillBeAutomaticallySavedAfterConnectionRestored: allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'All changes will be automatically saved<br />after connection restored.', 'Tutte le modifiche verranno salvate<br />al ripristino della connessione.',
areYouSureYouWantToDeleteThisAttachment: 'Sei sicuro di voler eliminare questo allegato?', areYouSureYouWantToDeleteThisAttachment: 'Sei sicuro di voler eliminare questo allegato?',
areYouSureYouWantToDeleteThisBoard: 'Sei sicuro di voler eliminare questa bacheca?', areYouSureYouWantToDeleteThisBoard: 'Sei sicuro di voler eliminare questa bacheca?',
areYouSureYouWantToDeleteThisCard: 'Sei sicuro di voler eliminare questa card?', areYouSureYouWantToDeleteThisCard: 'Sei sicuro di voler eliminare questa card?',
@@ -50,6 +50,7 @@ export default {
cardNotFound_title: 'Card non trovata', cardNotFound_title: 'Card non trovata',
cardOrActionAreDeleted: "La card o l'azione vengono eliminate.", cardOrActionAreDeleted: "La card o l'azione vengono eliminate.",
color: 'Colore', color: 'Colore',
copy_inline: 'copia',
createBoard_title: 'Crea Bacheca', createBoard_title: 'Crea Bacheca',
createLabel_title: 'Crea Etichetta', createLabel_title: 'Crea Etichetta',
createNewOneOrSelectExistingOne: 'Crea nuovo o seleziona<br />esistente.', createNewOneOrSelectExistingOne: 'Crea nuovo o seleziona<br />esistente.',
@@ -94,8 +95,10 @@ export default {
filterByLabels_title: 'Filtra per Etichetta', filterByLabels_title: 'Filtra per Etichetta',
filterByMembers_title: 'Filtra per Membro', filterByMembers_title: 'Filtra per Membro',
fromComputer_title: 'Dal Computer', fromComputer_title: 'Dal Computer',
fromTrello: 'Da Trello',
general: 'Generale', general: 'Generale',
hours: 'Ore', hours: 'Ore',
importBoard_title: 'Importa Board',
invalidCurrentPassword: 'Password corrente non valida', invalidCurrentPassword: 'Password corrente non valida',
labels: 'Etichette', labels: 'Etichette',
language: 'Lingua', language: 'Lingua',
@@ -123,7 +126,7 @@ export default {
phone: 'Telefono', phone: 'Telefono',
preferences: 'Preferenze', preferences: 'Preferenze',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: prmi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard.', 'Consiglio: premi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard.',
project: 'Progetto', project: 'Progetto',
projectNotFound_title: 'Progetto non trovato', projectNotFound_title: 'Progetto non trovato',
removeManager_title: 'Rimuovi Manager', removeManager_title: 'Rimuovi Manager',
@@ -193,6 +196,8 @@ export default {
deleteTask: 'Elimina task', deleteTask: 'Elimina task',
deleteTask_title: 'Elimina Task', deleteTask_title: 'Elimina Task',
deleteUser: 'Elimina utente', deleteUser: 'Elimina utente',
duplicate: 'Duplica',
duplicateCard_title: 'Duplica Card',
edit: 'Modifica', edit: 'Modifica',
editDueDate_title: 'Modifica data di scadenza', editDueDate_title: 'Modifica data di scadenza',
editDescription_title: 'Modifica Descrizione', editDescription_title: 'Modifica Descrizione',
@@ -204,9 +209,10 @@ export default {
editTitle_title: 'Modifica Titolo', editTitle_title: 'Modifica Titolo',
editUsername_title: 'Modifica Username', editUsername_title: 'Modifica Username',
hideDetails: 'Nascondi dettagli', hideDetails: 'Nascondi dettagli',
import: 'Importa',
leaveBoard: 'Lascia bacheca', leaveBoard: 'Lascia bacheca',
leaveProject: 'Lascia progetto', leaveProject: 'Lascia progetto',
logOut_title: 'Log Out', logOut_title: 'Disconnettiti',
makeCover_title: 'Crea Cover', makeCover_title: 'Crea Cover',
move: 'Muovi', move: 'Muovi',
moveCard_title: 'Muovi Card', moveCard_title: 'Muovi Card',
+2
View File
@@ -199,6 +199,8 @@ export default {
deleteProject: 'Удалить проект', deleteProject: 'Удалить проект',
deleteTask: 'Удалить задачу', deleteTask: 'Удалить задачу',
deleteUser: 'Удалить пользователя', deleteUser: 'Удалить пользователя',
duplicate: 'Дублировать',
duplicateCard_title: 'Дублировать карточку',
edit: 'Изменить', edit: 'Изменить',
editBackground: 'Изменить фон', editBackground: 'Изменить фон',
editDueDate: 'Изменить срок', editDueDate: 'Изменить срок',
+2 -4
View File
@@ -1,8 +1,7 @@
import { call, fork, join, put, select, take } from 'redux-saga/effects'; import { call, fork, join, put, select, take } from 'redux-saga/effects';
import selectors from '../../selectors'; import selectors from '../../selectors';
import actions from '../../actions'; import entryActions from '../../entry-actions';
import { removeAccessToken } from '../../utils/access-token-storage';
import ErrorCodes from '../../constants/ErrorCodes'; import ErrorCodes from '../../constants/ErrorCodes';
let lastRequestTask; let lastRequestTask;
@@ -22,8 +21,7 @@ function* queueRequest(method, ...args) {
}); });
} catch (error) { } catch (error) {
if (error.code === ErrorCodes.UNAUTHORIZED) { if (error.code === ErrorCodes.UNAUTHORIZED) {
yield call(removeAccessToken); yield put(entryActions.logout(false));
yield put(actions.logout()); // TODO: next url
yield take(); yield take();
} }
+2 -2
View File
@@ -86,7 +86,7 @@ export function* handleCardUpdate(card) {
yield put(actions.handleCardUpdate(card)); yield put(actions.handleCardUpdate(card));
} }
export function* moveCard(id, listId, index) { export function* moveCard(id, listId, index = 0) {
const position = yield select(selectors.selectNextCardPosition, listId, index, id); const position = yield select(selectors.selectNextCardPosition, listId, index, id);
yield call(updateCard, id, { yield call(updateCard, id, {
@@ -101,7 +101,7 @@ export function* moveCurrentCard(listId, index) {
yield call(moveCard, cardId, listId, index); yield call(moveCard, cardId, listId, index);
} }
export function* transferCard(id, boardId, listId, index) { export function* transferCard(id, boardId, listId, index = 0) {
const { cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath); const { cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath);
const position = yield select(selectors.selectNextCardPosition, listId, index, id); const position = yield select(selectors.selectNextCardPosition, listId, index, id);
+2 -3
View File
@@ -1,4 +1,4 @@
import { call, put, select, take } from 'redux-saga/effects'; import { call, put, select } from 'redux-saga/effects';
import request from '../request'; import request from '../request';
import requests from '../requests'; import requests from '../requests';
@@ -84,8 +84,7 @@ export function* logout(invalidateAccessToken = true) {
} catch (error) {} // eslint-disable-line no-empty } catch (error) {} // eslint-disable-line no-empty
} }
yield put(actions.logout()); yield put(actions.logout()); // TODO: next url
yield take();
} }
export default { export default {
+9
View File
@@ -1,10 +1,12 @@
import { call, put, select, take } from 'redux-saga/effects'; import { call, put, select, take } from 'redux-saga/effects';
import { push } from '../../../lib/redux-router'; import { push } from '../../../lib/redux-router';
import { logout } from './core';
import request from '../request'; import request from '../request';
import selectors from '../../../selectors'; import selectors from '../../../selectors';
import actions from '../../../actions'; import actions from '../../../actions';
import api from '../../../api'; import api from '../../../api';
import { getAccessToken } from '../../../utils/access-token-storage';
import ActionTypes from '../../../constants/ActionTypes'; import ActionTypes from '../../../constants/ActionTypes';
import Paths from '../../../constants/Paths'; import Paths from '../../../constants/Paths';
@@ -25,6 +27,13 @@ export function* goToCard(cardId) {
} }
export function* handleLocationChange() { export function* handleLocationChange() {
const accessToken = yield call(getAccessToken);
if (!accessToken) {
yield call(logout, false);
return;
}
const pathsMatch = yield select(selectors.selectPathsMatch); const pathsMatch = yield select(selectors.selectPathsMatch);
if (!pathsMatch) { if (!pathsMatch) {
+1
View File
@@ -218,6 +218,7 @@ export function* handleUserDelete(user) {
if (user.id === currentUserId) { if (user.id === currentUserId) {
yield call(logout, false); yield call(logout, false);
return;
} }
yield put(actions.handleUserDelete(user)); yield put(actions.handleUserDelete(user));
+5 -1
View File
@@ -4,5 +4,9 @@ import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes'; import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* coreWatchers() { export default function* coreWatchers() {
yield all([takeEvery(EntryActionTypes.LOGOUT, () => services.logout())]); yield all([
takeEvery(EntryActionTypes.LOGOUT, ({ payload: { invalidateAccessToken } }) =>
services.logout(invalidateAccessToken),
),
]);
} }
+8
View File
@@ -16,6 +16,10 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleSocketReconnect()); emit(entryActions.handleSocketReconnect());
}; };
const handleLogout = () => {
emit(entryActions.logout(false));
};
const handleUserCreate = api.makeHandleUserCreate(({ item }) => { const handleUserCreate = api.makeHandleUserCreate(({ item }) => {
emit(entryActions.handleUserCreate(item)); emit(entryActions.handleUserCreate(item));
}); });
@@ -171,6 +175,8 @@ const createSocketEventsChannel = () =>
socket.on('disconnect', handleDisconnect); socket.on('disconnect', handleDisconnect);
socket.on('reconnect', handleReconnect); socket.on('reconnect', handleReconnect);
socket.on('logout', handleLogout);
socket.on('userCreate', handleUserCreate); socket.on('userCreate', handleUserCreate);
socket.on('userUpdate', handleUserUpdate); socket.on('userUpdate', handleUserUpdate);
socket.on('userDelete', handleUserDelete); socket.on('userDelete', handleUserDelete);
@@ -227,6 +233,8 @@ const createSocketEventsChannel = () =>
socket.off('disconnect', handleDisconnect); socket.off('disconnect', handleDisconnect);
socket.off('reconnect', handleReconnect); socket.off('reconnect', handleReconnect);
socket.off('logout', handleLogout);
socket.off('userCreate', handleUserCreate); socket.off('userCreate', handleUserCreate);
socket.off('userUpdate', handleUserUpdate); socket.off('userUpdate', handleUserUpdate);
socket.off('userDelete', handleUserDelete); socket.off('userDelete', handleUserDelete);
+11 -8
View File
@@ -31,14 +31,6 @@ services:
# - DEFAULT_ADMIN_NAME=Demo Demo # - DEFAULT_ADMIN_NAME=Demo Demo
# - DEFAULT_ADMIN_USERNAME=demo # - DEFAULT_ADMIN_USERNAME=demo
# Email Notifications (https://nodemailer.com/smtp/)
# - SMTP_HOST=
# - SMTP_PORT=587
# - SMTP_SECURE=true
# - SMTP_USER=
# - SMTP_PASSWORD=
# - SMTP_FROM="Demo Demo" <demo@demo.demo>
# - OIDC_ISSUER= # - OIDC_ISSUER=
# - OIDC_CLIENT_ID= # - OIDC_CLIENT_ID=
# - OIDC_CLIENT_SECRET= # - OIDC_CLIENT_SECRET=
@@ -51,6 +43,17 @@ services:
# - OIDC_IGNORE_USERNAME=true # - OIDC_IGNORE_USERNAME=true
# - OIDC_IGNORE_ROLES=true # - OIDC_IGNORE_ROLES=true
# - OIDC_ENFORCED=true # - OIDC_ENFORCED=true
# Email Notifications (https://nodemailer.com/smtp/)
# - SMTP_HOST=
# - SMTP_PORT=587
# - SMTP_SECURE=true
# - SMTP_USER=
# - SMTP_PASSWORD=
# - SMTP_FROM="Demo Demo" <demo@demo.demo>
# - SLACK_BOT_TOKEN=
# - SLACK_CHANNEL_ID=
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "planka", "name": "planka",
"version": "1.16.2", "version": "1.16.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "planka", "name": "planka",
"version": "1.16.2", "version": "1.16.3",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "planka", "name": "planka",
"version": "1.16.2", "version": "1.16.3",
"private": true, "private": true,
"homepage": "https://plankanban.github.io/planka", "homepage": "https://plankanban.github.io/planka",
"repository": { "repository": {
+11 -8
View File
@@ -22,14 +22,6 @@ SECRET_KEY=notsecretkey
# DEFAULT_ADMIN_NAME=Demo Demo # DEFAULT_ADMIN_NAME=Demo Demo
# DEFAULT_ADMIN_USERNAME=demo # DEFAULT_ADMIN_USERNAME=demo
# Email Notifications (https://nodemailer.com/smtp/)
# SMTP_HOST=
# SMTP_PORT=587
# SMTP_SECURE=true
# SMTP_USER=
# SMTP_PASSWORD=
# SMTP_FROM="Demo Demo" <demo@demo.demo>
# OIDC_ISSUER= # OIDC_ISSUER=
# OIDC_CLIENT_ID= # OIDC_CLIENT_ID=
# OIDC_CLIENT_SECRET= # OIDC_CLIENT_SECRET=
@@ -43,6 +35,17 @@ SECRET_KEY=notsecretkey
# OIDC_IGNORE_ROLES=true # OIDC_IGNORE_ROLES=true
# OIDC_ENFORCED=true # OIDC_ENFORCED=true
# Email Notifications (https://nodemailer.com/smtp/)
# SMTP_HOST=
# SMTP_PORT=587
# SMTP_SECURE=true
# SMTP_USER=
# SMTP_PASSWORD=
# SMTP_FROM="Demo Demo" <demo@demo.demo>
# SLACK_BOT_TOKEN=
# SLACK_CHANNEL_ID=
## Do not edit this ## Do not edit this
TZ=UTC TZ=UTC
@@ -9,6 +9,10 @@ module.exports = {
deletedAt: new Date().toISOString(), deletedAt: new Date().toISOString(),
}); });
if (this.req.isSocket) {
sails.sockets.leaveAll(`@accessToken:${accessToken}`);
}
return { return {
item: accessToken, item: accessToken,
}; };
+1
View File
@@ -47,6 +47,7 @@ module.exports = {
card = await sails.helpers.cards.deleteOne.with({ card = await sails.helpers.cards.deleteOne.with({
record: card, record: card,
user: currentUser,
request: this.req, request: this.req,
}); });
+28
View File
@@ -14,6 +14,30 @@ const valuesValidator = (value) => {
return true; return true;
}; };
const buildAndSendSlackMessage = async (user, card, action) => {
const cardLink = `<${sails.config.custom.baseUrl}/cards/${card.id}|${card.name}>`;
let markdown;
switch (action.type) {
case Action.Types.CREATE_CARD:
markdown = `${cardLink} was created by ${user.name} in *${action.data.list.name}*`;
break;
case Action.Types.MOVE_CARD:
markdown = `${cardLink} was moved by ${user.name} to *${action.data.toList.name}*`;
break;
case Action.Types.COMMENT_CARD:
markdown = `*${user.name}* commented on ${cardLink}:\n>${action.data.text}`;
break;
default:
return;
}
await sails.helpers.utils.sendSlackMessage(markdown);
};
module.exports = { module.exports = {
inputs: { inputs: {
values: { values: {
@@ -67,6 +91,10 @@ module.exports = {
), ),
); );
if (sails.config.custom.slackBotToken) {
buildAndSendSlackMessage(values.user, values.card, action);
}
return action; return action;
}, },
}; };
+12
View File
@@ -1,9 +1,17 @@
const buildAndSendSlackMessage = async (user, card) => {
await sails.helpers.utils.sendSlackMessage(`*${card.name}* was deleted by ${user.name}`);
};
module.exports = { module.exports = {
inputs: { inputs: {
record: { record: {
type: 'ref', type: 'ref',
required: true, required: true,
}, },
user: {
type: 'ref',
required: true,
},
request: { request: {
type: 'ref', type: 'ref',
}, },
@@ -21,6 +29,10 @@ module.exports = {
}, },
inputs.request, inputs.request,
); );
if (sails.config.custom.slackBotToken) {
buildAndSendSlackMessage(inputs.user, card);
}
} }
return card; return card;
@@ -27,6 +27,7 @@ const buildAndSendEmail = async (user, board, card, action, notifiableUser) => {
`from ${action.data.fromList.name} to ${action.data.toList.name} ` + `from ${action.data.fromList.name} to ${action.data.toList.name} ` +
`on <a href="${process.env.BASE_URL}/boards/${board.id}">${board.name}</a></p>`, `on <a href="${process.env.BASE_URL}/boards/${board.id}">${board.name}</a></p>`,
}; };
break; break;
case Action.Types.COMMENT_CARD: case Action.Types.COMMENT_CARD:
emailData = { emailData = {
@@ -37,6 +38,7 @@ const buildAndSendEmail = async (user, board, card, action, notifiableUser) => {
`on <a href="${process.env.BASE_URL}/boards/${board.id}">${board.name}</a></p>` + `on <a href="${process.env.BASE_URL}/boards/${board.id}">${board.name}</a></p>` +
`<p>${action.data.text}</p>`, `<p>${action.data.text}</p>`,
}; };
break; break;
default: default:
return; return;
+1 -1
View File
@@ -25,7 +25,7 @@ module.exports = {
sails.log.info('Email sent: %s', info.messageId); sails.log.info('Email sent: %s', info.messageId);
} catch (error) { } catch (error) {
sails.log.error(error); sails.log.error(error); // TODO: provide description text?
} }
}, },
}; };
@@ -0,0 +1,53 @@
const POST_MESSAGE_API_URL = 'https://slack.com/api/chat.postMessage';
module.exports = {
inputs: {
markdown: {
type: 'string',
required: true,
},
},
async fn(inputs) {
const headers = {
Authorization: `Bearer ${sails.config.custom.slackBotToken}`,
'Content-Type': 'application/json; charset=utf-8',
};
const body = {
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: inputs.markdown,
},
},
],
channel: sails.config.custom.slackChannelId,
};
let response;
try {
response = await fetch(POST_MESSAGE_API_URL, {
headers,
method: 'POST',
body: JSON.stringify(body),
});
} catch (error) {
sails.log.error(error); // TODO: provide description text?
return;
}
if (!response.ok) {
sails.log.error('Error sending to Slack: %s', response.error);
return;
}
const responseJson = await response.json();
if (!responseJson.ok) {
sails.log.error('Error sending to Slack: %s', responseJson.error);
}
},
};
+1
View File
@@ -61,6 +61,7 @@ module.exports = function defineCurrentUserHook(sails) {
}); });
if (req.isSocket) { if (req.isSocket) {
sails.sockets.join(req, `@accessToken:${accessToken}`);
sails.sockets.join(req, `@user:${currentUser.id}`); sails.sockets.join(req, `@user:${currentUser.id}`);
} }
} }
+22 -11
View File
@@ -1,6 +1,14 @@
const openidClient = require('openid-client'); const openidClient = require('openid-client');
module.exports = function oidcServiceHook(sails) { /**
* oidc 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
*/
module.exports = function defineOidcHook(sails) {
let client = null; let client = null;
return { return {
@@ -9,17 +17,20 @@ module.exports = function oidcServiceHook(sails) {
*/ */
async initialize() { async initialize() {
if (sails.config.custom.oidcIssuer) { if (!sails.config.custom.oidcIssuer) {
const issuer = await openidClient.Issuer.discover(sails.config.custom.oidcIssuer); return;
client = new issuer.Client({
client_id: sails.config.custom.oidcClientId,
client_secret: sails.config.custom.oidcClientSecret,
redirect_uris: [sails.config.custom.oidcRedirectUri],
response_types: ['code'],
});
sails.log.info('OIDC hook has been loaded successfully');
} }
sails.log.info('Initializing custom hook (`oidc`)');
const issuer = await openidClient.Issuer.discover(sails.config.custom.oidcIssuer);
client = new issuer.Client({
client_id: sails.config.custom.oidcClientId,
client_secret: sails.config.custom.oidcClientSecret,
redirect_uris: [sails.config.custom.oidcRedirectUri],
response_types: ['code'],
});
}, },
getClient() { getClient() {
+24 -13
View File
@@ -1,6 +1,14 @@
const nodemailer = require('nodemailer'); const nodemailer = require('nodemailer');
module.exports = function smtpServiceHook(sails) { /**
* 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
*/
module.exports = function defineSmtpHook(sails) {
let transporter = null; let transporter = null;
return { return {
@@ -9,19 +17,22 @@ module.exports = function smtpServiceHook(sails) {
*/ */
async initialize() { async initialize() {
if (sails.config.custom.smtpHost) { if (!sails.config.custom.smtpHost) {
transporter = nodemailer.createTransport({ return;
pool: true,
host: sails.config.custom.smtpHost,
port: sails.config.custom.smtpPort,
secure: sails.config.custom.smtpSecure,
auth: sails.config.custom.smtpUser && {
user: sails.config.custom.smtpUser,
pass: sails.config.custom.smtpPassword,
},
});
sails.log.info('SMTP hook has been loaded successfully');
} }
sails.log.info('Initializing custom hook (`smtp`)');
transporter = nodemailer.createTransport({
pool: true,
host: sails.config.custom.smtpHost,
port: sails.config.custom.smtpPort,
secure: sails.config.custom.smtpSecure,
auth: sails.config.custom.smtpUser && {
user: sails.config.custom.smtpUser,
pass: sails.config.custom.smtpPassword,
},
});
}, },
getTransporter() { getTransporter() {
+38
View File
@@ -0,0 +1,38 @@
/**
* watcher 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
*/
module.exports = function defineWatcherHook(sails) {
const checkSocketConnectionsToLogout = () => {
Object.keys(sails.io.sockets.adapter.rooms).forEach((room) => {
if (!room.startsWith('@accessToken:')) {
return;
}
const accessToken = room.split(':')[1];
try {
sails.helpers.utils.verifyToken(accessToken);
} catch (error) {
sails.sockets.broadcast(room, 'logout');
sails.sockets.leaveAll(room);
}
});
};
return {
/**
* Runs when this Sails app loads/lifts.
*/
async initialize() {
sails.log.info('Initializing custom hook (`watcher`)');
setInterval(checkSocketConnectionsToLogout, 60 * 1000);
},
};
};
+10 -7
View File
@@ -34,13 +34,6 @@ module.exports.custom = {
defaultAdminEmail: defaultAdminEmail:
process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase(), process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase(),
smtpHost: process.env.SMTP_HOST,
smtpPort: process.env.SMTP_PORT || 587,
smtpSecure: process.env.SMTP_SECURE === 'true',
smtpUser: process.env.SMTP_USER,
smtpPassword: process.env.SMTP_PASSWORD,
smtpFrom: process.env.SMTP_FROM,
oidcIssuer: process.env.OIDC_ISSUER, oidcIssuer: process.env.OIDC_ISSUER,
oidcClientId: process.env.OIDC_CLIENT_ID, oidcClientId: process.env.OIDC_CLIENT_ID,
oidcClientSecret: process.env.OIDC_CLIENT_SECRET, oidcClientSecret: process.env.OIDC_CLIENT_SECRET,
@@ -58,4 +51,14 @@ module.exports.custom = {
oidcRedirectUri: `${ oidcRedirectUri: `${
sails.config.environment === 'production' ? process.env.BASE_URL : 'http://localhost:3000' sails.config.environment === 'production' ? process.env.BASE_URL : 'http://localhost:3000'
}/oidc-callback`, }/oidc-callback`,
smtpHost: process.env.SMTP_HOST,
smtpPort: process.env.SMTP_PORT || 587,
smtpSecure: process.env.SMTP_SECURE === 'true',
smtpUser: process.env.SMTP_USER,
smtpPassword: process.env.SMTP_PASSWORD,
smtpFrom: process.env.SMTP_FROM,
slackBotToken: process.env.SLACK_BOT_TOKEN,
slackChannelId: process.env.SLACK_CHANNEL_ID,
}; };