diff --git a/charts/planka/Chart.yaml b/charts/planka/Chart.yaml index 470f805..02158c7 100644 --- a/charts/planka/Chart.yaml +++ b/charts/planka/Chart.yaml @@ -15,13 +15,13 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.24 +version: 0.1.26 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.16.2" +appVersion: "1.16.4" dependencies: - alias: postgresql diff --git a/client/.env b/client/.env index e17aeb7..1b48387 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.16.2 +REACT_APP_VERSION=1.16.4 diff --git a/client/package-lock.json b/client/package-lock.json index 62cebb9..b1e6907 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,8 @@ "initials": "^3.1.2", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", + "linkify-react": "^4.1.3", + "linkifyjs": "^4.1.3", "lodash": "^4.17.21", "nanoid": "^5.0.3", "node-sass": "^9.0.0", @@ -11911,6 +11913,20 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "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": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", diff --git a/client/package.json b/client/package.json index 25cdce0..3f3c4e1 100755 --- a/client/package.json +++ b/client/package.json @@ -70,6 +70,8 @@ "initials": "^3.1.2", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", + "linkify-react": "^4.1.3", + "linkifyjs": "^4.1.3", "lodash": "^4.17.21", "nanoid": "^5.0.3", "node-sass": "^9.0.0", diff --git a/client/src/components/Card/Tasks.jsx b/client/src/components/Card/Tasks.jsx index d76340f..c530474 100644 --- a/client/src/components/Card/Tasks.jsx +++ b/client/src/components/Card/Tasks.jsx @@ -4,6 +4,8 @@ import classNames from 'classnames'; import { Progress } from 'semantic-ui-react'; import { useToggle } from '../../lib/hooks'; +import Linkify from '../Linkify'; + import styles from './Tasks.module.scss'; const Tasks = React.memo(({ items }) => { @@ -48,7 +50,7 @@ const Tasks = React.memo(({ items }) => { key={item.id} className={classNames(styles.task, item.isCompleted && styles.taskCompleted)} > - {item.name} + {item.name} ))} diff --git a/client/src/components/Card/Tasks.module.scss b/client/src/components/Card/Tasks.module.scss index 508ac89..8f8a7ec 100644 --- a/client/src/components/Card/Tasks.module.scss +++ b/client/src/components/Card/Tasks.module.scss @@ -55,8 +55,10 @@ display: block; font-size: 12px; line-height: 14px; + overflow: hidden; padding-bottom: 6px; padding-left: 14px; + text-overflow: ellipsis; &:before { content: "–"; diff --git a/client/src/components/CardModal/Tasks/Item.jsx b/client/src/components/CardModal/Tasks/Item.jsx index a34beb0..8bb3046 100755 --- a/client/src/components/CardModal/Tasks/Item.jsx +++ b/client/src/components/CardModal/Tasks/Item.jsx @@ -8,6 +8,7 @@ import { usePopup } from '../../../lib/popup'; import NameEdit from './NameEdit'; import ActionsStep from './ActionsStep'; +import Linkify from '../../Linkify'; import styles from './Item.module.scss'; @@ -65,7 +66,7 @@ const Item = React.memo( onClick={handleClick} > - {name} + {name} {isPersisted && canEdit && ( diff --git a/client/src/components/Linkify.jsx b/client/src/components/Linkify.jsx new file mode 100644 index 0000000..71b01fd --- /dev/null +++ b/client/src/components/Linkify.jsx @@ -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 ( + + {isSameSite ? url.pathname : content} + + ); + }, + [handleLinkClick], + ); + + return ( + + {children} + + ); +}); + +Linkify.propTypes = { + children: PropTypes.string.isRequired, + linkStopPropagation: PropTypes.bool, +}; + +Linkify.defaultProps = { + linkStopPropagation: false, +}; + +export default Linkify; diff --git a/client/src/containers/LoginContainer.js b/client/src/containers/LoginContainer.js index 881b65b..1a16788 100755 --- a/client/src/containers/LoginContainer.js +++ b/client/src/containers/LoginContainer.js @@ -20,7 +20,7 @@ const mapStateToProps = (state) => { isSubmittingUsingOidc, error, withOidc: !!oidcConfig, - isOidcEnforced: oidcConfig && oidcConfig.isEnforced, + isOidcEnforced: !!oidcConfig && oidcConfig.isEnforced, }; }; diff --git a/config/development/Dockerfile.client b/config/development/Dockerfile.client new file mode 100644 index 0000000..8a5ed92 --- /dev/null +++ b/config/development/Dockerfile.client @@ -0,0 +1,18 @@ +FROM node:18-alpine as server-dependencies + +RUN apk -U upgrade \ + && apk add build-base python3 \ + --no-cache + +WORKDIR /app/client +COPY package.json package-lock.json /app/client/ +RUN npm install npm@latest --global \ + && npm install pnpm --global \ + && pnpm import \ + && pnpm install + + +WORKDIR /app/ +COPY ../../package.json ../../package-lock.json /app/ +RUN pnpm import \ + && pnpm install diff --git a/config/development/Dockerfile.server b/config/development/Dockerfile.server new file mode 100644 index 0000000..0f51abe --- /dev/null +++ b/config/development/Dockerfile.server @@ -0,0 +1,14 @@ +FROM node:18-alpine as server-dependencies + +RUN apk -U upgrade \ + && apk add build-base python3 \ + --no-cache + +WORKDIR /app + +COPY package.json package-lock.json ./ + +RUN npm install npm@latest --global \ + && npm install pnpm --global \ + && pnpm import \ + && pnpm install diff --git a/config/development/nginx.conf b/config/development/nginx.conf new file mode 100644 index 0000000..94fc7d7 --- /dev/null +++ b/config/development/nginx.conf @@ -0,0 +1,47 @@ +user nginx; +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + sendfile on; + keepalive_timeout 65; + + server { + listen 80; + + location /api/ { + proxy_pass http://server:1337; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + + location /socket.io { + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://server:1337/socket.io; + } + + location / { + proxy_pass http://client:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + } +} diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 9fe7085..b44da1b 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -1,20 +1,18 @@ -version: '3' +version: '3.8' services: - planka: - image: ghcr.io/plankanban/planka:master - restart: on-failure + + server: + build: + context: ./server + dockerfile: ../config/development/Dockerfile.server volumes: - - user-avatars:/app/public/user-avatars - - project-background-images:/app/public/project-background-images - - attachments:/app/private/attachments - ports: - - 3000:1337 + - ./server:/app + - /app/node_modules environment: - - BASE_URL=http://localhost:3000 - - DATABASE_URL=postgresql://postgres@postgres/planka + - NODE_ENV=development + - DATABASE_URL=postgresql://user:password@postgres:5432/planka_db - SECRET_KEY=notsecretkey - # - TRUST_PROXY=0 # - TOKEN_EXPIRES_IN=365 # In days @@ -31,14 +29,6 @@ services: # - DEFAULT_ADMIN_NAME=Demo 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" - # - OIDC_ISSUER= # - OIDC_CLIENT_ID= # - OIDC_CLIENT_SECRET= @@ -51,26 +41,82 @@ services: # - OIDC_IGNORE_USERNAME=true # - OIDC_IGNORE_ROLES=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" + + # - SLACK_BOT_TOKEN= + # - SLACK_CHANNEL_ID= + + working_dir: /app + command: ["sh", "-c", "npm run start"] + depends_on: + - init-db + + client: + build: + context: ./client + dockerfile: ../config/development/Dockerfile.client + volumes: + - ./client:/app/client + - /app/client/node_modules + - /app/node_modules + environment: + - NODE_ENV=development + - CHOKIDAR_USEPOLLING=true + - BASE_URL=http://localhost:3000 + - REACT_APP_SERVER_BASE_URL=http://localhost:3000 + working_dir: /app/client + command: npm start + + init-db: + build: + context: ./server + dockerfile: ../config/development/Dockerfile.server + environment: + - DATABASE_URL=postgresql://user:password@postgres:5432/planka_db + + working_dir: /app + command: ["sh", "-c", "npm run db:init"] + volumes: + - ./server:/app + - /app/node_modules depends_on: postgres: condition: service_healthy postgres: - image: postgres:14-alpine - restart: on-failure + image: postgres:latest volumes: - db-data:/var/lib/postgresql/data environment: - - POSTGRES_DB=planka - - POSTGRES_HOST_AUTH_METHOD=trust + POSTGRES_DB: planka_db + POSTGRES_USER: user + POSTGRES_PASSWORD: password + ports: + - "5432:5432" + restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d planka"] interval: 10s timeout: 5s retries: 5 + proxy: + image: nginx:alpine + ports: + - "3000:80" + volumes: + - ./config/development/nginx.conf:/etc/nginx/nginx.conf + depends_on: + - server + - client + + volumes: - user-avatars: - project-background-images: - attachments: db-data: diff --git a/package-lock.json b/package-lock.json index eee91c9..9c2bff2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "planka", - "version": "1.16.2", + "version": "1.16.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "planka", - "version": "1.16.2", + "version": "1.16.4", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 7e06e77..dab2865 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "planka", - "version": "1.16.2", + "version": "1.16.4", "private": true, "homepage": "https://plankanban.github.io/planka", "repository": { diff --git a/server/api/helpers/utils/send-email.js b/server/api/helpers/utils/send-email.js index 7b8d08b..02593f8 100644 --- a/server/api/helpers/utils/send-email.js +++ b/server/api/helpers/utils/send-email.js @@ -23,9 +23,9 @@ module.exports = { from: sails.config.custom.smtpFrom, }); - sails.log.info('Email sent: %s', info.messageId); + sails.log.info(`Email sent: ${info.messageId}`); } catch (error) { - sails.log.error(error); // TODO: provide description text? + sails.log.error(`Error sending email: ${error}`); } }, }; diff --git a/server/api/helpers/utils/send-slack-message.js b/server/api/helpers/utils/send-slack-message.js index 573fcf8..510ae1b 100644 --- a/server/api/helpers/utils/send-slack-message.js +++ b/server/api/helpers/utils/send-slack-message.js @@ -35,19 +35,19 @@ module.exports = { body: JSON.stringify(body), }); } catch (error) { - sails.log.error(error); // TODO: provide description text? + sails.log.error(`Error sending to Slack: ${error}`); return; } if (!response.ok) { - sails.log.error('Error sending to Slack: %s', response.error); + sails.log.error(`Error sending to Slack: ${response.error}`); return; } const responseJson = await response.json(); if (!responseJson.ok) { - sails.log.error('Error sending to Slack: %s', responseJson.error); + sails.log.error(`Error sending to Slack: ${responseJson.error}`); } }, };