mirror of
https://github.com/wekan/wekan.git
synced 2026-11-05 15:45:01 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7f9018a60 | ||
|
|
b6fe0a7256 | ||
|
|
02e3c35d69 | ||
|
|
4766b83da7 | ||
|
|
011c172c8c | ||
|
|
cb18f9b735 | ||
|
|
abe8af0788 | ||
|
|
997f1fb9df | ||
|
|
9828081c76 | ||
|
|
d2ec3ee400 | ||
|
|
43006f9025 | ||
|
|
fcfd5bce9b | ||
|
|
4cbefed339 | ||
|
|
7d594c95ae | ||
|
|
7d5f29e114 | ||
|
|
29b617769a | ||
|
|
f0d25a2431 | ||
|
|
09805a8a91 | ||
|
|
6f7ad270d1 | ||
|
|
bd1c611688 | ||
|
|
ae9be62ef3 | ||
|
|
41cd166358 | ||
|
|
e39dcd4ebf | ||
|
|
4f1a60aa7b | ||
|
|
5757cfc8ca | ||
|
|
c7c8a200ff | ||
|
|
71fcf2b454 | ||
|
|
1dbc26d200 | ||
|
|
84fee7b660 | ||
|
|
3b7a276347 | ||
|
|
02cf16d8c6 | ||
|
|
54944717ee | ||
|
|
b99d21d9f1 | ||
|
|
3dfd84973c | ||
|
|
dd4611e43f | ||
|
|
69e6b8bcd1 | ||
|
|
6ab29dc81e | ||
|
|
9ae6a91daf | ||
|
|
c3674330ac | ||
|
|
be5ddd77ef | ||
|
|
9fc88a783c | ||
|
|
748fd2d391 | ||
|
|
4ac24dc546 | ||
|
|
4c630d4ba8 | ||
|
|
a563d3c4b2 | ||
|
|
95b186e50a | ||
|
|
bbc525ef27 | ||
|
|
bfb3fd246b | ||
|
|
3a968052b9 | ||
|
|
d424bd6386 | ||
|
|
3824066f7f | ||
|
|
774d752620 | ||
|
|
dbca7cd72d | ||
|
|
8497e7c537 | ||
|
|
7f44459c9f | ||
|
|
d5c69e0259 | ||
|
|
598aa8dfa1 | ||
|
|
0360ca1583 | ||
|
|
9c51391a75 | ||
|
|
2ed13a1593 | ||
|
|
ca50c9d1aa | ||
|
|
548d7b483e | ||
|
|
71bbc9f67e | ||
|
|
05fd6fd3a5 | ||
|
|
e8939bc1e6 | ||
|
|
bc8d58011c | ||
|
|
0bf8cae81c | ||
|
|
984ec160da | ||
|
|
a2df80e64d | ||
|
|
3aaf8f9b61 | ||
|
|
1dfc223ca0 | ||
|
|
604cc455e0 | ||
|
|
e85072a14a | ||
|
|
1621f3784a | ||
|
|
ba6ad20a30 | ||
|
|
3847157753 | ||
|
|
4e77133aca | ||
|
|
77c696e37b | ||
|
|
e3f17be14d | ||
|
|
5ca4ebe09a | ||
|
|
3117e419bb | ||
|
|
3eba4aa782 | ||
|
|
127f1fb04e | ||
|
|
14b4f5b5ed | ||
|
|
e6a4abd394 | ||
|
|
25cdc4db0d | ||
|
|
c062d93d57 | ||
|
|
e014d5c816 | ||
|
|
84ab805ec1 | ||
|
|
a9a5ac7a07 | ||
|
|
0119e62da5 | ||
|
|
284d4b25ea | ||
|
|
75df523ef0 | ||
|
|
e5c844fca7 | ||
|
|
8a84707726 | ||
|
|
9f7eac158e | ||
|
|
9b729b502e | ||
|
|
cef965f73f | ||
|
|
43dd98f8a1 | ||
|
|
2cd772fcf5 | ||
|
|
eaa1d823de | ||
|
|
1a41c72c21 | ||
|
|
7943a17432 | ||
|
|
d41e17d6a0 | ||
|
|
4ec2cbcd5a | ||
|
|
1ce2d8cc88 | ||
|
|
8f76c7b2b0 | ||
|
|
b07c4d0a7b | ||
|
|
5b43b7159e | ||
|
|
7ce71dbb2e | ||
|
|
d615e599f4 | ||
|
|
aad00cdba2 | ||
|
|
07800da9af | ||
|
|
da0d338e33 | ||
|
|
c3c091635d | ||
|
|
8c499823cb | ||
|
|
1844425e8f | ||
|
|
68dd14140f | ||
|
|
1ba32f5269 | ||
|
|
30f36a7f31 | ||
|
|
20b9c9ddd6 | ||
|
|
0e9aed5a4b | ||
|
|
27d8468be0 | ||
|
|
d01526bbff | ||
|
|
ab3b48b334 | ||
|
|
b0da9097c1 | ||
|
|
158130cbfc | ||
|
|
53328f40c6 | ||
|
|
e7eeaf0b10 | ||
|
|
609b07a8db | ||
|
|
7545bb5506 | ||
|
|
457713968c | ||
|
|
372a58c112 | ||
|
|
527962966b | ||
|
|
a37e7d1cc7 | ||
|
|
33ad40d012 | ||
|
|
44b5e12de9 | ||
|
|
1207b573b5 | ||
|
|
a26295a2fb | ||
|
|
b2da9ac8d4 | ||
|
|
53dcf7379d | ||
|
|
dfa6c78d4f | ||
|
|
795850ed05 | ||
|
|
9ef7f4a07a | ||
|
|
cb2394a95d | ||
|
|
ac368de06a | ||
|
|
b26494766f | ||
|
|
68ab63c7fe | ||
|
|
e3e71b254a | ||
|
|
fc4eadedc9 | ||
|
|
9dca403782 | ||
|
|
dd90995db7 | ||
|
|
f8e31745d8 |
No files matched your search
@@ -7,13 +7,18 @@ name: Release All Platforms
|
||||
# Or use the convenience script (bumps versions, commits, pushes, then triggers):
|
||||
# ./releases/release-all.sh 9.35 9.36
|
||||
#
|
||||
# The Docker image and snap cover the platforms MongoDB ships server binaries for
|
||||
# (amd64, arm64; win64 + mac bundles too); MongoDB 7.x is the default database.
|
||||
# Additionally, WeKan server bundles are built for every other architecture
|
||||
# Node.js 24.x publishes binaries for — s390x and ppc64le — and attached to the
|
||||
# GitHub Release as extra downloads (run a separate MongoDB for those). These
|
||||
# extra-arch bundles are NOT part of the Docker image or snap. (armv7l/32-bit ARM
|
||||
# is not built: Node.js 24 ships no armv7l binaries.)
|
||||
# WeKan's default database is MongoDB, which ships server binaries only for amd64
|
||||
# and arm64 (+ the win64/mac bundles). On every OTHER architecture that has a
|
||||
# Node.js 24 build — ppc64le, s390x, riscv64 — there is no MongoDB, so those
|
||||
# bundles ship FerretDB v1 instead: the wekan/FerretDB fork with its embedded
|
||||
# pure-Go SQLite backend, which speaks the MongoDB wire protocol. The FerretDB
|
||||
# binary is cross-compiled once (build-ferretdb) and baked into EVERY bundle, so
|
||||
# amd64/arm64 users can opt into it too (WEKAN_DB=ferretdb, `snap set wekan
|
||||
# database=ferretdb`, or Docker env). Whichever backend is chosen binds the same
|
||||
# MongoDB port, so MONGO_URL is unchanged. ppc64le/s390x/riscv64 are now covered
|
||||
# by the Docker image as well (self-contained, FerretDB auto-started). riscv64
|
||||
# uses unofficial-builds.nodejs.org for Node 24. (armv7l/32-bit ARM is still not
|
||||
# built: there is NO Node.js 24 build for it anywhere, so nothing could run it.)
|
||||
#
|
||||
# This workflow does EVERYTHING for a release remotely (nothing is built or
|
||||
# pushed on the maintainer's machine), running jobs in parallel where possible:
|
||||
@@ -25,18 +30,24 @@ name: Release All Platforms
|
||||
# - charts: updates and publishes the wekan/charts Helm chart and pushes,
|
||||
# only AFTER the `docker` job has published the image (skipped if
|
||||
# docker fails)
|
||||
# - build-ferretdb: downloads the newest prebuilt ferretdb.zip from the
|
||||
# wekan/FerretDB releases (FerretDB is built + released separately by
|
||||
# its own build.sh — not compiled here); every bundle embeds its own
|
||||
# - build-*: builds the amd64 bundle (full Meteor 3 build), then rebuilds native
|
||||
# modules for arm64 from that amd64 base
|
||||
# modules for arm64 from that amd64 base (each embeds its FerretDB)
|
||||
# - release: creates the GitHub Release with the newest CHANGELOG.md notes and
|
||||
# uploads the core wekan-<version>-{amd64,arm64}.zip bundles
|
||||
# - build-win64 / build-mac-arm64 / build-extra-arches: built AFTER release, each
|
||||
# rebuilds the amd64 bundle's native modules for its platform (Windows,
|
||||
# macOS arm64, and QEMU-emulated s390x/ppc64le) and attaches its
|
||||
# .zip to the Release as an extra download. Decoupled from `release` so
|
||||
# a slow/flaky build never blocks it; not in the Docker image or snap
|
||||
# - docker: builds and pushes the multi-arch image to Docker Hub, Quay.io, GHCR
|
||||
# macOS arm64, and QEMU-emulated ppc64le/s390x/riscv64) and attaches
|
||||
# its .zip to the Release. Windows/macOS strip the Linux FerretDB back
|
||||
# out (MongoDB-only). Decoupled from `release` so a slow/flaky build
|
||||
# never blocks it
|
||||
# - docker: builds and pushes the multi-arch image (amd64, arm64, ppc64le, s390x,
|
||||
# riscv64) to Docker Hub, Quay.io, GHCR
|
||||
# - snap: builds the snap on Launchpad (amd64 + arm64), pushes it to the Snap
|
||||
# Store and attaches the .snap files to the GitHub Release
|
||||
# Store and attaches the .snap files to the GitHub Release. `snap set
|
||||
# wekan database=ferretdb` switches the running backend to FerretDB v1
|
||||
#
|
||||
# Required GitHub Secrets:
|
||||
# GHCR_AUTH - base64("username:token") for ghcr.io
|
||||
@@ -258,7 +269,7 @@ jobs:
|
||||
# The full Meteor build always runs here on x86_64; every other platform then
|
||||
# only rebuilds the native npm modules from this bundle.
|
||||
build-amd64:
|
||||
needs: prepare
|
||||
needs: [prepare, build-ferretdb]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 180
|
||||
env:
|
||||
@@ -308,6 +319,31 @@ jobs:
|
||||
cd .build/bundle/programs/server
|
||||
npm install
|
||||
|
||||
- name: Download ferretdb.zip
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ferretdb-zip
|
||||
|
||||
- name: Embed Node.js, FerretDB (amd64) and launcher into the bundle
|
||||
# Make the release .zip self-contained (offline): bundle Node.js + FerretDB
|
||||
# + a start-wekan.sh that runs WeKan on the bundled Node against the bundled
|
||||
# FerretDB (SQLite) by default, storing files on the filesystem under
|
||||
# WRITABLE_PATH. This amd64 bundle is the base every other Linux arch is
|
||||
# derived from; those jobs overwrite bundle/node and bundle/ferretdb with
|
||||
# their own arch's binaries and inherit this launcher.
|
||||
run: |
|
||||
# Bundled Node.js: the runner's node (setup-node) is linux-x64 Node 24,
|
||||
# matching this base bundle's arch.
|
||||
cp -L "$(command -v node)" .build/bundle/node
|
||||
chmod +x .build/bundle/node
|
||||
# Bundled FerretDB (amd64) from the single-source ferretdb.zip.
|
||||
unzip -o -j ferretdb.zip "ferretdb/amd64/ferretdb-amd64" -d .build/bundle
|
||||
mv .build/bundle/ferretdb-amd64 .build/bundle/ferretdb
|
||||
chmod +x .build/bundle/ferretdb
|
||||
# Self-contained launcher (defaults to FerretDB SQLite + WRITABLE_PATH).
|
||||
cp releases/ferretdb/start-wekan.sh .build/bundle/
|
||||
chmod +x .build/bundle/start-wekan.sh
|
||||
|
||||
- name: Create amd64 zip
|
||||
run: |
|
||||
cd .build
|
||||
@@ -321,7 +357,7 @@ jobs:
|
||||
|
||||
# ─── 3. Build arm64 bundle (rebuild native modules natively) ─────────────────
|
||||
build-arm64:
|
||||
needs: [prepare, build-amd64]
|
||||
needs: [prepare, build-amd64, build-ferretdb]
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -360,6 +396,21 @@ jobs:
|
||||
node:${NODE_VERSION}-slim \
|
||||
sh -c "apt-get update -qq && apt-get install -y -q build-essential python3 && npm install"
|
||||
|
||||
- name: Download ferretdb.zip
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ferretdb-zip
|
||||
|
||||
- name: Embed Node.js + FerretDB (arm64) into the bundle
|
||||
# The launcher (start-wekan.sh) is inherited from the amd64 base bundle;
|
||||
# swap bundle/node (runner is arm64 -> linux-arm64 Node 24) and bundle/ferretdb.
|
||||
run: |
|
||||
cp -L "$(command -v node)" bundle/node
|
||||
chmod +x bundle/node
|
||||
unzip -o -j ferretdb.zip "ferretdb/arm64/ferretdb-arm64" -d bundle
|
||||
mv bundle/ferretdb-arm64 bundle/ferretdb
|
||||
chmod +x bundle/ferretdb
|
||||
|
||||
- name: Create arm64 zip
|
||||
run: |
|
||||
zip -r wekan-${VERSION}-arm64.zip bundle
|
||||
@@ -376,6 +427,7 @@ jobs:
|
||||
- prepare
|
||||
- build-amd64
|
||||
- build-arm64
|
||||
- build-ferretdb
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
@@ -392,6 +444,10 @@ jobs:
|
||||
tag_name: v${{ needs.prepare.outputs.version }}
|
||||
name: v${{ needs.prepare.outputs.version }}
|
||||
body: ${{ needs.prepare.outputs.changelog }}
|
||||
# ferretdb.zip is NOT attached here: it already lives on the
|
||||
# wekan/FerretDB releases (https://github.com/wekan/FerretDB/releases),
|
||||
# which is where the WeKan builds fetch it from. The per-arch FerretDB
|
||||
# binaries are embedded inside each wekan-<version>-<arch>.zip bundle.
|
||||
files: |
|
||||
wekan-${{ needs.prepare.outputs.version }}-amd64.zip
|
||||
wekan-${{ needs.prepare.outputs.version }}-arm64.zip
|
||||
@@ -406,7 +462,7 @@ jobs:
|
||||
|
||||
# Build win64 bundle (rebuild native modules on Windows).
|
||||
build-win64:
|
||||
needs: [prepare, build-amd64, release]
|
||||
needs: [prepare, build-amd64, release, build-ferretdb]
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -436,6 +492,10 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
7z x wekan-${VERSION}-amd64.zip
|
||||
# Drop the Linux node + FerretDB binaries + POSIX launcher inherited from
|
||||
# the amd64 base bundle; the Windows node.exe, ferretdb.exe and
|
||||
# start-wekan.bat are added in a later step.
|
||||
rm -f bundle/node bundle/ferretdb bundle/.ferretdb-default bundle/start-wekan.sh
|
||||
# Fix: bcrypt 5.0.1 (Meteor accounts-password) ships node-addon-api@3
|
||||
# which fails to compile on Node.js 24. Replace with bcrypt 6.0.0.
|
||||
BCRYPT_DIR="$(pwd)/bundle/programs/server/npm/node_modules/meteor/accounts-password/node_modules/bcrypt"
|
||||
@@ -492,6 +552,34 @@ jobs:
|
||||
echo "=== bundle contents ==="
|
||||
ls -1 bundle/
|
||||
|
||||
- name: Download ferretdb.zip
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ferretdb-zip
|
||||
|
||||
- name: Embed Node.js, FerretDB (win64) and launcher into the bundle
|
||||
shell: bash
|
||||
# Make the Windows .zip self-contained: bundle node.exe (from the runner's
|
||||
# win-x64 Node 24), ferretdb.exe (from the single-source ferretdb.zip) and
|
||||
# start-wekan.bat. FerretDB embedding is tolerant: if it did not compile for
|
||||
# windows/amd64 the bundle stays MongoDB-only (start-wekan.bat then won't
|
||||
# find ferretdb.exe).
|
||||
run: |
|
||||
# Bundled Node.js (win-x64 node.exe from setup-node).
|
||||
NODE_SRC="$(command -v node)"
|
||||
[ -f "$NODE_SRC" ] || NODE_SRC="${NODE_SRC}.exe"
|
||||
cp "$NODE_SRC" bundle/node.exe
|
||||
# Bundled FerretDB (win64).
|
||||
if 7z e -y ferretdb.zip "ferretdb/win64/ferretdb-win64.exe" -obundle > /dev/null 2>&1 \
|
||||
&& [ -f bundle/ferretdb-win64.exe ]; then
|
||||
mv bundle/ferretdb-win64.exe bundle/ferretdb.exe
|
||||
echo "Embedded FerretDB win64 -> bundle/ferretdb.exe"
|
||||
else
|
||||
echo "No win64 FerretDB in ferretdb.zip; Windows bundle stays MongoDB-only."
|
||||
fi
|
||||
# Self-contained Windows launcher (defaults to FerretDB SQLite).
|
||||
cp src/releases/ferretdb/start-wekan.bat bundle/
|
||||
|
||||
- name: Create win64 zip
|
||||
shell: cmd
|
||||
run: 7z a wekan-%VERSION%-win64.zip bundle
|
||||
@@ -514,7 +602,7 @@ jobs:
|
||||
|
||||
# Build mac-arm64 bundle (rebuild native modules on Apple Silicon).
|
||||
build-mac-arm64:
|
||||
needs: [prepare, build-amd64, release]
|
||||
needs: [prepare, build-amd64, release, build-ferretdb]
|
||||
runs-on: macos-14 # macos-14 runners are Apple Silicon (arm64)
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -532,6 +620,10 @@ jobs:
|
||||
- name: Rebuild native modules for macOS arm64
|
||||
run: |
|
||||
unzip -q wekan-${VERSION}-amd64.zip
|
||||
# Replace the Linux node + FerretDB binaries inherited from the amd64 base
|
||||
# bundle with the macOS builds (added in a later step). The POSIX launcher
|
||||
# start-wekan.sh works on macOS, so keep it.
|
||||
rm -f bundle/node bundle/ferretdb bundle/.ferretdb-default
|
||||
# Fix: bcrypt 5.0.1 (Meteor accounts-password) ships node-addon-api@3
|
||||
# which fails to compile on Node.js 24. Replace with bcrypt 6.0.0.
|
||||
BCRYPT_DIR="$(pwd)/bundle/programs/server/npm/node_modules/meteor/accounts-password/node_modules/bcrypt"
|
||||
@@ -551,6 +643,29 @@ jobs:
|
||||
npm install
|
||||
cd ../../..
|
||||
|
||||
- name: Download ferretdb.zip
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ferretdb-zip
|
||||
|
||||
- name: Embed Node.js + FerretDB (mac-arm64) into the bundle
|
||||
# Bundle the macOS arm64 Node.js (runner's darwin-arm64 Node 24) and the
|
||||
# macOS FerretDB binary from the single-source ferretdb.zip. FerretDB is
|
||||
# tolerant: if it did not compile for darwin/arm64 the bundle stays
|
||||
# MongoDB-only (start-wekan.sh then won't find ferretdb).
|
||||
run: |
|
||||
cp -L "$(command -v node)" bundle/node
|
||||
chmod +x bundle/node
|
||||
if unzip -o -j ferretdb.zip "ferretdb/mac-arm64/ferretdb-mac-arm64" -d bundle >/dev/null 2>&1 \
|
||||
&& [ -f bundle/ferretdb-mac-arm64 ]; then
|
||||
mv bundle/ferretdb-mac-arm64 bundle/ferretdb
|
||||
chmod +x bundle/ferretdb
|
||||
echo "Embedded FerretDB mac-arm64 -> bundle/ferretdb"
|
||||
else
|
||||
echo "No mac-arm64 FerretDB in ferretdb.zip; macOS bundle stays MongoDB-only."
|
||||
rm -f bundle/start-wekan.sh
|
||||
fi
|
||||
|
||||
- name: Create mac-arm64 zip
|
||||
run: zip -r wekan-${VERSION}-mac-arm64.zip bundle
|
||||
|
||||
@@ -568,13 +683,49 @@ jobs:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: gh release upload "v${VERSION}" "wekan-${VERSION}-mac-arm64.zip" --clobber
|
||||
|
||||
# Extra-architecture Linux bundles (s390x, ppc64le).
|
||||
# armv7l (32-bit ARM) is intentionally excluded: Node.js 24 ships no armv7l
|
||||
# binaries (official or unofficial), so there is no node:24 linux/arm/v7 image
|
||||
# to rebuild native modules against and no Node 24 runtime to run a bundle on
|
||||
# an armv7 device. See https://nodejs.org/dist/latest-v24.x/ (no -linux-armv7l).
|
||||
# ─── Fetch the prebuilt standalone FerretDB binaries: ferretdb.zip ───────────
|
||||
# WeKan normally uses MongoDB, but MongoDB Community only ships amd64/arm64
|
||||
# server binaries. On every other platform WeKan can instead use FerretDB v1
|
||||
# (the wekan/FerretDB fork with its embedded pure-Go SQLite backend, which
|
||||
# speaks the MongoDB wire protocol). FerretDB is built + released SEPARATELY in
|
||||
# the wekan/FerretDB repo (its build.sh cross-compiles every platform into
|
||||
# ferretdb.zip and it is attached to a GitHub Release there); this workflow does
|
||||
# NOT build FerretDB with Go. Here we just download the newest released
|
||||
# ferretdb.zip and republish it, so every WeKan build (bundle .zip, snap, docker,
|
||||
# win64, mac) takes its FerretDB binary from that same single source. The zip
|
||||
# layout is ferretdb/<arch>/ferretdb-<arch>[.exe] + ferretdb/README.md.
|
||||
build-ferretdb:
|
||||
needs: prepare
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Download the latest ferretdb.zip from wekan/FerretDB releases
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# /releases/latest/download/<asset> always resolves to the asset on the
|
||||
# newest (non-prerelease) release of wekan/FerretDB.
|
||||
url="https://github.com/wekan/FerretDB/releases/latest/download/ferretdb.zip"
|
||||
echo "Downloading ${url} ..."
|
||||
curl -fSL --retry 5 --retry-delay 10 -o ferretdb.zip "$url"
|
||||
echo "=== ferretdb.zip contents ==="
|
||||
unzip -l ferretdb.zip
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ferretdb-zip
|
||||
path: ferretdb.zip
|
||||
retention-days: 1
|
||||
|
||||
# Extra-architecture Linux bundles (ppc64le, s390x, riscv64) — the arches that
|
||||
# have a Node.js 24 build but NO MongoDB server, so they ship FerretDB instead.
|
||||
# ppc64le and s390x use official nodejs.org binaries; riscv64 uses the
|
||||
# unofficial-builds.nodejs.org binary (nodejs.org ships no riscv64). armv7l
|
||||
# (32-bit ARM) is intentionally excluded: there is NO Node.js 24 build for it
|
||||
# anywhere (official or unofficial), so there is no runtime to run the bundle on
|
||||
# an armv7 device, regardless of RAM. See https://nodejs.org/dist/latest-v24.x/
|
||||
# (no -linux-armv7l) and https://unofficial-builds.nodejs.org/ (riscv64 only).
|
||||
build-extra-arches:
|
||||
needs: [prepare, build-amd64, release]
|
||||
needs: [prepare, build-amd64, release, build-ferretdb]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 300
|
||||
strategy:
|
||||
@@ -583,8 +734,13 @@ jobs:
|
||||
include:
|
||||
- arch: s390x
|
||||
platform: linux/s390x
|
||||
node_base: official
|
||||
- arch: ppc64le
|
||||
platform: linux/ppc64le
|
||||
node_base: official
|
||||
- arch: riscv64
|
||||
platform: linux/riscv64
|
||||
node_base: unofficial
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
@@ -620,11 +776,60 @@ jobs:
|
||||
popd > /dev/null
|
||||
rm -rf "$TMP"
|
||||
fi
|
||||
# The official node:${NODE_VERSION} Docker image only publishes amd64,
|
||||
# arm64 and ppc64le manifests — no s390x, no riscv64 — so
|
||||
# `docker run --platform linux/s390x node:24-slim` fails with "no matching
|
||||
# manifest". Instead build on an ubuntu:26.04 base (which publishes every
|
||||
# arch) and install the Node.js binary tarball inside the emulated
|
||||
# container: ppc64le/s390x from nodejs.org (official), riscv64 from
|
||||
# unofficial-builds.nodejs.org (nodejs.org ships no riscv64). Resolve the
|
||||
# exact latest ${NODE_VERSION}.x version on the host (fast, no emulation)
|
||||
# and pass it in along with which mirror to use.
|
||||
NODE_FULL=$(curl -fsSL https://nodejs.org/dist/index.json \
|
||||
| python3 -c "import json,sys; print(next(r['version'] for r in json.load(sys.stdin) if r['version'].startswith('v${NODE_VERSION}.')))")
|
||||
echo "Using Node ${NODE_FULL} (${{ matrix.node_base }}) for ${{ matrix.arch }}"
|
||||
docker run --rm --platform ${{ matrix.platform }} \
|
||||
-e NODE_FULL="$NODE_FULL" -e ARCH="${{ matrix.arch }}" \
|
||||
-e NODE_BASE="${{ matrix.node_base }}" \
|
||||
-v "$(pwd)/bundle:/bundle" \
|
||||
-w /bundle/programs/server \
|
||||
node:${NODE_VERSION}-slim \
|
||||
sh -c "apt-get update -qq && apt-get install -y -q build-essential python3 && npm install"
|
||||
ubuntu:26.04 \
|
||||
bash -c '
|
||||
set -eux
|
||||
apt-get update -qq
|
||||
apt-get install -y -q build-essential python3 curl xz-utils ca-certificates
|
||||
if [ "$NODE_BASE" = unofficial ]; then
|
||||
NODE_URL="https://unofficial-builds.nodejs.org/download/release/${NODE_FULL}/node-${NODE_FULL}-linux-${ARCH}.tar.xz"
|
||||
else
|
||||
NODE_URL="https://nodejs.org/dist/${NODE_FULL}/node-${NODE_FULL}-linux-${ARCH}.tar.xz"
|
||||
fi
|
||||
curl -fsSL -o /tmp/node.tar.xz "$NODE_URL"
|
||||
mkdir -p /opt/node
|
||||
tar -xJf /tmp/node.tar.xz -C /opt/node --strip-components=1
|
||||
export PATH=/opt/node/bin:$PATH
|
||||
node --version
|
||||
npm install
|
||||
# Bundle the target-arch Node.js binary for the self-contained
|
||||
# launcher. /bundle is the host bundle/ dir (see -v mount above).
|
||||
cp /opt/node/bin/node /bundle/node
|
||||
chmod +x /bundle/node
|
||||
'
|
||||
|
||||
- name: Download ferretdb.zip
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ferretdb-zip
|
||||
|
||||
- name: Embed FerretDB (${{ matrix.arch }}) into the bundle
|
||||
# These arches have no MongoDB server, so FerretDB is their default DB.
|
||||
# The launcher (start-wekan.sh) is inherited from the amd64 base
|
||||
# bundle; swap in this arch's binary from ferretdb.zip. A marker file makes
|
||||
# FerretDB the default backend at runtime (see the launcher + Docker entrypoint).
|
||||
run: |
|
||||
unzip -o -j ferretdb.zip "ferretdb/${{ matrix.arch }}/ferretdb-${{ matrix.arch }}" -d bundle
|
||||
mv bundle/ferretdb-${{ matrix.arch }} bundle/ferretdb
|
||||
chmod +x bundle/ferretdb
|
||||
touch bundle/.ferretdb-default
|
||||
|
||||
- name: Create ${{ matrix.arch }} zip
|
||||
run: zip -r wekan-${VERSION}-${{ matrix.arch }}.zip bundle
|
||||
@@ -647,9 +852,11 @@ jobs:
|
||||
# Mirrors releases/docker-build.sh step 3 (multi-arch buildx --push).
|
||||
# Needs `release` because the Dockerfile downloads wekan-<version>-<arch>.zip
|
||||
# from the GitHub Release; building before those assets exist 404s and (without
|
||||
# set -e in the Dockerfile) shipped a broken image missing /build/main.js.
|
||||
# set -e in the Dockerfile) shipped a broken image missing /build/main.js. Also
|
||||
# needs `build-extra-arches` because the image now covers ppc64le/s390x/riscv64,
|
||||
# whose zips (with FerretDB baked in) are attached by that job after `release`.
|
||||
docker:
|
||||
needs: [prepare, release]
|
||||
needs: [prepare, release, build-extra-arches]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
@@ -687,14 +894,14 @@ jobs:
|
||||
# Release for each built platform. Even though this job needs `release`
|
||||
# (so the assets are uploaded), GitHub's releases/download/<tag>/<asset>
|
||||
# URL can briefly 404 right after upload (CDN/propagation lag), which used
|
||||
# to fail the multi-arch build mid-way. Poll here until both the amd64 and
|
||||
# arm64 bundles return 200, so the (expensive, emulated) buildx run only
|
||||
# starts once the assets are actually fetchable. The Dockerfile also
|
||||
# to fail the multi-arch build mid-way. Poll here until every arch bundle
|
||||
# baked into the image returns 200, so the (expensive, emulated) buildx run
|
||||
# only starts once the assets are actually fetchable. The Dockerfile also
|
||||
# retries per-arch as a second layer of safety.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base="https://github.com/wekan/wekan/releases/download/v${VERSION}"
|
||||
for arch in amd64 arm64; do
|
||||
for arch in amd64 arm64 ppc64le s390x riscv64; do
|
||||
url="${base}/wekan-${VERSION}-${arch}.zip"
|
||||
echo "Waiting for ${url} ..."
|
||||
ok=0
|
||||
@@ -771,7 +978,7 @@ jobs:
|
||||
# report in the Admin Panel — a stale, different version.
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--platform linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/riscv64 \
|
||||
--build-arg VERSION=${VERSION} \
|
||||
-t wekanteam/wekan:v${VERSION} \
|
||||
-t wekanteam/wekan:latest \
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
name: Build Sandstorm .spk
|
||||
|
||||
# Manually triggered. Builds ONLY the Sandstorm .spk package (nothing else) and
|
||||
# uploads it as wekan-sandstorm-YYYY_MM_DD-HH_MM_SS.spk to the NEWEST WeKan GitHub
|
||||
# Release as an attachment, so you can download it, test whether the Sandstorm
|
||||
# build works, and see any errors — without running a full release.
|
||||
#
|
||||
# gh workflow run sandstorm.yml
|
||||
#
|
||||
# EXPERIMENTAL in CI (same caveats as the build-sandstorm job in release-all.yml):
|
||||
# * Sandstorm needs unprivileged user namespaces; GitHub's Ubuntu runners are VMs
|
||||
# that allow them, but this is the fragile part.
|
||||
# * meteor-spk SIGNS the .spk with the app private key from the Sandstorm keyring;
|
||||
# provide it as the SANDSTORM_KEYRING secret (base64 of ~/.sandstorm keyring).
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
NODE_VERSION: '24'
|
||||
|
||||
jobs:
|
||||
sandstorm:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install system dependencies and Meteor
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y p7zip-full wget curl build-essential g++ make python3 unzip
|
||||
if [ ! -f "$HOME/.meteor/meteor" ]; then curl https://install.meteor.com/ | sh; fi
|
||||
echo "$HOME/.meteor" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install meteor-spk
|
||||
run: |
|
||||
cd "$HOME"
|
||||
wget -q https://releases.wekan.team/dev/meteor-spk/projects.7z
|
||||
7z x projects.7z >/dev/null
|
||||
rm -f projects.7z
|
||||
echo "$HOME/projects/meteor-spk/meteor-spk-0.6.0" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install Sandstorm (dev, unattended)
|
||||
run: curl https://install.sandstorm.io | sudo bash -s -- -d
|
||||
|
||||
- name: Restore the Sandstorm signing keyring
|
||||
env:
|
||||
SANDSTORM_KEYRING: ${{ secrets.SANDSTORM_KEYRING }}
|
||||
run: |
|
||||
if [ -n "${SANDSTORM_KEYRING:-}" ]; then
|
||||
mkdir -p "$HOME/.sandstorm"
|
||||
printf '%s' "$SANDSTORM_KEYRING" | base64 -d > "$HOME/.sandstorm/sandstorm-keyring"
|
||||
chmod 600 "$HOME/.sandstorm/sandstorm-keyring"
|
||||
else
|
||||
echo "::warning::SANDSTORM_KEYRING secret not set; meteor-spk pack may fail to sign the app update."
|
||||
fi
|
||||
|
||||
- name: Install Meteor npm dependencies
|
||||
run: |
|
||||
(meteor update --npm 2>/dev/null || true) && meteor npm install
|
||||
|
||||
- name: Build the Sandstorm .spk
|
||||
id: build
|
||||
run: |
|
||||
set -euo pipefail
|
||||
STAMP="$(date +'%Y_%m_%d-%H_%M_%S')"
|
||||
SPK="wekan-sandstorm-${STAMP}.spk"
|
||||
meteor-spk pack "$SPK"
|
||||
ls -la "$SPK"
|
||||
echo "spk=$SPK" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ steps.build.outputs.spk }}
|
||||
path: ${{ steps.build.outputs.spk }}
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload the .spk to the newest WeKan release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="$(gh release list --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || true)"
|
||||
if [ -z "${TAG:-}" ] || [ "$TAG" = "null" ]; then
|
||||
echo "::error::No existing GitHub Release found to attach the .spk to."
|
||||
exit 1
|
||||
fi
|
||||
echo "Uploading ${{ steps.build.outputs.spk }} to the newest release: $TAG"
|
||||
gh release upload "$TAG" "${{ steps.build.outputs.spk }}" --clobber
|
||||
+818
-2
@@ -34,10 +34,826 @@ them up next.
|
||||
[#4560](https://github.com/wekan/wekan/issues/4560) (LDAP→OIDC), [#3707](https://github.com/wekan/wekan/issues/3707)
|
||||
& [#3700](https://github.com/wekan/wekan/issues/3700) (LDAP), [#3575](https://github.com/wekan/wekan/issues/3575)
|
||||
(WebHooks), [#1192](https://github.com/wekan/wekan/issues/1192) (Sandstorm).
|
||||
- **LDAP security/config that needs a real LDAP directory to verify safely (an unverified auth change could lock users out):**
|
||||
[#4419](https://github.com/wekan/wekan/issues/4419) (after password→LDAP migration the old local password still
|
||||
works — the safe fix is a `validateLoginAttempt` hook that rejects password-service logins for `authenticationMethod:'ldap'`
|
||||
users, or `$unset services.password` on migration; both must be tested against a directory before shipping),
|
||||
[#4158](https://github.com/wekan/wekan/issues/4158) (LDAP_ENCRYPTION naming/docs: accept `starttls`/`true`, deprecate
|
||||
`tls`/`ssl`, validate unknown values).
|
||||
- **Need the running app to reproduce/verify (runtime UI or publication/mergebox state), not unit-testable here:**
|
||||
[#4959](https://github.com/wekan/wekan/issues/4959) & [#4825](https://github.com/wekan/wekan/issues/4825) (per-list
|
||||
card counts on the All Boards page — the `boardLists`/`boardMembers` helpers in `client/components/boards/boardsList.js`
|
||||
were deliberately stubbed to `[]` to stop the #4214 "icons random dance"; re-enabling needs a non-reactive count source
|
||||
and a lists/cards subscription for the dashboard, verified live so #4214 does not return),
|
||||
[#4822](https://github.com/wekan/wekan/issues/4822) (maximized card mis-positioned after scrolling in swimlanes view),
|
||||
[#3826](https://github.com/wekan/wekan/issues/3826) (cannot reorder cards in a list full of subtask cards — drag-drop,
|
||||
data-loss risk), [#5282](https://github.com/wekan/wekan/issues/5282) & [#3252](https://github.com/wekan/wekan/issues/3252)
|
||||
("Removed nonexistent document" on attachment / comment / checklist delete — a publication/mergebox double-remove, same
|
||||
class as the fixed #5685, not the delete itself),
|
||||
[#4897](https://github.com/wekan/wekan/issues/4897) (OAuth2 users intermittently show stale/missing data — caching/
|
||||
publication timing), [#3276](https://github.com/wekan/wekan/issues/3276) (excessive-time card colour does not refresh to
|
||||
red until focus leaves the card — reactive re-render), [#3189](https://github.com/wekan/wekan/issues/3189) (Worker-role
|
||||
user cannot re-assign a card to themselves after a prior assignee was removed — role/deny permission),
|
||||
[#3576](https://github.com/wekan/wekan/issues/3576) (mobile back button after search returns to settings, not the board),
|
||||
[#3453](https://github.com/wekan/wekan/issues/3453) (cross-board subtask full-path label vanishes after refresh — router),
|
||||
[#3378](https://github.com/wekan/wekan/issues/3378) (SyncedCron "duplicate key" in cronHistory for notification_cleanup —
|
||||
scheduler/dev-reload timing), [#3199](https://github.com/wekan/wekan/issues/3199) (Archive Restore/Delete links need
|
||||
visual separation from neighbouring cards — CSS refinement),
|
||||
[#3144](https://github.com/wekan/wekan/issues/3144) (archived-card activities render "undefined" in board settings —
|
||||
the card lookup returns nothing once the card is archived/unpublished; needs the activity to carry a stored card title
|
||||
or the feed to fall back to "{title} [archived]", verified live),
|
||||
[#3114](https://github.com/wekan/wekan/issues/3114) (mobile card view stays open but blank after another client
|
||||
deletes/moves the card — reactive close-on-remove), [#3037](https://github.com/wekan/wekan/issues/3037) (after moving a
|
||||
card between boards via the REST API, opening it bounces back to the old board — the raw boardId/listId update does not
|
||||
go through the full Card.move() re-home; the dedicated REST move/sort/date/archive path was fixed in #5398/#5399/#5537/
|
||||
#5546, so use that rather than a raw field update), [#3070](https://github.com/wekan/wekan/issues/3070) (using a
|
||||
template from Add Board / the top bar — issue body would not load; needs the app to reproduce).
|
||||
- **Already correct in the current code (could not reproduce; endpoint/logic verified by reading):**
|
||||
[#4774](https://github.com/wekan/wekan/issues/4774) (`POST /users/register` is a native handler that returns 403 only
|
||||
when registration is disabled via `forbidClientAccountCreation`; it works by default),
|
||||
[#4055](https://github.com/wekan/wekan/issues/4055) (ISO week number — the native DST-safe `getISOWeek()` is correct;
|
||||
a regression test was added this release).
|
||||
- **Incomplete report (no reproduction details provided by the reporter):**
|
||||
[#4593](https://github.com/wekan/wekan/issues/4593) (new team member permissions — issue body empty).
|
||||
- **Feature requests / behaviour-by-design rather than bugs:**
|
||||
[#5547](https://github.com/wekan/wekan/issues/5547), [#3843](https://github.com/wekan/wekan/issues/3843),
|
||||
[#3823](https://github.com/wekan/wekan/issues/3823), [#3138](https://github.com/wekan/wekan/issues/3138),
|
||||
[#5547](https://github.com/wekan/wekan/issues/5547), [#3843](https://github.com/wekan/wekan/issues/3843)
|
||||
(attachments added in comments not shown in the card attachment list),
|
||||
[#3828](https://github.com/wekan/wekan/issues/3828) (board members seeing all cards on a board is by design — WeKan has
|
||||
no per-card visibility model), [#3823](https://github.com/wekan/wekan/issues/3823),
|
||||
[#3748](https://github.com/wekan/wekan/issues/3748) (linked cards mirror the original rather than owning their own
|
||||
labels/custom fields), [#4023](https://github.com/wekan/wekan/issues/4023) (Japanese font alignment → per-board font
|
||||
request), [#4043](https://github.com/wekan/wekan/issues/4043) (invitation email occasionally sent without a valid
|
||||
code), [#3138](https://github.com/wekan/wekan/issues/3138),
|
||||
[#2204](https://github.com/wekan/wekan/issues/2204) (restrict permanent delete to the Admin role).
|
||||
- **Needs a maintainer decision on the intended contract (partly already works):**
|
||||
[#4912](https://github.com/wekan/wekan/issues/4912) (a global `act-editCard` webhook — card title/description edits
|
||||
ALREADY reach the global webhook via `Activities.after.insert` as `act-a-changedTitle` / `act-a-changedDescription`
|
||||
from #3619/#5482; a single consolidated `act-editCard` action needs a decision on which fields count and whether it
|
||||
supplements or replaces the existing per-field events, to avoid duplicate webhook deliveries),
|
||||
[#3113](https://github.com/wekan/wekan/issues/3113) (webhook `user` field shows the full name, not the username — the
|
||||
same `params.user` feeds both the e-mail notification text, where the full name is intended, and the webhook payload,
|
||||
where a username is expected; the safe change is to ADD a `username` field to the webhook rather than repurpose `user`).
|
||||
|
||||
# v9.84 2026-07-11 WeKan ® release
|
||||
|
||||
This release adds the following features and fixes:
|
||||
|
||||
- **Fix scheduled backup cron init crashing at server startup on Meteor 3**:
|
||||
|
||||
The scheduled-backup cron used Meteor's synchronous Mongo API
|
||||
(`BackupSettings.findOne`/`upsert`), which Meteor 3 no longer allows on the
|
||||
server — startup logged `findOne is not available on the server. Please use
|
||||
findOneAsync() instead.` and the cron silently never registered, so scheduled
|
||||
backups did not run. `registerCron()` is now `async` and uses `findOneAsync`,
|
||||
the `getBackupSchedule`/`saveBackupSchedule` methods use `findOneAsync`/
|
||||
`upsertAsync`, and every `registerCron()` caller awaits it. Added
|
||||
`tests/backupCron.test.cjs` (positive + negative cron-registration tests and a
|
||||
source guard that fails if the synchronous server-forbidden API is
|
||||
reintroduced), wired into the `test:unit:node` suite.
|
||||
|
||||
- **Snap: show the correct writable path for parallel installs in backup instructions**:
|
||||
|
||||
The backup/migration help text printed by `wekan.help` hardcoded
|
||||
`/var/snap/wekan/common/files`, which is wrong for a parallel snap install
|
||||
(e.g. `wekan_customer`, whose data lives under `/var/snap/wekan_customer/common`).
|
||||
It now prints `$SNAP_COMMON/files`, which snapd sets per instance. Display-only;
|
||||
all functional snap paths already derive from `$SNAP_COMMON`/`$SNAP_DATA`, so
|
||||
parallel installs were already fully supported.
|
||||
|
||||
- **Fix flaky server-side Mocha i18n test (TAPi18n `.loadLanguage`)**:
|
||||
|
||||
The `.loadLanguage` suite stubbed `addResourceBundle` on the shared
|
||||
`TAPi18n.i18n` singleton and asserted the call count. Because `loadLanguage()`
|
||||
reads `this.i18n` late and each test re-`init()`s the singleton, cross-test
|
||||
state could leave the stub watching a different i18next instance than the one
|
||||
the bundle was registered on, so the count read 0 and the run intermittently
|
||||
reported `expected addResourceBundle to be called once`. The tests now assert
|
||||
on observable i18next state (`hasResourceBundle`/`getResourceBundle` under the
|
||||
normalised `toI18nCode`), which is instance-agnostic and deterministic and
|
||||
mirrors the reliable `.setLanguage` suite. Also stopped a leaked
|
||||
`Tracker.autorun` in the `.getLanguage` reactive test (the cross-test hazard).
|
||||
|
||||
- **[Fix rebuild-all.yml s390x build; add bundled FerretDB v1 for ppc64le, s390x, riscv64 and to the Docker image](https://github.com/wekan/wekan/commit/0360ca1583b4b17b465ded20c4fd7560004aee47)**:
|
||||
|
||||
The extra-arch bundle build ran `node:24-slim` under QEMU, but the official
|
||||
`node:24` image publishes no `linux/s390x` manifest (only amd64/arm64/ppc64le),
|
||||
so the s390x leg failed with "no matching manifest for linux/s390x". The
|
||||
emulated native-module rebuild now runs on an `ubuntu:26.04` base (which
|
||||
publishes every arch) and installs Node.js from nodejs.org; riscv64 uses
|
||||
unofficial-builds.nodejs.org (nodejs.org ships no riscv64).
|
||||
|
||||
MongoDB Community only ships amd64/arm64 server binaries, so on ppc64le, s390x
|
||||
and riscv64 — the other architectures with a Node.js 24 build — WeKan now
|
||||
bundles FerretDB v1 (the [wekan/FerretDB](https://github.com/wekan/FerretDB)
|
||||
fork with its embedded pure-Go SQLite backend, which speaks the MongoDB wire
|
||||
protocol) instead of requiring MongoDB. The FerretDB binary is cross-compiled
|
||||
once for all five architectures (CGO off, static, no QEMU) and embedded in
|
||||
every `.zip` next to `main.js`, so amd64/arm64 users can opt in too with
|
||||
`WEKAN_DB=ferretdb`. The Docker image now covers all five architectures and
|
||||
auto-starts FerretDB on the MongoDB-less ones. FerretDB telemetry is disabled
|
||||
and locked (`--telemetry=disable`, plus `DO_NOT_TRACK`). armv7l/32-bit ARM is
|
||||
still not built: there is no Node.js 24 build for it anywhere, so nothing could
|
||||
run WeKan there regardless of RAM.
|
||||
|
||||
- **[Snap: choose MongoDB or FerretDB v1 with "snap set wekan database=ferretdb"; FerretDB default for new installs; disable mongosh telemetry](https://github.com/wekan/wekan/commit/598aa8dfa1cc406243e0db681573093d9db7783f)**:
|
||||
|
||||
The WeKan snap gains a `database` setting (`mongodb` or `ferretdb`). A new
|
||||
`ferretdb` service runs FerretDB v1 (SQLite) on the same port MongoDB would
|
||||
use, so `MONGO_URL` is unchanged; only one database runs at a time, and the
|
||||
configure hook stops one and starts the other when the setting changes. Switch
|
||||
with `snap set wekan database=ferretdb` (or back with `database=mongodb`).
|
||||
|
||||
New snap installs default to FerretDB on all platforms (via the install hook,
|
||||
which runs only on fresh installs — upgrades keep MongoDB, and a pre-existing
|
||||
MongoDB data directory is detected and kept so no data is lost). There is no
|
||||
`snap install --db=ferretdb` flag (snapd has no custom install options); use
|
||||
the two-step `snap install wekan --channel=latest/beta` then
|
||||
`snap set wekan database=ferretdb`.
|
||||
|
||||
mongosh collects anonymized usage analytics by default and the snap invokes it
|
||||
several times; it is now disabled so nothing phones home. mongod itself has no
|
||||
phone-home telemetry (Cloud Free Monitoring is opt-in and stays off).
|
||||
|
||||
- **[Admin Panel / Version: show database type, version, commit, storage engine and reactivity mode](https://github.com/wekan/wekan/commit/d5c69e02595c9a4caf597496a4f1572064142b9f)**:
|
||||
|
||||
Admin Panel / Version now shows whether WeKan is using MongoDB or FerretDB v1
|
||||
(SQLite), the MongoDB-compatible version and the server git commit, FerretDB's
|
||||
own version and commit when FerretDB is in use, the storage engine, and which
|
||||
reactivity mechanism is currently in use — changeStreams / oplog / polling
|
||||
(FerretDB has no oplog, so it uses polling).
|
||||
|
||||
- **[Standalone ferretdb.zip for all platforms, and rebuild-all.yml uses the prebuilt one from wekan/FerretDB releases](https://github.com/wekan/wekan/commit/3824066f7fb88acd66bc1f4c70350e8516f5f00b)**:
|
||||
|
||||
FerretDB v1 (the wekan/FerretDB fork, pure-Go SQLite backend, CGO off, no QEMU)
|
||||
is now cross-compiled for every platform Go and modernc.org/sqlite support — far
|
||||
beyond the arches Node.js ships — and packed into a single `ferretdb.zip`:
|
||||
|
||||
ferretdb/<arch>/ferretdb-<arch> (Linux/macOS/BSD, executable)
|
||||
ferretdb/<arch>/ferretdb-<arch>.exe (Windows only)
|
||||
ferretdb/README.md (links to https://github.com/wekan/FerretDB)
|
||||
|
||||
That zip is produced and released in the wekan/FerretDB repo (by its `build.sh`,
|
||||
which gained sequential and parallel "Build ferretdb.zip" menu options). The
|
||||
WeKan release workflow no longer builds FerretDB with Go: it downloads the newest
|
||||
`ferretdb.zip` from https://github.com/wekan/FerretDB/releases and every WeKan
|
||||
build (the bundle .zip for each arch, and via the bundle the Docker image and
|
||||
snap, including the Windows and macOS bundles) embeds its per-arch binary from
|
||||
that one source. ferretdb.zip is not re-attached to the WeKan releases.
|
||||
|
||||
- **[Fix #6445: dynamic-import chunks 404 under a sub-path (duplicated build-chunks/build-chunks/)](https://github.com/wekan/wekan/commit/dbca7cd72d4f9791ba109dc25d359cce1e849e3a)**:
|
||||
|
||||
Under a sub-path deployment (ROOT_URL like `https://host/wekan`, usually behind
|
||||
a reverse proxy that strips the prefix), language selection and other
|
||||
lazy-loaded features failed with `ENOENT ... build-chunks/build-chunks/<id>.js`.
|
||||
rspack's client runtime builds each chunk URL as public-path + chunk-name, and
|
||||
the chunk name already carries the `build-chunks/` prefix, but
|
||||
client/00-startup.js set the sub-path public path to `<sub-path>/build-chunks/`,
|
||||
so rspack appended a second `build-chunks/`. It now sets the public path to just
|
||||
`<sub-path>/` and lets rspack add `build-chunks/` itself.
|
||||
|
||||
- **[Add snapcraft-core24.yaml so the newest WeKan can be published to the Snap Stable channel](https://github.com/wekan/wekan/commit/3a968052b96d4778797e175ad17205260535cbbe)**:
|
||||
|
||||
The main `snapcraft.yaml` uses `base: core26`, which (until core26 is released)
|
||||
needs `build-base: devel` + `grade: devel`, so it can only go to Snap Beta/Edge.
|
||||
`snapcraft-core24.yaml` builds the SAME newest WeKan (Meteor 3.5, Node.js 24,
|
||||
FerretDB v1, MongoDB 7, Caddy 2) on `base: core24` (a released base, `grade:
|
||||
stable`), so the Snap Stable channel can finally be updated from the old 6.09
|
||||
snap. Only base/build-base/grade differ.
|
||||
|
||||
- **[Self-contained release bundles: bundle Node.js + FerretDB + start-wekan.{sh,bat}](https://github.com/wekan/wekan/commit/bfb3fd246bc348900fa1fbda732dcb80afb027d1)**:
|
||||
|
||||
Each `wekan-<version>-<arch>.zip` is now fully offline. Its `bundle/` directory
|
||||
contains the WeKan server, a Node.js binary for that platform, a FerretDB v1
|
||||
(SQLite) binary, and a `start-wekan.sh` (`start-wekan.bat` on Windows) that by
|
||||
default runs WeKan on the bundled Node against the bundled FerretDB SQLite,
|
||||
storing data and attachments/avatars on the filesystem under `WRITABLE_PATH`
|
||||
(as in the Windows Offline guide) — no separate Node or database install
|
||||
needed. The Docker image and snap, which have their own Node and entrypoint,
|
||||
strip the redundant bundled Node + launchers to stay small.
|
||||
|
||||
- **[Standalone sandstorm.yml workflow to build + attach the .spk (Sandstorm removed from release-all.yml)](https://github.com/wekan/wekan/commit/5757cfc8ca4f0757206e668f80f9b12d91bafb27)**:
|
||||
|
||||
Sandstorm packaging (mirroring `releases/release-sandstorm.sh` +
|
||||
`install-sandstorm.sh`: installs Meteor, meteor-spk 0.6.0 and a dev Sandstorm,
|
||||
runs `meteor-spk pack`) is **not tested well enough yet**, so it no longer runs
|
||||
as part of a full release — the `build-sandstorm` job was removed from
|
||||
`release-all.yml`. Instead a separate, manually-triggered `sandstorm.yml` builds
|
||||
ONLY the `.spk` and uploads it as `wekan-sandstorm-YYYY_MM_DD-HH_MM_SS.spk` to the
|
||||
newest WeKan GitHub Release (plus a workflow artifact), so it can be downloaded
|
||||
and tested for errors without affecting releases. Experimental in CI — Sandstorm
|
||||
needs unprivileged user namespaces, and signing the `.spk` needs the app private
|
||||
key via the `SANDSTORM_KEYRING` secret; `spk publish` / scp upload stay manual.
|
||||
|
||||
- **[Migration: resumable progress in WRITABLE_PATH + compact the old MongoDB after success](https://github.com/wekan/wekan/commit/4c630d4ba87b3a6e582d0898179898416e6e9890)**:
|
||||
|
||||
The MongoDB → FerretDB / GridFS → filesystem migrator (used by the Snap and
|
||||
Sandstorm migrations) now checkpoints progress to
|
||||
`$WRITABLE_PATH/migration-progress.json` after every collection and file phase,
|
||||
so an interrupted migration (snap refresh, Sandstorm grain restart, power loss)
|
||||
**resumes** on restart instead of starting over — skipping collections already
|
||||
copied. Once migration has completed, a later boot reclaims the now-duplicated
|
||||
disk space in the old MongoDB by running `compact` on each source collection
|
||||
(best-effort, once). The existing ROOT_URL progress dashboard and disk-space
|
||||
checks are kept, and the dashboard is restored from the checkpoint on resume.
|
||||
|
||||
- **[Snap: migrate the Caddyfile from Caddy v1 to Caddy v2 format on upgrade](https://github.com/wekan/wekan/commit/748fd2d391ca3ae187c14c1676c682dbebf49eb8)**:
|
||||
|
||||
When upgrading from an old WeKan snap, `$SNAP_COMMON/Caddyfile` may still be in
|
||||
Caddy v1 syntax, which Caddy 2 cannot parse (caddy would fail to start).
|
||||
`caddy-control` now runs a converter before `caddy run` when caddy is enabled:
|
||||
a no-op if the file already parses as Caddy 2, otherwise it backs up the
|
||||
original, converts the common v1 directives (`proxy / TARGET` →
|
||||
`reverse_proxy TARGET`, drop the v1 `websocket`/`transparent` presets Caddy 2
|
||||
does by default, `gzip` → `encode gzip`, strip the `http://` scheme), validates
|
||||
with `caddy adapt`, and only keeps a valid result — falling back to the shipped
|
||||
Caddy 2 template otherwise, so caddy always starts with valid config.
|
||||
|
||||
- **[Rename docker-compose-ferretdb.yml to -v2-postgresql.yml and add -v1-sqlite.yml](https://github.com/wekan/wekan/commit/be5ddd77efdd03753acf73feb577ab615da7d3eb)**:
|
||||
|
||||
`docker-compose-ferretdb.yml` (FerretDB 2 + PostgreSQL) is renamed to
|
||||
`docker-compose-ferretdb-v2-postgresql.yml`, and a new
|
||||
`docker-compose-ferretdb-v1-sqlite.yml` runs WeKan against FerretDB v1 with the
|
||||
embedded SQLite backend — no PostgreSQL or MongoDB — fetching the v1 binary for
|
||||
the container's architecture from the newest wekan/FerretDB release. Both
|
||||
compose files now carry the FULL `wekan` service from `docker-compose.yml`
|
||||
(every documented environment variable and feature); the only differences are
|
||||
database-related (the `ferretdb` service replaces `mongodb`, `MONGO_URL` points
|
||||
at FerretDB, `MONGO_OPLOG_URL` is dropped and reactivity is polling, since
|
||||
FerretDB has no MongoDB change streams / replica-set oplog).
|
||||
|
||||
- **[Snap: one-time MongoDB → FerretDB v1 migration on upgrade, with live progress at ROOT_URL](https://github.com/wekan/wekan/commit/6ab29dc81e44c11a3c813d717090e418652b2599)**:
|
||||
|
||||
On first boot after upgrading an old MongoDB-based WeKan snap, `mongodb-control`
|
||||
hands off to a new `migration-control` before starting mongod. It opens the
|
||||
existing MongoDB data with the right bundled mongod — **mongod 7 for MongoDB 7
|
||||
data (all arches, including the arm64 snaps already on newest WeKan)**, or a
|
||||
bundled **old mongod 3.2 (amd64 only)** for WeKan 6.09 / MongoDB 3.2 data —
|
||||
starts a temporary FerretDB v1 (SQLite), and runs the migrator, which moves text
|
||||
data to FerretDB and **both CollectionFS GridFS and Meteor-Files GridFS
|
||||
attachments+avatars to the filesystem**, shows a **live progress counter at
|
||||
ROOT_URL**, checkpoints to `$SNAP_COMMON` (resumable), and compacts the old
|
||||
MongoDB when done. It then switches the snap to `database=ferretdb`. Idempotent,
|
||||
resumable, never deletes the source data, only switches on success. (The amd64
|
||||
6.09/MongoDB-3.2 path needs testing on real 6.09 data; the arm64/MongoDB-7 path
|
||||
uses the already-bundled mongod 7.)
|
||||
|
||||
- **[Snap: bundle migratemongo (MongoDB 3.2 binaries + old libraries + AVX wrappers) to read 6.09 data](https://github.com/wekan/wekan/commit/dd4611e43f09aff2b793eb70b0dfd0138c39e87f)**:
|
||||
|
||||
Per docs/Backup/Backup.md and https://github.com/wekan/migratemongo, running the
|
||||
old MongoDB tools/server in the snap needs `LC_ALL=C` and their libraries on
|
||||
`LD_LIBRARY_PATH` (`$SNAP/lib/<arch>-linux-gnu`), and the 2016 MongoDB 3.2
|
||||
binaries additionally need old libraries (`libssl`/`libcrypto.so.1.0.0`,
|
||||
`libpng12`, `libexpat`) that modern bases lack. A new `migratemongo` snapcraft
|
||||
part (amd64) stages https://github.com/wekan/migratemongo at `$SNAP/migratemongo`
|
||||
(its MongoDB 3.2 `bin/`, the old `lib/x86_64-linux-gnu/`, and the `avx/` QEMU
|
||||
wrappers) — also filling in the `$SNAP/migratemongo/avx` path that
|
||||
`mongodb-control`/`-backup`/`-restore` already referenced but was never bundled.
|
||||
`migration-control` now reads the amd64 6.09 MongoDB 3.2 data with that mongod
|
||||
(old `LD_LIBRARY_PATH`) and the legacy `mongo` shell (mongosh cannot talk to 3.2).
|
||||
|
||||
- **[Snap migration: read MongoDB 3.2 via migratemongo CLI (dump → restore into mongod 7), fix migrator NODE_PATH](https://github.com/wekan/wekan/commit/b99d21d9f19a867af74166732aaf18c59d52a1d0)**:
|
||||
|
||||
The bundled Node MongoDB driver can't connect to a 3.2 server, and no single
|
||||
driver version spans 3.2 and MongoDB 7 / FerretDB — so rather than aliasing an
|
||||
EOL Node driver into `package.json`, the amd64 6.09/3.2 case uses the
|
||||
migratemongo CLI: `migration-control` dumps the `wekan` database with the old
|
||||
migratemongo `mongodump` (MongoDB 3.2, old libraries), then loads it into a fresh
|
||||
temporary mongod 7 with the snap's modern `mongorestore`. The existing
|
||||
driver-based migrator then reads that mongod 7 exactly like the arm64 MongoDB-7
|
||||
case (both GridFS types → filesystem, text → FerretDB v1 SQLite); the MongoDB-7
|
||||
path connects the driver directly, unchanged. Also fixes a real bug: the
|
||||
standalone migrator in `$SNAP/bin` could not resolve its `mongodb`/`bson`
|
||||
imports — `NODE_PATH` now points at the WeKan bundle's `node_modules`.
|
||||
|
||||
- **[Snap migration: only MongoDB 3 migrates (mongo CLI read + Node driver insert), FerretDB SQLite at files/db](https://github.com/wekan/wekan/commit/02cf16d8c6a8941bbab3e7d07fdc952beb2395d8)**:
|
||||
|
||||
Refines the snap migration to the intended design: a **MongoDB 7** database works
|
||||
with newest WeKan as-is and is **not** migrated; only the old 6.09 / MongoDB 3.2
|
||||
data is. `migration-control` now checks whether mongod 7 can open the data — if
|
||||
so it keeps MongoDB; otherwise it migrates the 3.x data. It reads it with the
|
||||
legacy `mongoexport` CLI (the Node driver can't talk to 3.2) and inserts into
|
||||
FerretDB with the Node driver — text streamed directly, GridFS attachments+avatars
|
||||
reassembled per-file straight to `files/attachments`/`files/avatars` — with **no
|
||||
mongodump/mongorestore and no intermediate MongoDB 7** (which the earlier commit
|
||||
used). FerretDB's SQLite now lives at `<files>/db`, next to attachments/avatars
|
||||
(the `files/<name>` layout), across the snap, offline launchers, Docker entrypoint
|
||||
and the v1-sqlite compose.
|
||||
|
||||
- **[Admin Panel / Attachments: migrate text data between MongoDB and FerretDB v1 (SQLite), both directions](https://github.com/wekan/wekan/commit/84fee7b660e365d900e1ac355d97fcccf3debc25)**:
|
||||
|
||||
A new "Database migration" section in Admin Panel / Attachments with two buttons:
|
||||
migrate text-based data (everything except attachments/avatars, which stay on the
|
||||
filesystem) **to FerretDB v1 (SQLite)** or **back to MongoDB**. WeKan is connected
|
||||
to one database at a time, so the server opens a second driver connection to the
|
||||
OTHER database (both speak the MongoDB wire protocol) and copies the text
|
||||
collections into it, upserting by `_id` (idempotent). The target is
|
||||
`WEKAN_FERRETDB_URL` (default `mongodb://127.0.0.1:27018/wekan`) or
|
||||
`WEKAN_MONGODB_URL` (default `:27019`); both must be running. Progress is shown
|
||||
live; afterwards point `MONGO_URL` at the other database and restart (Snap:
|
||||
`snap set wekan database=ferretdb` / `=mongodb`). Admin-only.
|
||||
|
||||
- **[Admin Panel / Attachments / Backup: scheduled backups streamed to storage, restore + list](https://github.com/wekan/wekan/commit/c7c8a200ff551f979fe6eeb30266d7c435cd7fa4)**:
|
||||
|
||||
A new Backup section in Admin Panel / Attachments. Select any of **Attachments**,
|
||||
**Avatars**, **Data** (all text-based collections that are not attachments/avatars)
|
||||
and a **storage** (filesystem, S3/MinIO, Azure, GCS). "Backup now" streams the
|
||||
`.zip` **directly to the selected storage — no temp file, no extra disk** — as
|
||||
`backup/YYYY/MM/DD/HH_MM_SS/backup.zip` containing
|
||||
`YYYY_MM_DD-HH_MM_SS/{attachments,avatars,data/<collection>.json}` (filesystem pipes
|
||||
to the file; S3 uses `@aws-sdk/lib-storage` streaming, Azure `uploadStream`, GCS
|
||||
`createWriteStream`, with the cloud credentials from the storage tabs). A
|
||||
**scheduler** (off/daily/weekly/monthly + time/day) runs backups via synced-cron.
|
||||
**List backups** shows a table (storage, datetime, path); pick one and **Restore**
|
||||
with "Add missing data only" or "Replace all data". Admin-only. (Cloud upload and
|
||||
restore are not exercised end-to-end yet; jszip assembles the whole zip, so very
|
||||
large attachment sets use notable memory.)
|
||||
|
||||
- **[Backup: switch from jszip to archiver+unzipper for low-memory streaming](https://github.com/wekan/wekan/commit/e39dcd4ebf19c4280cc8f48352e91b547c5ff673)**:
|
||||
|
||||
Follow-up to the Backup section above: it no longer holds whole files or the
|
||||
whole zip in memory. The backup `.zip` is written with **archiver**, streaming
|
||||
each attachment/avatar straight from disk and each text collection a document at
|
||||
a time from a Mongo cursor as **NDJSON**, piped directly to the destination
|
||||
(filesystem or S3/Azure/GCS streaming upload). Restore uses **unzipper**: each
|
||||
file entry is piped to disk and each NDJSON data entry is applied line-by-line
|
||||
in 200-doc batches. A board with thousands of cards or a 5 GB attachment now
|
||||
backs up and restores with flat memory.
|
||||
|
||||
- **[Stream board exports (JSON, CSV/TSV, Excel) with bounded memory](https://github.com/wekan/wekan/commit/41cd166358ea40a4517eddd02fe1913c22b7777b)**:
|
||||
|
||||
The board export routes used to buffer the whole board in memory: the JSON
|
||||
export built one object with every card, comment, activity, checklist and
|
||||
base64 attachment; CSV called that same builder; Excel additionally loaded data
|
||||
it never renders and did O(n²) `find()` lookups. On large boards this peaked at
|
||||
gigabytes and could exceed V8's max string length. Now the JSON export writes
|
||||
the document straight to the response a card at a time from raw cursors
|
||||
(attachments base64-encoded in aligned chunks), CSV streams one row per card
|
||||
keeping only the small lookup tables in memory, and Excel uses the exceljs
|
||||
streaming `WorkbookWriter`, committing each row and resolving card titles via an
|
||||
id→title map. Peak memory stays flat regardless of board size, and the JSON
|
||||
output is unchanged so import round-trips.
|
||||
|
||||
- **[Export board to HTML .zip: stream to disk and include every card](https://github.com/wekan/wekan/commit/ae9be62ef31014f5f452d32808c55b96d4e97f54)**:
|
||||
|
||||
The HTML export cloned the live DOM and built the whole `.zip` as an in-memory
|
||||
blob — but infinite scroll keeps only ~10 cards per list in the DOM, so most
|
||||
cards were missing, and the archive was buffered whole in browser RAM. Every
|
||||
list's card limit is now lifted so the entire board renders before the
|
||||
snapshot, and the zip is written with JSZip's `generateInternalStream` piped
|
||||
straight to the chosen file via the File System Access API, chunk by chunk with
|
||||
backpressure (browsers without the API fall back to the previous blob download).
|
||||
|
||||
- **[Lazy card loading for very large boards: CARDS_LOADING=all|lazy + Admin Panel / Features](https://github.com/wekan/wekan/commit/6f7ad270d124bf961b811743d1b78abf24f8f7e5)**:
|
||||
|
||||
A board's `board` publication normally ships **every** non-archived card (with
|
||||
full fields, comments, attachments and checklists) into each viewer's minimongo.
|
||||
The list rendering is already infinite-scrolled (~10 cards per list in the DOM),
|
||||
but the whole dataset still crosses the wire and sits in browser memory, so a
|
||||
board with thousands of cards is heavy for every viewer.
|
||||
|
||||
A new **`CARDS_LOADING`** mode (`all` default, or `lazy`) makes each list load
|
||||
only the cards it is about to render. In lazy mode the board publication ships
|
||||
no cards; instead each list/swimlane subscribes to a windowed publication
|
||||
(`boardCardsWindow`) for just its visible window — growing as you scroll — plus
|
||||
a reactive total count (`boardListCardCount`) so it knows when more remain. The
|
||||
window selector is ANDed with a server-forced board scope and refuses `$where`.
|
||||
This is set by the `CARDS_LOADING` env var (exposed in `docker-compose*.yml`,
|
||||
the bundle `start-wekan.sh`/`.bat`, and `snap set wekan cards-loading=lazy`) and
|
||||
also at runtime in a **new Admin Panel / Features** section — the intended home
|
||||
for optional / performance / future tier-gated capabilities. ([client + Features](https://github.com/wekan/wekan/commit/09805a8a9185c41c84b2596e2854f209aecf2ac0),
|
||||
[platform env](https://github.com/wekan/wekan/commit/f0d25a24314fe4f11bc01933fd6ebbac0d7fea25))
|
||||
|
||||
Lazy mode is opt-in and **experimental**: card counters and WIP limits are
|
||||
accurate (they read a server count for the list's exact selector), but the
|
||||
Calendar/Table/Gantt views and multi-select currently reflect only the cards
|
||||
loaded so far, and open boards must be reloaded after switching modes. Default
|
||||
`all` is unchanged. ([accurate counters + WIP](https://github.com/wekan/wekan/commit/7d5f29e11479be6a3b8393c8817b30620e99a068))
|
||||
|
||||
- **[Fix Transifex push: remove duplicate database-migration i18n keys](https://github.com/wekan/wekan/commit/4cbefed339d61b9ec10c1d1227c0b201d060dc77)**:
|
||||
|
||||
`en.i18n.json` had `database-migration` and `database-migration-description`
|
||||
defined twice, so the Transifex source push failed with "Duplicate string key".
|
||||
The later, unused copies ("Database Migration" / "Updating database structure…")
|
||||
were removed, keeping the values the Admin Panel / Attachments migration UI
|
||||
actually renders. JSON now parses with unique keys.
|
||||
|
||||
- **[Finnish translations for the newest features](https://github.com/wekan/wekan/commit/43006f9025fd8770c59334189af6bb3b6786226c)**:
|
||||
|
||||
Translated the remaining English strings of the Upcoming features into Finnish
|
||||
(`fi.i18n.json`), matching existing terminology: Admin Panel / Version
|
||||
(database type, FerretDB/MongoDB version + commit, reactivity mode), Attachments
|
||||
/ Database migration, Features (card loading all/lazy), and Backup (schedule,
|
||||
storage, restore, list). 44 keys; product names/acronyms kept as-is.
|
||||
|
||||
- **[Unit + negative tests for the newest features](https://github.com/wekan/wekan/commit/d2ec3ee400192b65f87edc29dbe6c12eb0cd0401)**:
|
||||
|
||||
Extract the pure, security-/correctness-critical logic of the recent features
|
||||
into Meteor-free `models/lib/*` modules (shared by production and tests) and add
|
||||
plain-Node tests with positive and negative cases, wired into `test:unit:node`:
|
||||
the windowed-card publication's `$where` selector safety, `CARDS_LOADING` mode
|
||||
resolution + window-count id, the JSON export's streaming base64 chunker, and
|
||||
the backup files-root + schedule-text helpers. 41 assertions, all passing.
|
||||
|
||||
- **[docs: Design/Multiverse/Alternative-Architectures.md](https://github.com/wekan/wekan/commit/9828081c762fe180f01f1195351c0b1c2b95d59c)**:
|
||||
|
||||
Document which CPU architectures WeKan can run on and why (the limit is Node.js,
|
||||
not FerretDB): the Node 24 / MongoDB / FerretDB v1 matrix, why armhf/armv7l/i386
|
||||
are unsupported (Node dropped 32-bit) and loong64 is not buildable in CI (no
|
||||
QEMU emulation / base image), the JavaScript-engine alternatives (Deno/Bun cover
|
||||
fewer arches; QuickJS/JSC/SpiderMonkey/JVM run on 32-bit but cannot run Meteor),
|
||||
and server-rewrite options (Go recommended, QuickJS niche, Tcl/Tk a poor fit).
|
||||
|
||||
- **[Fix backup build: archiver@8 (ESM) + @aws-sdk/lib-storage dependency](https://github.com/wekan/wekan/commit/abe8af0788d15053770d9f008098ab62a8daf55e)**:
|
||||
|
||||
The Meteor/rspack build failed on `server/methods/backup.js`: `archiver@8` is now
|
||||
pure ESM with no default export or `archiver('zip', …)` factory (it exports
|
||||
classes), and the S3 streaming upload used `@aws-sdk/lib-storage`, which was not
|
||||
a dependency. Use `import { ZipArchive } from 'archiver'` /
|
||||
`new ZipArchive({ zlib: { level: 6 } })`, and add `@aws-sdk/lib-storage`
|
||||
(pinned to `~3.1073.0` to match the installed `@aws-sdk/client-s3`).
|
||||
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v9.83 2026-07-09 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- **[Fix #3624 swimlane REST regression: await the async board.swimlanes()](https://github.com/wekan/wekan/commit/6a6db32884f68dadd5911ec3487225c908f40176)**:
|
||||
|
||||
The swimlanes CRUD e2e test (23-rest-api-more.e2e.js:209) regressed on all
|
||||
browsers. Root cause: on the server ReactiveCache.getSwimlanes is async, so
|
||||
board.swimlanes() returns a Promise in the REST handler. The #3624 change read
|
||||
board.swimlanes().map(s => s.sort); .map on a Promise throws, the insert never
|
||||
ran, the error was swallowed by the handler's catch (returned as 200 with no
|
||||
_id), and the test read .title off a null swimlane.
|
||||
|
||||
The earlier "move the require to a top-level import" commit was not the real
|
||||
cause (the same module.exports import pattern works server-side, e.g.
|
||||
ruleDeletePermission in rulesButton.js). The actual fix is to await
|
||||
board.swimlanes() before mapping. This also means API-created swimlanes now get
|
||||
a real max(existing sort)+1 sort — previously the un-awaited `.length` yielded
|
||||
undefined, so they were stored with no sort at all, which is the #3624 symptom.
|
||||
|
||||
Verified: the swimlane test passed in every kept local run through
|
||||
2026-07-09_02-38-07 and failed starting 09-52-25 (right after the #3624 change),
|
||||
confirming the regression window; the tests/swimlaneSort.test.cjs unit test
|
||||
still passes.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v9.82 2026-07-09 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- **[Fix #5536: automated rule can now move/link a card to a different board](https://github.com/wekan/wekan/commit/18153cbc556823318a31766935dd3d718a88eaee)**:
|
||||
The rules "move card to top/bottom of a list on another board" and "link
|
||||
card to another board" actions failed for boards the rule creator did not
|
||||
own. The action showed BLANK after creating the rule (the wizard's optimistic
|
||||
client inserts landed in minimongo limbo / were rejected by allow-deny for
|
||||
non-owner members) — these now create the rule through the server
|
||||
`rules.createRule` method, keeping the action's destination boardId. And
|
||||
execution crashed with an "Internal Server Error" because the destination
|
||||
swimlane fallback dereferenced `._id` on a possibly-undefined swimlane titled
|
||||
exactly `Default` (renamed/translated/deleted on the destination board);
|
||||
resolution now uses the board's real default swimlane via a Meteor-free
|
||||
resolver `models/lib/ruleActionResolve.js`, unit tested in
|
||||
`tests/ruleActionResolve.test.cjs`. Thanks to DarthKillian and xet7.
|
||||
- **[Fix #4978: board background updates when switching boards via the favorites bar](https://github.com/wekan/wekan/commit/af6ed501c6f341c3c1fbceade704ef0ae69a5669)**:
|
||||
Switching directly between two boards via the favorites bar reused the same
|
||||
boardBody template instance, so the one-shot `setBackgroundImage()` in
|
||||
onRendered never re-ran and the previous board's background stuck. It is now
|
||||
applied inside a reactive autorun and clears any stale inline background when
|
||||
the new board has no image. The decision is a Meteor-free helper
|
||||
`models/lib/boardBackground.js`, unit tested in `tests/boardBackground.test.cjs`.
|
||||
Thanks to dasarne and xet7.
|
||||
- **[Fix #4881: Due this week / next week filter respects the start day of week](https://github.com/wekan/wekan/commit/b4a83462c39fb4be2e1c33aa7c90e690abffc79e)**:
|
||||
The "Due this week" filter selected next week's cards and ignored the
|
||||
configured start weekday, because it derived its window from
|
||||
`startOf(now(), 'week')` — which the native dateUtils never implemented, so
|
||||
it returned the date unchanged. A new Meteor-free helper
|
||||
`models/lib/weekStart.js` computes the correct week window for any start day
|
||||
of week; the this/next-week buttons now toggle per week too. Unit tested in
|
||||
`tests/weekStart.test.cjs`. Thanks to mimZD and xet7.
|
||||
- **[Fix #4946: calendar week numbers respect the defined start day of week](https://github.com/wekan/wekan/commit/9f9430595507d5e305f3919bd0d3f5298239e971)**:
|
||||
In the Calendar view the week-number column was numbered from Sunday
|
||||
regardless of the start-day-of-week setting. The calendar now computes the
|
||||
number with `weekNumberByFirstDay()` (in `models/lib/weekStart.js`) from the
|
||||
same firstDay used to lay out the grid, unit tested in
|
||||
`tests/weekStart.test.cjs`. Thanks to helioguardabaxo and xet7.
|
||||
- **[Fix #4653: LDAP username with a hyphen no longer becomes a dot](https://github.com/wekan/wekan/commit/a780c8b2a2c558a2b6e79f64b075007836af24d1)**:
|
||||
With `LDAP_UTF8_NAMES_SLUGIFY` enabled, `limax(text, { separator: '.' })`
|
||||
turned every non-alphanumeric run — hyphens included — into `.`, so an LDAP
|
||||
username like `p.parta-partb` became `p.parta.partb` and the user could not
|
||||
log in. The username is now slugified per hyphen-separated segment and
|
||||
rejoined with `-`, preserving hyphens while still transliterating UTF-8. Pure
|
||||
helper `packages/wekan-ldap/server/usernameSlug.js`, unit tested in
|
||||
`tests/ldapUsernameSlug.test.cjs`. Thanks to RowhamD and xet7.
|
||||
- **[Fix #4236: Enter adds a new line in the card title, consistent with the description](https://github.com/wekan/wekan/commit/aa5241a1ef3a8611701d4fb28e5d5b9e67f74900)**:
|
||||
The card title textarea submitted on plain Enter (only Shift+Enter made a new
|
||||
line), unlike the description field which inserts a new line on Enter and
|
||||
saves on Ctrl/Cmd+Enter. The title now uses the same shared, Meteor-free rule
|
||||
`isSubmitKey()` (`models/lib/editorSubmitKey.js`): submit only on
|
||||
Ctrl/Cmd+Enter, newline otherwise. Unit tested in
|
||||
`tests/editorSubmitKey.test.cjs`. Thanks to listenerri and xet7.
|
||||
- **[Fix #4055: ISO week-number regression test (already correct in current code)](https://github.com/wekan/wekan/commit/4e9614894a3b8eb23286f834c6bc4183f861d7eb)**:
|
||||
#4055 reported the week number was one/two weeks too high for 2021-10-25..31
|
||||
(ISO week 43). That was the old moment-based math; the current native,
|
||||
DST-safe `getISOWeek()` computes it correctly. Added
|
||||
`tests/isoWeek.test.cjs` pinning the reported dates so it cannot regress.
|
||||
Thanks to marcungeschikts and xet7.
|
||||
- **[Fix #4394: Register / Forgot Password links stay hidden after a failed login](https://github.com/wekan/wekan/commit/9e754e69b60509e3cb7abc9bef3bc61e27d3fa84)**:
|
||||
Security. With registration / forgot-password disabled in the Admin Panel, the
|
||||
links were hidden by a one-shot `.hide()` that a useraccounts form re-render
|
||||
(e.g. an LDAP failed login) dropped, so the links reappeared. The disable
|
||||
state is now a class on the stable `<body>` ancestor with matching CSS, so the
|
||||
links are re-hidden on every re-render. Thanks to Alsterdetektive1 and xet7.
|
||||
- **[Fix #4494: creating a board from a template no longer breaks subtasks](https://github.com/wekan/wekan/commit/032f06feb825ba62aebddfdc24f494812195542b)**:
|
||||
A board created from a template inherited the template's
|
||||
subtasksDefaultBoardId / dateSettingsDefaultBoardId; when those pointed at the
|
||||
template board itself, subtasks created on the new board were dropped onto the
|
||||
TEMPLATE board and linked back across boards. Board.copy() now repoints such
|
||||
self-referential defaults to the copy and clears the paired list id so it
|
||||
self-heals on the new board. Pure helper `models/lib/boardCopyDefaults.js`,
|
||||
unit tested in `tests/boardCopyDefaults.test.cjs`. Thanks to Xilef11 and xet7.
|
||||
- **[Fix #4249: filter by card title now works for renamed linked cards](https://github.com/wekan/wekan/commit/e089e176345452956889f709f0eb05b1687bec2c)**:
|
||||
Renaming a linked card wrote the new title only to the linked target, leaving
|
||||
the linking card's own title field stale; filter-by-title queries the own
|
||||
field, so linked cards dropped out of title filters after a rename.
|
||||
Card.setTitle() now also writes the linking card's own title. Pure helper
|
||||
`models/lib/linkedCardTitle.js`, unit tested in
|
||||
`tests/linkedCardTitle.test.cjs`. Thanks to Ben0it-T and xet7.
|
||||
- **[Fix #3606: activity feed no longer shows "edited/deleted comment undefined"](https://github.com/wekan/wekan/commit/4c721b997514d5e6659cf1725c719238d6db3e58)**:
|
||||
The feed passed the comment id (absent on old activities, and gone for a
|
||||
deleted comment) into the activity string. Edit/delete activities now store
|
||||
the comment text and render it via `Activities.commentDisplayText()`, which
|
||||
falls back to the live comment text and then to an empty string — never
|
||||
"undefined". Pure helper `models/lib/commentActivity.js`, unit tested in
|
||||
`tests/commentActivity.test.cjs`. Thanks to janchuelo and xet7.
|
||||
- **[Fix #3624: new_swimlane REST API appends the swimlane last and accepts a sort](https://github.com/wekan/wekan/commit/04021f8c1df4b8e8f285a4cfffc03404ada4188e)**:
|
||||
`POST /api/boards/:boardId/swimlanes` set the sort to the swimlane count, so a
|
||||
new swimlane appeared FIRST when existing sort values were non-contiguous. It
|
||||
now appends at max(existing sort)+1 and honors an optional explicit `sort` in
|
||||
the body. Pure helper `models/lib/swimlaneSort.js`, unit tested in
|
||||
`tests/swimlaneSort.test.cjs`. Thanks to tamasberesoebb and xet7.
|
||||
- **[Fix #3185: copying a card (or template) now copies its subtasks' checklists](https://github.com/wekan/wekan/commit/fae7bcaa416d766c32d46b29b91e3fc6b7d022ce)**:
|
||||
Copying a card inserted each subtask as a bare card document, so the subtasks'
|
||||
checklists (and items) were dropped and the copied subtasks came out empty.
|
||||
Card.copy() now copies each subtask's checklists onto the new subtask. Pure
|
||||
helper `models/lib/subtaskCopy.js`, unit tested in `tests/subtaskCopy.test.cjs`.
|
||||
Thanks to ramses345 and xet7.
|
||||
- **[Fix #5439: list scrollbar is always visible on desktop and mobile browsers](https://github.com/wekan/wekan/commit/82e0afa38f50223959cc3cc0091c52da7af93840)**:
|
||||
List bodies used `overflow-y: auto`, so overlay scrollbars (macOS/iOS/Android/
|
||||
Firefox) auto-hid. The list body now keeps a scrollbar visible across all
|
||||
engines — `overflow-y: scroll`, `::-webkit-scrollbar` (Chrome/Safari/Edge/
|
||||
mobile WebKit), `scrollbar-width`/`scrollbar-color` (Firefox/Gecko) and
|
||||
`scrollbar-gutter: stable`. Cross-browser rules in `models/lib/scrollbarCss.js`,
|
||||
applied in `client/components/lists/list.css`, unit tested (builder contract +
|
||||
applied CSS, positive + negative) in `tests/scrollbarCss.test.cjs`. Thanks to xet7.
|
||||
- **[Fix #6444: RTL — typing a card title no longer garbles other lists' minicard titles](https://github.com/wekan/wekan/commit/3aaf8f9b6180661abb0a86becf15710fc6937ef8)**:
|
||||
In an RTL language (e.g. Arabic) the board root is `dir="rtl"`, and both the
|
||||
minicard title (a `dir="auto"` `.viewer`) and the add-card composer textarea
|
||||
were `dir="auto"` with no bidi isolation, so they shared the surrounding
|
||||
bidirectional context. Typing a strong RTL character into one list's composer
|
||||
re-resolved that shared context and visibly reflowed the displayed minicard
|
||||
titles of the OTHER lists (no data change, reverted on refresh). Each title and
|
||||
the composer now use `unicode-bidi: isolate`. Locked in (positive + negative)
|
||||
by `tests/minicardBidiIsolation.test.cjs`; browser RTL behaviour is covered by
|
||||
`tests/playwright/specs/18-rtl-layout.e2e.js`. Thanks to xet7.
|
||||
- **[Fix the Playwright CI test failures introduced by the #3624 and #4236 fixes above](https://github.com/wekan/wekan/commit/6a6db32884f68dadd5911ec3487225c908f40176)**:
|
||||
Two browser tests failed on Chromium, Firefox and WebKit after this release's
|
||||
changes. (1) `23-rest-api-more.e2e.js:209` (swimlanes CRUD): on the server
|
||||
`ReactiveCache.getSwimlanes` is async, so `board.swimlanes()` returns a Promise
|
||||
in the REST handler. The #3624 change read `board.swimlanes().map(s => s.sort)`;
|
||||
`.map` on a Promise throws, so `POST /api/boards/:boardId/swimlanes` never ran
|
||||
the insert, the error was swallowed by the handler's `try/catch` (returned as
|
||||
200 with no `_id`), and the test read `.title` off a `null` swimlane. The old
|
||||
`board.swimlanes().length` had tolerated the un-awaited Promise by silently
|
||||
yielding `undefined` (so API-created swimlanes were stored with no sort at all —
|
||||
the very #3624 symptom). Fixed by `await`ing `board.swimlanes()` before mapping,
|
||||
which also gives API-created swimlanes a real `max(sort)+1` value.
|
||||
(2) `02-cards-open-view.e2e.js:93` (editing the card title): #4236 deliberately
|
||||
made plain Enter insert a newline in the card title (Ctrl/Cmd+Enter saves), but
|
||||
the `CardPage` page object still saved with plain Enter, so the title never
|
||||
persisted and the read timed out — updated to save with `Control+Enter`
|
||||
([`984ec16`](https://github.com/wekan/wekan/commit/984ec160daec54b0e6b16cd15d89884f1c4fadf1)).
|
||||
The server-side Mocha, import-regression and Node E2E jobs were already green;
|
||||
this makes the Playwright jobs green too. Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v9.81 2026-07-09 WeKan ® release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- **[Feature #5394: the Link-card popup's Cards dropdown is now sorted alphabetically](https://github.com/wekan/wekan/commit/b07c4d0a7bed0f03ba2d7d1ad1f030e7edf3dfff)**:
|
||||
In the "Link to this card" popup, the Cards pull-down list is now sorted
|
||||
alphabetically by card title (case-insensitive, locale/numeric aware)
|
||||
instead of board sort order, so a card can be found on boards with many
|
||||
cards. Thanks to xet7.
|
||||
- **[Feature #5396: edit Lists (title, color) via the REST API + api.py commands](https://github.com/wekan/wekan/commit/7ce71dbb2e1eae5159503eefac7e6f349dfb59c8)**:
|
||||
Lists can now be edited through the REST API like cards can. The endpoint
|
||||
`PUT /api/boards/:boardId/lists/:listId` already accepted `title`, `color`,
|
||||
`starred` and `wipLimit`, but the color was stored unvalidated; it now
|
||||
validates the color with `normalizeListColor` (a named palette color or a
|
||||
custom `#rrggbb` hex) and rejects an unknown color with a clear 400 instead
|
||||
of silently storing None. The pure field/validation logic lives in a
|
||||
Meteor-free helper `models/lib/listApiUpdate.js` and is unit tested in
|
||||
`tests/listApiUpdate.test.cjs`. The `api.py` reference CLI gains two new
|
||||
commands mirroring `editcard`/`editcardcolor`: `editlist BOARDID LISTID
|
||||
NEWLISTTITLE` and `editlistcolor BOARDID LISTID COLOR`. Thanks to C0rn3j and xet7.
|
||||
- **[Feature #5514: custom color-wheel (RGB/hex) picker with automatic readable text contrast](https://github.com/wekan/wekan/commit/30f36a7f319675bd3aac7a4ba0700427000726b9)**:
|
||||
The color pickers that previously offered only a fixed set of named colors now also include a native
|
||||
color wheel (`<input type="color">`), so any `#rrggbb` color can be chosen. The wheel was added
|
||||
alongside the existing swatches for card labels ("categories"), swimlanes ("tabs"), lists and cards.
|
||||
The schema color fields accept a custom hex in addition to the named palette, and existing named-color
|
||||
data keeps working; a stored hex is rendered with an inline `background-color` instead of the named
|
||||
CSS class. A new pure, Meteor-free helper `models/lib/contrastColor.js` computes a readable text color
|
||||
from sRGB relative luminance (white text on dark backgrounds, black on light), maps the named palette
|
||||
to hex, and validates/normalizes hex; it is applied as an inline text color wherever a chosen color is
|
||||
a background behind text (label chips, swimlane / list headers, minicard, card details header), so text
|
||||
stays readable on any color. Covered by `tests/contrastColor.test.cjs` (20 assertions pass).
|
||||
Thanks to Ruyeex and xet7.
|
||||
- **[Feature #5621: Rules can set a date field to a custom time (value + minute/hour/day/week/month later)](https://github.com/wekan/wekan/commit/53328f40c6e092988175ce820c14658c855390de)**:
|
||||
The Rules "Set date relative to now" action previously only offset a date field (Start / Due / End /
|
||||
Received) by a whole number of DAYS. It now has a unit selector, so a rule can set a date field to
|
||||
now + `<value>` `<unit> later`, where the unit is minute(s) / hour(s) / day(s) / week(s) / month(s);
|
||||
negative values move the date earlier. Months use a real calendar-month add (e.g. keeping the same
|
||||
day-of-month) rather than a fixed 30-day approximation. Existing rules created before this change
|
||||
have no stored unit and keep working exactly as before (no unit ⇒ days), so the change is fully
|
||||
backward compatible. The offset math lives in a new pure, Meteor-free helper
|
||||
`models/lib/relativeDateOffset.js` used by `server/rulesHelper.js` and covered by
|
||||
`tests/relativeDateOffset.test.cjs` (15 assertions pass). Note that the related "overdue" Rules
|
||||
trigger and the "set date field to now" action from the same feature request already existed in an
|
||||
earlier release; this change adds only the missing custom-time unit selector. Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- **[Fix #5351: users are auto-added to organizations matching their email domain on sign-up](https://github.com/wekan/wekan/commit/d41e17d6a088edf6abbb1f9517d2ecb58d618f03)**: the Organization setting "Automatically add users with the domain name" (`org.orgAutoAddUsersWithDomainName`) could be configured, but nothing at sign-up ever read it, so a new user whose email domain matched an organization was never added to it; the `Accounts.onCreateUser` hook now, on every non-admin sign-up path (password registration, LDAP, invitation code, new OIDC user), adds the user to each organization whose configured domain exactly matches the domain part of their email (case-insensitive, exact — a subdomain does not match and an empty org domain matches nobody), using the same `{ orgId, orgDisplayName }` membership shape used everywhere else and never duplicating an existing membership, with the matching decision extracted into a Meteor-free, unit-tested `orgsToAutoAddForEmail` helper.
|
||||
Thanks to xet7.
|
||||
- **[Fix #5369: the Activities show/hide control is now a clear eye / eye-slash icon toggle](https://github.com/wekan/wekan/commit/1ce2d8cc8871e5da6f0ce5dde04520885649c0ba)**: the Activities panel's show/hide control was a generic, unlabeled material toggle switch whose ON/OFF meaning was counterintuitive; it is replaced on the card details Activities panel with an eye / eye-slash icon toggle (open eye = activities shown, crossed eye = activities hidden) that mirrors the login/register password-visibility toggle, so the icon reflects the current visibility, clicking flips it, and the tooltip states the action (Show activities / Hide activities). For consistency the board sidebar Activities toggle, which drives the same showActivities state, was switched from the check / empty-square icon to the same eye / eye-slash toggle. The underlying show/hide setting and its persistence are unchanged.
|
||||
Thanks to xet7.
|
||||
- **[Fix #5442: outgoing webhooks now include the label name on add/remove label](https://github.com/wekan/wekan/commit/aad00cdba2bb896068f4a896cf67df2a7bd79a73)**: the addedLabel/removedLabel outgoing webhook (and notification) text showed a bare, generic "label" with no name, because labels are embedded in the board document but the Activities `label()` helper looked the label id up in the Cards collection and always returned undefined, so the `__label__` token was never filled; the hook now resolves the label from the already-loaded board via `getLabelById` and a pure, unit-tested `labelDisplayName` helper (name, then color for a nameless label as shown in the UI, then the id), so the webhook always carries the label's display name.
|
||||
Thanks to xet7.
|
||||
- **[Fix #5482: adding/editing a card description now triggers outgoing webhooks](https://github.com/wekan/wekan/commit/da0d338e336ecfd3c120753aaea064e9487bf3cd)**: outgoing webhooks fire only when an operation logs an activity (the Activities.after.insert hook posts to the board's webhooks), but changing a card's description created no activity — unlike title/date changes — so no webhook was sent; the Cards before.update hook now logs an `a-changedDescription` activity on first-time set and later edits (but not on no-op / empty-to-empty saves), so the existing webhook hook fires, with a Meteor-free unit-tested `descriptionChanged` helper.
|
||||
Thanks to xet7.
|
||||
- **[Fix: the Rules Workflow view is now fully translatable via i18n](https://github.com/wekan/wekan/commit/8c499823cbc8c5785ec604015d55e28daa11336e)**: the Rules Workflow view rendered its trigger/action palette chips ("Card is created", "Move card to top", "Set received date to now", etc.) as hardcoded English regardless of the UI language, while the rest of the page was translated; each palette entry now carries an i18n key translated at render time via TAPi18n.__ (reusing existing rule keys where they fit, plus new r-w-* keys for workflow-only labels), and the slot clear tooltip now uses the existing r-remove key, so the whole view follows the selected language.
|
||||
Thanks to xet7.
|
||||
- **[Fix: deleting a board rule no longer fails with "Access denied [403]"](https://github.com/wekan/wekan/commit/68dd14140f620574a95db42dc1078a7b4bd97916)**: deleting a rule ran three separate client-side Collection.remove() calls (Rules + Triggers + Actions), each gated by a per-collection allow() rule that resolved the board from that document's own boardId; when a trigger/action document had no resolvable boardId (legacy docs, or docs not published to the client) the board came back null, `allowIsBoardAdmin` returned false, and Meteor rejected the mutation with 403 "Access denied" — so the delete failed even for a legitimate board admin. Rule deletion now goes through a new server method `rules.deleteRule` that authorizes once (active board admin or site admin) and removes the rule, its trigger and its action server-side, bypassing the brittle client allow/deny; the permission decision is a Meteor-free, unit-tested helper and no permission is loosened.
|
||||
Thanks to xet7.
|
||||
- **[Fix #5510: adding a board label via the REST API no longer errors/hangs](https://github.com/wekan/wekan/commit/0e9aed5a4b359188a9390f1132443614b3b0e133)**: PUT /api/boards/:boardId/labels only sent a response when the body had a `label` key, so a body without one hung until the client timed out, and a bare-string `label` pushed a schema-invalid label and returned 200; the handler now always returns JSON (2xx on success, 4xx on bad input, real error status otherwise) via a pure, unit-tested input helper.
|
||||
Thanks to xet7.
|
||||
- **[Fix #5604: CSV export no longer crashes on boards with dangling references](https://github.com/wekan/wekan/commit/d01526bbff892a946a58aae1b516bd0b82d23113)**: exporting a large/old board to CSV failed with "Couldn't download - Network issue" because Exporter.buildCsv read `.title`/`.username`/`.name` directly on the result of looking up a card's deleted list/swimlane/owner/member/assignee/label/customField by id, throwing "Cannot read property 'title' of undefined"; the per-card row builder is now a null-safe helper that emits a blank cell for missing references while keeping identical output for well-formed cards.
|
||||
Thanks to xet7.
|
||||
- **[Fix #5656: the Calendar view now honors the active board filters](https://github.com/wekan/wekan/commit/b0da9097c18dea12ccce8dec6de8a5cf2519551a)**: the Calendar view queried cards by board and date only and ignored the active Filter sidebar (member / assignee / due-date / label / custom-field), so it showed every card in the interval unlike the Board / Lists / Swimlanes views; it now ANDs the Filter selector into its query and refetches when the filter changes.
|
||||
Thanks to kerier and xet7.
|
||||
- **[Fix #6442: All Boards "Custom (drag order)" — drop now persists the reorder](https://github.com/wekan/wekan/commit/457713968cf3c365899271606398b7796a7b07f4)**:
|
||||
Follow-up to #6439, which restored the drag preview but left the drop a no-op: dragging a board on
|
||||
the All Boards page in Custom (drag order) mode showed the dashed-border preview, but releasing it
|
||||
snapped the board back without reordering. The drop handler built the current on-screen order from
|
||||
`el.classList[0]` of each `.js-board`, but the item is `li.js-board(class="{{_id}} …")` and Jade
|
||||
emits the literal `js-board` class FIRST, so `classList[0]` was the string `"js-board"` for every
|
||||
board — never the board `_id`. The ordered ids were therefore `['js-board','js-board',…]`, the
|
||||
(correct, unit-tested) `computeReorderedSortIndex` helper could not find the dragged/target ids
|
||||
among them, returned `null`, and nothing was written to `profile.boardSortIndex`; the preview still
|
||||
worked because it is driven by the `dragover` CSS class, independent of the id. The fix reads each
|
||||
board's `_id` from its Blaze data context (the same source `dragstart` uses via `this._id`) instead
|
||||
of the literal class, so the real display order reaches the reorder helper and the drop persists.
|
||||
Guarded by a new case in `tests/boardSortReorder.test.cjs` (the wrong-class extraction yields no
|
||||
mapping; real ids reorder).
|
||||
Thanks to jullbo and xet7.
|
||||
- **[Fix #6443: cards on a deleted swimlane are invisible in swimlane view](https://github.com/wekan/wekan/commit/a37e7d1cc7e1a4541c498c974aa97b2e36136065)**:
|
||||
On some old boards a swimlane was deleted while its cards kept the now-dangling `swimlaneId` (an
|
||||
"orphaned" card), so those cards showed no content in swimlane mode even though they worked in list
|
||||
mode. Cards with no swimlane at all (`null` / `''` / missing) already appear in every swimlane, but
|
||||
an orphaned card matched no existing swimlane and so was visible in NO swimlane (while list view,
|
||||
which applies no swimlane scope, still showed it) — exactly the reported symptom. The fix mirrors
|
||||
the existing orphaned-**list** fallback (`Swimlanes.orphanedSwimlaneLists`, which surfaces orphaned
|
||||
lists in the first swimlane) for **cards**: when a list's cards are fetched for the board's FIRST
|
||||
swimlane, the swimlane-membership clause becomes a single `{ swimlaneId: { $nin: <otherSwimlaneIds> } }`
|
||||
(everything not owned by another existing swimlane: own id, `null`/`''`, missing, or orphaned). It
|
||||
stays a single field clause with no second `$or`, so the #6441 board-wide label filter still holds,
|
||||
and orphaned cards appear once — in the first swimlane — without a database migration. Threaded
|
||||
through the pure `models/lib/swimlaneFilter.js` helpers, the in-memory `filterCardsByListAndSwimlane`,
|
||||
a new `List.orphanedCardsSwimlaneIds` helper and the `cards()`/`cardsUnfiltered()`/`allCards()` model
|
||||
methods plus the `listBody` `cardsWithLimit` render helper. Covered by new cases in
|
||||
`tests/swimlaneFilter.test.cjs` (18 assertions pass; the #6441 regression guards stay intact).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v9.80 2026-07-06 WeKan ® release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- **[Admin Panel Domains table: pagination, column sort and search (like the Board Table view)](https://github.com/wekan/wekan/issues/5850)**:
|
||||
The Admin Panel > People > Domains table loaded every domain (aggregated from all users) into the
|
||||
browser at once, with a fixed order and no search. It now behaves like the Board Table view: the
|
||||
server aggregates the domains and returns only one small page, so the whole list is never sent to
|
||||
the browser. You can order by the Domain or Users column (click the header to toggle ascending /
|
||||
descending, with a ▲/▼ indicator) and filter with a search box; prev/next controls page through the
|
||||
results. The search + sort + slice runs in the new pure, unit-tested `models/lib/domainTablePage.js`
|
||||
behind a new `getDomainsWithUserCountsPage` admin method (`server/models/users.js`), and the
|
||||
`domainGeneral` template (`client/components/settings/peopleBody.{jade,js,css}`) is now
|
||||
self-contained and fetches only the current page. Covered by `tests/domainTablePage.test.cjs`.
|
||||
Thanks to xet7.
|
||||
|
||||
and adds the following tests:
|
||||
|
||||
- **[Verified and added a regression test for the board-invitation email language](https://github.com/wekan/wekan/commit/dfa6c78d4f032a698f6feaad189c86da903d27fb)**:
|
||||
Confirmed that a board-invitation email is localised in the existing recipient's own profile
|
||||
language, or — when the invitee is a new account created by the invite — in the inviter's profile
|
||||
language, defaulting to `en` (`en.i18n.json`) when none is set. The behaviour was already correct;
|
||||
the language choice is now extracted into the pure, unit-tested `models/lib/inviteEmailLanguage.js`
|
||||
used by `inviteUserToBoard`, and locked in by `tests/inviteEmailLanguage.test.cjs`.
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- **[Linked-card minicard now shows the cover image of the real card](https://github.com/wekan/wekan/commit/9ef7f4a07a6b40a2582af24b27fe133119adcd18)**:
|
||||
A linked card (created by "Link card to this card") on one board did not show the cover image of
|
||||
the real card it points at on another board, even though the card's other fields did. A linked
|
||||
card is only a placeholder — its real content lives on the card at `linkedId` — and every other
|
||||
minicard getter resolves through the real card (`getTitle`/`getReceived`/`getDue`/…), but the
|
||||
cover helpers read `this.coverId` directly, and a linked card has no `coverId` of its own. The
|
||||
real card's cover attachment is already published to the linking board (see the "linked cards" /
|
||||
"attachments for linked cards" children of the `board` publication), so this was purely a
|
||||
client-side resolution gap. `Card.cover()` and the minicard `cover()` helper now resolve the
|
||||
cover id through the real card via the pure, unit-tested `models/lib/linkedCardCover.js`; normal
|
||||
cards are unaffected. Covered by `tests/linkedCardCover.test.cjs`.
|
||||
Thanks to 32Dexter and xet7.
|
||||
|
||||
- **[Fix date-picker calendar stays fully visible when opened low on a scrolled page](https://github.com/wekan/wekan/commit/7a0eb61c0a4d3ace60f95cfbda7c1d70dff665ef)**:
|
||||
Opening a date field (due/start/end date, or a date custom field) low on the screen showed the
|
||||
calendar popup extending past the visible area, and — because the pop-over is `position: absolute`
|
||||
(document coordinates) — scrolling to reach it moved the calendar along with the page, so the full
|
||||
calendar could never be seen (the workaround was to close it, drag the field to the center and
|
||||
reopen). `Popup._getOffset` computed the space above/below the opener and the clamped `top` from
|
||||
the opener's DOCUMENT offset mixed with the VIEWPORT height, ignoring the page scroll, so on a
|
||||
scrolled page the anchored popup landed outside the visible viewport. The geometry now runs in
|
||||
viewport coordinates (subtracting the page scroll) and clamps the popup fully within the visible
|
||||
viewport, then converts back to document coordinates for the absolute style; when the page is not
|
||||
scrolled the output is unchanged. Extracted the math into the pure, unit-tested
|
||||
`client/lib/popupOffset.js`, used by `client/lib/popup.js`. Covered by `tests/popupOffset.test.cjs`.
|
||||
Thanks to MarcusDger and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v9.79 2026-07-06 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- **[Fix #6439: Custom (drag order) sort on All Boards page now reorders via drag-and-drop](https://github.com/wekan/wekan/commit/f8e31745d8104c58dc7bbcb4c1e4c615141a8e82)**:
|
||||
On the All Boards page the jQuery-ui sortable that reordered boards in the "Custom (drag order)"
|
||||
mode was removed when the page switched to HTML5 drag-and-drop for workspaces, and nothing
|
||||
replaced it, so dragging a board showed a not-allowed cursor and never updated
|
||||
`profile.boardSortIndex`. Added HTML5 `dragover`/`drop` reorder handlers on the board tiles in
|
||||
`client/components/boards/boardsList.js` (active only in the custom sort mode), a
|
||||
`setBoardSortIndexes` helper in `models/users.js` to persist the new order in one write, a
|
||||
drop-hint style in `client/components/boards/boardsList.css`, and extracted the reorder
|
||||
decision/index math into the pure, unit-tested `models/lib/boardSortReorder.js`. Covered by
|
||||
`tests/boardSortReorder.test.cjs`.
|
||||
Thanks to jullbo and xet7.
|
||||
|
||||
- **[Fix #6440: '+' add-item button on minicard checklist does nothing](https://github.com/wekan/wekan/commit/dd90995db74593228d40bc504405620ec09c175d)**:
|
||||
On the minicard the checklist add-item `<form>` is rendered inside the `a.minicard-wrapper`
|
||||
anchor, so the native form `submit` event never reached the Blaze event map — the #5565
|
||||
minicard-checklist work wired only a `submit .js-add-checklist-item` handler (which never fires
|
||||
there) and gave the Save button no click handler, so clicking "+" Save did nothing. Added an
|
||||
explicit `click .js-submit-add-checklist-item-form` handler in
|
||||
`client/components/cards/minicard.js` (mirroring the working edit-item button) that inserts the
|
||||
item, with the blank-input guard and title parsing extracted into the pure, Meteor-free
|
||||
`models/lib/checklistItemTitles.js`. Card-detail checklists are unaffected. Covered by
|
||||
`tests/checklistItemTitles.test.cjs`.
|
||||
Thanks to jullbo and xet7.
|
||||
|
||||
- **[Fix #6441: label filter now applies board-wide across all swimlanes](https://github.com/wekan/wekan/commit/9dca403782d476eb3c593fe614848ef02bb7f5a3)**:
|
||||
A label filter hid non-matching cards in one swimlane (e.g. "Focus") but left another swimlane
|
||||
(e.g. "Background") unfiltered. Each list scopes its cards to the current swimlane while also
|
||||
showing shared/orphaned cards that have no swimlane; that fallback was written as a bare
|
||||
top-level `$or`, which competes with the board Filter's own top-level `$or` (label/member
|
||||
criteria) when the two selectors are combined — dropping the label criterion in every swimlane
|
||||
except the default one. The swimlane-membership fallback is now a single
|
||||
`swimlaneId: { $in: [id, null, ''] }` clause (the same form already used in `sidebarFilters.js`,
|
||||
`cardDetails.js` and `dialogWithBoardSwimlaneList.js`), extracted to the pure, unit-tested
|
||||
`models/lib/swimlaneFilter.js` and used by `client/components/lists/listBody.js` and
|
||||
`models/lists.js`, so no second `$or` exists and the filter is always `$and`-combined and applied
|
||||
board-wide. Covered by `tests/swimlaneFilter.test.cjs`.
|
||||
Thanks to jullbo and xet7.
|
||||
|
||||
- **[Fix flaky server-side Mocha test (i18n zh-CN "is not a spy")](https://github.com/wekan/wekan/commit/fc4eadedc9109602ee029b60c44eeda869aa98c3)**:
|
||||
The server-side Mocha suite intermittently reported "1 failing" on the #5756
|
||||
`imports/i18n/i18n.test.js` region-tag test with `TypeError: [Function] is not a spy`. The
|
||||
assertion re-read `TAPi18n.i18n.addResourceBundle`, and the sinon-chai matcher (routed through
|
||||
chai-as-promised) could evaluate after this suite's `afterEach` had already run
|
||||
`sinon.restore()` — at which point the property is the original function, not the stub. The
|
||||
`.loadLanguage` tests now assert on the captured stub reference (restore unwraps the property but
|
||||
leaves the spy intact), which is deterministic. Reproduced and verified with a standalone
|
||||
sinon/chai script.
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v9.78 2026-07-06 WeKan ® release
|
||||
|
||||
|
||||
+34
-10
@@ -7,7 +7,7 @@ LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
|
||||
# TARGETARCH and TARGETVARIANT are automatically provided by Docker Buildx
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
ARG VERSION=9.78
|
||||
ARG VERSION=9.84
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV BUILD_DEPS="apt-utils gnupg wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip"
|
||||
@@ -185,24 +185,35 @@ apt-get update --assume-yes
|
||||
apt-get upgrade --assume-yes
|
||||
apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS}
|
||||
|
||||
# Multi-arch mapping: Docker TARGETARCH -> Node.js arch name + WeKan bundle name
|
||||
# arm/v7 uses TARGETARCH=arm; armhf has no MongoDB Community -> uses FerretDB
|
||||
# Multi-arch mapping: Docker TARGETARCH -> Node.js arch name + WeKan bundle name.
|
||||
# amd64/arm64 have MongoDB Community; ppc64le/s390x/riscv64 have no MongoDB server
|
||||
# and ship FerretDB v1 instead (the ferretdb binary is baked into their .zip and
|
||||
# started by wekan-entrypoint.sh). riscv64's Node.js 24 comes from
|
||||
# unofficial-builds.nodejs.org (nodejs.org ships no riscv64). armv7l is excluded:
|
||||
# there is no Node.js 24 build for it anywhere.
|
||||
NODE_BASE="official"
|
||||
case "${TARGETARCH}" in
|
||||
"amd64") NODE_ARCH="x64" WEKAN_ARCH="amd64" ;;
|
||||
"arm64") NODE_ARCH="arm64" WEKAN_ARCH="arm64" ;;
|
||||
"arm") NODE_ARCH="armv7l" WEKAN_ARCH="armhf" ;;
|
||||
"ppc64le") NODE_ARCH="ppc64le" WEKAN_ARCH="ppc64le" ;;
|
||||
"s390x") NODE_ARCH="s390x" WEKAN_ARCH="s390x" ;;
|
||||
"riscv64") NODE_ARCH="riscv64" WEKAN_ARCH="riscv64" NODE_BASE="unofficial" ;;
|
||||
*) echo "Unsupported architecture: ${TARGETARCH}"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Node.js Installation (official nodejs.org builds for Node 22)
|
||||
# Node.js installation. Official nodejs.org builds for amd64/arm64/ppc64le/s390x;
|
||||
# unofficial-builds.nodejs.org for riscv64 (nodejs.org ships no riscv64 binary).
|
||||
cd /tmp
|
||||
wget "https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz"
|
||||
wget "https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc"
|
||||
grep "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt.asc | shasum -a 256 -c -
|
||||
if [ "${NODE_BASE}" = "unofficial" ]; then
|
||||
NODE_DIST="https://unofficial-builds.nodejs.org/download/release/${NODE_VERSION}"
|
||||
else
|
||||
NODE_DIST="https://nodejs.org/dist/${NODE_VERSION}"
|
||||
fi
|
||||
wget "${NODE_DIST}/node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz"
|
||||
wget "${NODE_DIST}/SHASUMS256.txt"
|
||||
grep " node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz\$" SHASUMS256.txt | shasum -a 256 -c -
|
||||
tar xzf "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
|
||||
rm -f "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt.asc
|
||||
rm -f "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt
|
||||
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs"
|
||||
|
||||
# NPM configuration
|
||||
@@ -229,6 +240,12 @@ rm "wekan-${VERSION}-${WEKAN_ARCH}.zip"
|
||||
npm install --prefix ./bundle/programs/server
|
||||
mv /home/wekan/app/bundle /build
|
||||
|
||||
# The .zip bundle now ships a self-contained launcher + its own Node.js for the
|
||||
# offline downloads; the Docker image installs its own Node and uses
|
||||
# wekan-entrypoint.sh, so drop the redundant bundled node + launchers (keeps
|
||||
# /build/ferretdb, which the entrypoint uses). Saves ~80 MB per arch.
|
||||
rm -f /build/node /build/start-wekan.sh /build/start-wekan.bat
|
||||
|
||||
# Restore original tar
|
||||
mv $(which tar)~ $(which tar)
|
||||
|
||||
@@ -247,10 +264,17 @@ mkdir -p /data
|
||||
chown wekan:wekan --recursive /data
|
||||
EOR
|
||||
|
||||
# Database-backend selector entrypoint. Every arch's bundle now ships a FerretDB
|
||||
# binary (baked into the .zip and moved to /build/ferretdb above); this script
|
||||
# starts FerretDB v1 (SQLite) or leaves the DB external based on WEKAN_DB and the
|
||||
# /build/.ferretdb-default marker (present only on MongoDB-less arches). See
|
||||
# releases/ferretdb/wekan-entrypoint.sh.
|
||||
COPY --chmod=755 releases/ferretdb/wekan-entrypoint.sh /build/wekan-entrypoint.sh
|
||||
|
||||
USER wekan
|
||||
ENV PORT=8080
|
||||
EXPOSE $PORT
|
||||
STOPSIGNAL SIGKILL
|
||||
WORKDIR /build
|
||||
|
||||
CMD ["bash", "-c", "ulimit -s 65500; exec node main.js"]
|
||||
CMD ["bash", "/build/wekan-entrypoint.sh"]
|
||||
@@ -103,7 +103,7 @@ By working directly with WeKan ® you get the benefit of active maintenance and
|
||||
|
||||
## Getting Started with Development
|
||||
|
||||
The default branch uses Meteor 3.5-rc.2 with Node.js 24.x.
|
||||
The main branch uses Meteor 3.5 with Node.js 24.x.
|
||||
See [CHANGELOG.md](https://github.com/wekan/wekan/blob/main/CHANGELOG.md) for the latest runtime updates.
|
||||
|
||||
To contribute, [create a fork](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#2-create-fork-of-httpsgithubcomwekanwekan-at-github-web-page) and run `./rebuild-wekan.sh` (or `./rebuild-wekan.bat` on Windows) as detailed [here](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#3-select-option-1-to-install-dependencies-and-then-enter). Once you're ready, please test your code and [submit a pull request (PR)](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#7-test).
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v9.78.0"
|
||||
appVersion: "v9.84.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -37,6 +37,8 @@ If *nix: chmod +x api.py => ./api.py users
|
||||
python3 api.py lists BOARDID # Lists of BOARDID
|
||||
python3 api.py list BOARDID LISTID # Info of LISTID
|
||||
python3 api.py createlist BOARDID LISTTITLE # Create list
|
||||
python3 api.py editlist BOARDID LISTID NEWLISTTITLE # Edit list title
|
||||
python3 api.py editlistcolor BOARDID LISTID COLOR (Color available: `white`, `green`, `yellow`, `orange`, `red`, `purple`, `blue`, `sky`, `lime`, `pink`, `black`, `silver`, `peachpuff`, `crimson`, `plum`, `darkgreen`, `slateblue`, `magenta`, `gold`, `navy`, `gray`, `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo`) # Edit list color
|
||||
python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION
|
||||
python3 api.py editcard BOARDID LISTID CARDID NEWCARDTITLE NEWCARDDESCRIPTION
|
||||
python3 api.py getcard BOARDID LISTID CARDID # Get card info
|
||||
@@ -432,6 +434,49 @@ if arguments == 5:
|
||||
|
||||
if arguments >= 4:
|
||||
|
||||
if sys.argv[1] == 'editlist':
|
||||
|
||||
# ------- EDIT LIST START -----------
|
||||
boardid = sys.argv[2]
|
||||
listid = sys.argv[3]
|
||||
newlisttitle = sys.argv[4]
|
||||
edlist = wekanurl + apiboards + boardid + s + l + s + listid
|
||||
print(edlist)
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
put_data = {'title': '{}'.format(newlisttitle)}
|
||||
body = requests.put(edlist, data=put_data, headers=headers)
|
||||
print("=== EDIT LIST ===\n")
|
||||
body = requests.get(edlist, headers=headers)
|
||||
data2 = body.text.replace('}', "}\n")
|
||||
print(data2)
|
||||
# ------- EDIT LIST END -----------
|
||||
|
||||
if sys.argv[1] == 'editlistcolor':
|
||||
|
||||
# ------- EDIT LIST COLOR START -----------
|
||||
boardid = sys.argv[2]
|
||||
listid = sys.argv[3]
|
||||
newcolor = sys.argv[4]
|
||||
|
||||
valid_colors = ['white', 'green', 'yellow', 'orange', 'red', 'purple', 'blue', 'sky', 'lime', 'pink', 'black',
|
||||
'silver', 'peachpuff', 'crimson', 'plum', 'darkgreen', 'slateblue', 'magenta', 'gold', 'navy',
|
||||
'gray', 'saddlebrown', 'paleturquoise', 'mistyrose', 'indigo']
|
||||
|
||||
if newcolor not in valid_colors:
|
||||
print("Invalid color. Choose a color from the list.")
|
||||
sys.exit(1)
|
||||
|
||||
edlist = wekanurl + apiboards + boardid + s + l + s + listid
|
||||
print(edlist)
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
put_data = {'color': '{}'.format(newcolor)}
|
||||
body = requests.put(edlist, data=put_data, headers=headers)
|
||||
print("=== EDIT LIST COLOR ===\n")
|
||||
body = requests.get(edlist, headers=headers)
|
||||
data2 = body.text.replace('}', "}\n")
|
||||
print(data2)
|
||||
# ------- EDIT LIST COLOR END -----------
|
||||
|
||||
if sys.argv[1] == 'newuser':
|
||||
|
||||
# ------- CREATE NEW USER START -----------
|
||||
|
||||
+10
-4
@@ -1,7 +1,13 @@
|
||||
// Fix dynamic-import chunk loading when Wekan runs under a sub-path (ROOT_URL includes a pathname,
|
||||
// e.g. https://example.com/wekan). Rspack uses __webpack_public_path__ to prefix chunk URLs; by
|
||||
// default it is set to /build-chunks/ (no context path). We update it here, at module evaluation
|
||||
// time, before any import() expression runs (those are deferred to Meteor.startup callbacks).
|
||||
// e.g. https://example.com/wekan). Rspack's runtime builds every chunk URL as
|
||||
// __webpack_public_path__ + chunkName, and the chunk name ALREADY includes the "build-chunks/"
|
||||
// prefix (its runtime does `u = e => "build-chunks/" + e + "." + hash + ".js"`). The default
|
||||
// public path is "/", so at the site root chunks correctly resolve to "/build-chunks/<chunk>".
|
||||
// Under a sub-path we must prefix that sub-path — but must NOT re-add "build-chunks/" ourselves,
|
||||
// or the URL becomes "/wekan/build-chunks/build-chunks/<chunk>" and 404s (issue #6445, which broke
|
||||
// the lazy-loaded language chunks). So set the public path to "<sub-path>/" and let rspack append
|
||||
// "build-chunks/" itself. Done here at module evaluation time, before any import() expression runs
|
||||
// (those are deferred to Meteor.startup callbacks).
|
||||
/* global __webpack_public_path__:writable */
|
||||
try {
|
||||
const _cfg = typeof window !== 'undefined' && window.__meteor_runtime_config__;
|
||||
@@ -9,7 +15,7 @@ try {
|
||||
if (_rootUrl) {
|
||||
const _rootPath = new URL(_rootUrl).pathname.replace(/\/+$/, '');
|
||||
if (_rootPath && _rootPath !== '/') {
|
||||
__webpack_public_path__ = _rootPath + '/build-chunks/';
|
||||
__webpack_public_path__ = _rootPath + '/';
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
@@ -15,6 +15,20 @@
|
||||
.reactions-popup .add-comment-reaction:hover {
|
||||
background-color: #b0c4de;
|
||||
}
|
||||
/* Eye / eye-slash icon toggle for showing/hiding activities. Open eye
|
||||
(.eye-icon) = activities shown; crossed eye (.eye-slash-icon) = hidden.
|
||||
Matches the password-visibility toggle used on the login/register pages. */
|
||||
.activities-eye-toggle {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4em;
|
||||
text-decoration: none;
|
||||
}
|
||||
.activities-eye-toggle .eye-icon,
|
||||
.activities-eye-toggle .eye-slash-icon {
|
||||
line-height: 1;
|
||||
}
|
||||
.activities {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@@ -106,10 +106,10 @@ template(name="activity")
|
||||
|
||||
//- comment activity ----------------------------------------------------
|
||||
if($eq activity.activityType 'deleteComment')
|
||||
| {{{_ 'activity-deleteComment' activity.commentId}}}.
|
||||
| {{{_ 'activity-deleteComment' (sanitize activity.commentDisplayText)}}}.
|
||||
|
||||
if($eq activity.activityType 'editComment')
|
||||
| {{{_ 'activity-editComment' activity.commentId}}}.
|
||||
| {{{_ 'activity-editComment' (sanitize activity.commentDisplayText)}}}.
|
||||
|
||||
if($eq activity.activityType 'addComment')
|
||||
| {{{_ 'activity-on' cardLink}}}
|
||||
@@ -133,6 +133,9 @@ template(name="activity")
|
||||
if($eq activity.activityType 'a-changedTitle')
|
||||
| {{{_ 'activity-changedTitle' (sanitize activity.value) cardLink}}}.
|
||||
|
||||
if($eq activity.activityType 'a-changedDescription')
|
||||
| {{{_ 'activity-changedDescription' cardLink}}}.
|
||||
|
||||
//- customField activity ------------------------------------------------
|
||||
if($eq activity.activityType 'createCustomField')
|
||||
| {{_ 'activity-customfield-created' customField}}.
|
||||
|
||||
@@ -6,11 +6,13 @@ import '/client/lib/dragscrollTouch';
|
||||
import { boardConverter } from '/client/lib/boardConverter';
|
||||
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
|
||||
import { toFullCalendarFirstDay } from '/client/lib/calendarFirstDay';
|
||||
import { weekNumberByFirstDay } from '/models/lib/weekStart';
|
||||
import Swimlanes from '/models/swimlanes';
|
||||
import Lists from '/models/lists';
|
||||
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
|
||||
// SubsManager removed for Meteor 3 migration
|
||||
const { calculateIndex } = Utils;
|
||||
@@ -578,7 +580,15 @@ Template.boardBody.onRendered(function () {
|
||||
});
|
||||
|
||||
dragscroll.reset();
|
||||
Utils.setBackgroundImage();
|
||||
// #4978: apply the board background inside a reactive autorun instead of a
|
||||
// one-shot call. Switching directly between boards (e.g. the favorites bar)
|
||||
// reuses this template instance and never re-fires onRendered, so the
|
||||
// background used to stay on the previously shown board. Reading the current
|
||||
// board inside setBackgroundImage() tracks both board switches and background
|
||||
// edits; it performs no DB writes, so there is no autorun write loop.
|
||||
this.autorun(() => {
|
||||
Utils.setBackgroundImage();
|
||||
});
|
||||
});
|
||||
|
||||
Template.boardBody.onDestroyed(function () {
|
||||
@@ -929,6 +939,11 @@ Template.calendarView.onRendered(function () {
|
||||
document.documentElement.dir = TAPi18n.getLanguageDirection();
|
||||
|
||||
this.autorun(function () {
|
||||
// #5656: refetch calendar events whenever the active board Filter changes
|
||||
// so the Calendar view stays in sync with the other views. Reading the
|
||||
// Filter's reactive state here establishes the dependency; the events()
|
||||
// callback below applies the actual selector.
|
||||
Filter.isActive();
|
||||
const calendarEl = document.getElementById('calendar-view');
|
||||
if (calendarEl && calendarEl._wekanCalendar) {
|
||||
calendarEl._wekanCalendar.refetchEvents();
|
||||
@@ -959,6 +974,12 @@ Template.calendarView.helpers({
|
||||
editable: true,
|
||||
selectable: true,
|
||||
weekNumbers: true,
|
||||
// #4946: FullCalendar's default weekNumberCalculation is locale-based
|
||||
// (Sunday-anchored for many locales), so the week-number column ignored
|
||||
// the user's start-day-of-week setting even though `firstDay` moved the
|
||||
// grid. Compute the number from the SAME firstDay so the label matches the
|
||||
// way the week is laid out.
|
||||
weekNumberCalculation: date => weekNumberByFirstDay(date, firstDay),
|
||||
// Use non-localized AM/PM time format to avoid confusing notations like 上/下/中
|
||||
// Use full 'am'/'pm' instead of single-letter 'a'/'p' for clarity
|
||||
eventTimeFormat: {
|
||||
@@ -1002,6 +1023,12 @@ Template.calendarView.helpers({
|
||||
isRTL: TAPi18n.isRTL(),
|
||||
events(fetchInfo, callback) {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
// #5656: apply the active board Filter (member / assignee / due-date /
|
||||
// label / custom-field) so the Calendar view shows the same scoped set
|
||||
// of cards as the Board / Lists / Swimlanes views.
|
||||
const filterSelector = Filter.isActive()
|
||||
? Filter._getMongoSelector()
|
||||
: undefined;
|
||||
const events = [];
|
||||
const pushEvent = function (card, title, start, end, extraCls) {
|
||||
start = start || card.startAt;
|
||||
@@ -1026,12 +1053,12 @@ Template.calendarView.helpers({
|
||||
});
|
||||
};
|
||||
currentBoard
|
||||
.cardsInInterval(fetchInfo.start, fetchInfo.end)
|
||||
.cardsInInterval(fetchInfo.start, fetchInfo.end, filterSelector)
|
||||
.forEach(function (card) {
|
||||
pushEvent(card);
|
||||
});
|
||||
currentBoard
|
||||
.cardsDueInBetween(fetchInfo.start, fetchInfo.end)
|
||||
.cardsDueInBetween(fetchInfo.start, fetchInfo.end, filterSelector)
|
||||
.forEach(function (card) {
|
||||
pushEvent(
|
||||
card,
|
||||
|
||||
@@ -1078,6 +1078,13 @@ body.grey-icons-enabled .checkmark-no-grey {
|
||||
margin: 8px; /* Space between board items */
|
||||
}
|
||||
|
||||
/* #6439: highlight the board a dragged board will be dropped before, when
|
||||
reordering in the Custom (drag order) sort mode. */
|
||||
.js-board.board-reorder-over {
|
||||
outline: 2px dashed #3498db;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Reset background for add-board button */
|
||||
.js-add-board {
|
||||
background-color: transparent !important;
|
||||
|
||||
@@ -6,6 +6,10 @@ import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
|
||||
import { BoardMultiSelection } from '/client/lib/boardMultiSelection';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import {
|
||||
isDragReorderEnabled,
|
||||
computeReorderedSortIndex,
|
||||
} from '/models/lib/boardSortReorder';
|
||||
|
||||
// SubsManager removed for Meteor 3 migration
|
||||
|
||||
@@ -59,6 +63,15 @@ const DEFAULT_WORKSPACE_ICON = '📁';
|
||||
// Matches the Admin Panel > People page size.
|
||||
const BOARDS_PER_PAGE = 25;
|
||||
|
||||
// #6439: the effective All Boards sort mode for the current user, defaulting to
|
||||
// 'custom' (manual drag order) when unknown. Used to gate board drag-reordering.
|
||||
function currentAllBoardsSortBy() {
|
||||
const cu = ReactiveCache.getCurrentUser();
|
||||
return cu && typeof cu.getAllBoardsSortBy === 'function'
|
||||
? cu.getAllBoardsSortBy()
|
||||
: 'custom';
|
||||
}
|
||||
|
||||
function getCurrentWorkspacesTree() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const tree =
|
||||
@@ -973,6 +986,55 @@ Template.boardList.events({
|
||||
document.querySelectorAll('.workspace-node.board-drag-hint, .js-select-menu.board-drag-hint').forEach((el) => {
|
||||
el.classList.remove('board-drag-hint');
|
||||
});
|
||||
document.querySelectorAll('.js-board.board-reorder-over').forEach((el) => {
|
||||
el.classList.remove('board-reorder-over');
|
||||
});
|
||||
},
|
||||
// #6439: reorder boards on the All Boards page by dropping one board onto
|
||||
// another. Only active in the "custom" (manual drag order) sort mode — in the
|
||||
// sorted modes the order comes from the title, so a board is not a valid drop
|
||||
// target (which is why, before this, the browser showed the not-allowed cursor
|
||||
// and nothing persisted). The reorder decision/index math lives in the pure,
|
||||
// unit-tested models/lib/boardSortReorder.js.
|
||||
'dragover .js-board'(evt, tpl) {
|
||||
if (!isDragReorderEnabled(currentAllBoardsSortBy())) return;
|
||||
// A multi-board drag is a share/assign gesture, not a reorder.
|
||||
const dt = evt.originalEvent.dataTransfer;
|
||||
if (dt && dt.getData('application/x-board-multi') === 'true') return;
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
if (dt) dt.dropEffect = 'move';
|
||||
evt.currentTarget.classList.add('board-reorder-over');
|
||||
},
|
||||
'dragleave .js-board'(evt) {
|
||||
evt.currentTarget.classList.remove('board-reorder-over');
|
||||
},
|
||||
'drop .js-board'(evt, tpl) {
|
||||
if (!isDragReorderEnabled(currentAllBoardsSortBy())) return;
|
||||
const dt = evt.originalEvent.dataTransfer;
|
||||
if (dt && dt.getData('application/x-board-multi') === 'true') return;
|
||||
const draggedId = dt ? dt.getData('text/plain') : '';
|
||||
if (!draggedId) return;
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
evt.currentTarget.classList.remove('board-reorder-over');
|
||||
const targetId = this._id;
|
||||
// #6442: current display order of the board grid. Read each board's _id from
|
||||
// its Blaze data context (the same source dragstart uses via `this._id`) —
|
||||
// NOT el.classList[0]. On `li.js-board(class="{{_id}} …")` Jade emits the
|
||||
// literal `js-board` class FIRST, so classList[0] is the string "js-board"
|
||||
// for every board; the old code therefore produced ['js-board','js-board',…],
|
||||
// computeReorderedSortIndex could not find the dragged/target ids among them,
|
||||
// returned null, and the drop silently did nothing (board snapped back).
|
||||
const orderedIds = Array.from(tpl.findAll('.js-board'))
|
||||
.map((el) => Blaze.getData(el)?._id)
|
||||
.filter(Boolean);
|
||||
const mapping = computeReorderedSortIndex(orderedIds, draggedId, targetId);
|
||||
if (!mapping) return;
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser && typeof currentUser.setBoardSortIndexes === 'function') {
|
||||
currentUser.setBoardSortIndexes(mapping);
|
||||
}
|
||||
},
|
||||
'click .js-clone-board'(evt) {
|
||||
if (confirm(TAPi18n.__('duplicate-board-confirm'))) {
|
||||
|
||||
@@ -66,7 +66,7 @@ template(name="tableView")
|
||||
td.table-view-cell-labels
|
||||
.div
|
||||
each label in row.labels
|
||||
span.card-label(class="card-label-{{label.color}}" title="{{label.name}}")
|
||||
span.card-label(class="{{labelColorClass label.color}}" style="{{labelColorStyle label.color}}" title="{{label.name}}")
|
||||
+viewer
|
||||
= label.name
|
||||
if showReceivedColumn
|
||||
|
||||
@@ -6,7 +6,7 @@ template(name="cardDetails")
|
||||
+attachmentViewer
|
||||
|
||||
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}' class='{{#if cardCollapsed}}card-details-collapsed{{/if}}'): .card-details-canvas
|
||||
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
|
||||
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}' style='{{colorStyle}}')
|
||||
+inlinedForm(classNames="js-card-details-title")
|
||||
+editCardTitleForm
|
||||
else
|
||||
@@ -127,7 +127,7 @@ template(name="cardDetails")
|
||||
| {{_ 'labels'}}
|
||||
a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
|
||||
each labels
|
||||
span.card-label(class="card-label-{{color}}" title=name)
|
||||
span.card-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title=name)
|
||||
+viewer
|
||||
= name
|
||||
if canModifyCard
|
||||
@@ -741,12 +741,12 @@ template(name="cardDetails")
|
||||
i.fa.fa-history
|
||||
| {{ _ 'activities'}}
|
||||
if currentUser.isBoardMember
|
||||
.material-toggle-switch(title="{{_ 'show-activities'}}")
|
||||
if showActivities
|
||||
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard")
|
||||
label.toggle-label(for="toggleShowActivitiesCard")
|
||||
if showActivities
|
||||
a.activities-eye-toggle.js-toggle-show-activities-card(title="{{_ 'hide-activities'}}" aria-label="{{_ 'hide-activities'}}")
|
||||
i.fa.fa-eye.eye-icon
|
||||
else
|
||||
a.activities-eye-toggle.js-toggle-show-activities-card(title="{{_ 'show-activities'}}" aria-label="{{_ 'show-activities'}}")
|
||||
i.fa.fa-eye-slash.eye-slash-icon
|
||||
|
||||
if currentUser.isBoardAdmin
|
||||
if isLoaded.get
|
||||
@@ -1199,6 +1199,10 @@ template(name="setCardColorPopup")
|
||||
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
|
||||
if(isSelected color)
|
||||
i.fa.fa-check
|
||||
// #5514: native color wheel for picking a custom RGB / hex color
|
||||
.color-wheel-wrap
|
||||
label(for="cardColorWheel") {{_ 'custom-color'}}
|
||||
input.js-card-color-wheel#cardColorWheel(type="color" value=currentColorHex title="{{_ 'custom-color'}}")
|
||||
button.primary.confirm.js-submit {{_ 'save'}}
|
||||
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
|
||||
|
||||
|
||||
@@ -50,8 +50,10 @@ import Users from '/models/users';
|
||||
import Lists from '/models/lists';
|
||||
import CardComments from '/models/cardComments';
|
||||
import { ALLOWED_COLORS } from '/config/const';
|
||||
import { isHexColor, toHex } from '/models/lib/contrastColor';
|
||||
import { uniqBy } from '/imports/lib/collectionHelpers';
|
||||
import { memberTargetBoardId } from '/models/lib/linkedCardMembers';
|
||||
import { isSubmitKey } from '/models/lib/editorSubmitKey';
|
||||
import { UserAvatar } from '../users/userAvatar';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { BoardSwimlaneListCardDialog } from '/client/lib/dialogWithBoardSwimlaneListCard';
|
||||
@@ -1046,7 +1048,7 @@ Template.cardDetails.events({
|
||||
const card = Template.currentData();
|
||||
await card.toggleHideCheckedChecklistItems();
|
||||
},
|
||||
async 'change #toggleShowActivitiesCard'() {
|
||||
async 'click .js-toggle-show-activities-card'() {
|
||||
const card = Template.currentData();
|
||||
await card.toggleShowActivities();
|
||||
},
|
||||
@@ -1498,9 +1500,11 @@ Template.editCardTitleForm.events({
|
||||
Utils.showCopied(promise, $tooltip);
|
||||
},
|
||||
'keydown .js-edit-card-title'(event) {
|
||||
// If enter key was pressed, submit the data
|
||||
// Unless the shift key is also being pressed
|
||||
if (event.keyCode === 13 && !event.shiftKey) {
|
||||
// #4236: match the card description field — Ctrl/Cmd+Enter saves; plain
|
||||
// Enter (and Shift+Enter) insert a new line instead of submitting. The old
|
||||
// handler submitted on any Enter without Shift, so Enter could not add a
|
||||
// newline in the title.
|
||||
if (isSubmitKey(event)) {
|
||||
$('.js-submit-edit-card-title-form').click();
|
||||
}
|
||||
},
|
||||
@@ -2032,6 +2036,10 @@ Template.setCardColorPopup.helpers({
|
||||
}
|
||||
return tpl.currentColor.get() === color;
|
||||
},
|
||||
// #5514: current color as a '#rrggbb' hex for the native color wheel <input>.
|
||||
currentColorHex() {
|
||||
return toHex(Template.instance().currentColor.get()) || '#0079bf';
|
||||
},
|
||||
});
|
||||
|
||||
Template.setCardColorPopup.events({
|
||||
@@ -2039,6 +2047,13 @@ Template.setCardColorPopup.events({
|
||||
const paletteData = Blaze.getData(event.currentTarget);
|
||||
tpl.currentColor.set(paletteData?.color);
|
||||
},
|
||||
// #5514: picking from the native color wheel stores a custom hex.
|
||||
'input .js-card-color-wheel'(event, tpl) {
|
||||
const value = event.currentTarget.value;
|
||||
if (isHexColor(value)) {
|
||||
tpl.currentColor.set(value);
|
||||
}
|
||||
},
|
||||
async 'click .js-submit'(event, tpl) {
|
||||
event.preventDefault();
|
||||
const card = Cards.findOne(getCardId());
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
/* #5514: native color-wheel (custom RGB / hex) picker wrapper, shared by the
|
||||
label / list / swimlane / card color popups. */
|
||||
.color-wheel-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 6px 0 10px 0;
|
||||
}
|
||||
.color-wheel-wrap label {
|
||||
margin: 0;
|
||||
}
|
||||
.color-wheel-wrap input[type="color"] {
|
||||
width: 44px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
border: 1px solid #000;
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -4,9 +4,12 @@ template(name="formLabel")
|
||||
|
||||
label {{_ "select-color"}}
|
||||
.palette-colors: each labels
|
||||
span.card-label.palette-color.js-palette-color(class="card-label-{{color}}")
|
||||
span.card-label.palette-color.js-palette-color(class="{{labelColorClass color}}" style="{{labelColorStyle color}}")
|
||||
if(isSelected color)
|
||||
i.fa.fa-check
|
||||
.color-wheel-wrap
|
||||
label(for="labelColorWheel") {{_ 'custom-color'}}
|
||||
input.js-label-color-wheel#labelColorWheel(type="color" value=currentColorHex title="{{_ 'custom-color'}}")
|
||||
|
||||
template(name="createLabelPopup")
|
||||
form.create-label
|
||||
@@ -31,8 +34,8 @@ template(name="cardLabelsPopup")
|
||||
a.card-label-edit-button.fa.fa-pencil.js-edit-label
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
span.fa.label-handle(class="fa-arrows" title="{{_ 'dragLabel'}}")
|
||||
span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}"
|
||||
class="{{# if isLabelSelected ../_id }}active{{/if}}")
|
||||
span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="{{labelColorClass color}}"
|
||||
class="{{# if isLabelSelected ../_id }}active{{/if}}" style="{{labelColorStyle color}}")
|
||||
+viewer
|
||||
= name
|
||||
if(isLabelSelected ../_id)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { LABEL_COLORS } from '/models/metadata/colors';
|
||||
import { isHexColor, toHex } from '/models/lib/contrastColor';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
@@ -28,6 +29,11 @@ Template.formLabel.helpers({
|
||||
isSelected(color) {
|
||||
return Template.instance().currentColor.get() === color;
|
||||
},
|
||||
// #5514: current color as a '#rrggbb' hex for the color-wheel <input>, which
|
||||
// only accepts hex. Named colors map to their palette hex; fall back to green.
|
||||
currentColorHex() {
|
||||
return toHex(Template.instance().currentColor.get()) || '#3cb500';
|
||||
},
|
||||
});
|
||||
|
||||
Template.formLabel.events({
|
||||
@@ -36,6 +42,13 @@ Template.formLabel.events({
|
||||
const selectedColor = paletteData?.color || Template.currentData()?.color || getFallbackLabelColor();
|
||||
tpl.currentColor.set(selectedColor);
|
||||
},
|
||||
// #5514: picking a color from the native color wheel stores a custom hex.
|
||||
'input .js-label-color-wheel'(event, tpl) {
|
||||
const value = event.currentTarget.value;
|
||||
if (isHexColor(value)) {
|
||||
tpl.currentColor.set(value);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Template.createLabelPopup.helpers({
|
||||
@@ -131,7 +144,16 @@ Template.createLabelPopup.events({
|
||||
? selectedColorIcon.closest('.js-palette-color')
|
||||
: null;
|
||||
const selectedColorData = selectedPaletteNode && Blaze.getData(selectedPaletteNode);
|
||||
const color = selectedColorData?.color || getFallbackLabelColor();
|
||||
// #5514: a named swatch (has the check icon) wins; otherwise fall back to
|
||||
// the custom color-wheel hex, then to the default palette color.
|
||||
let color = selectedColorData?.color;
|
||||
if (!color) {
|
||||
const wheel = templateInstance.find('.js-label-color-wheel');
|
||||
if (wheel && isHexColor(wheel.value)) {
|
||||
color = wheel.value;
|
||||
}
|
||||
}
|
||||
color = color || getFallbackLabelColor();
|
||||
board.addLabel(name, color);
|
||||
Popup.back();
|
||||
},
|
||||
@@ -158,7 +180,16 @@ Template.editLabelPopup.events({
|
||||
? selectedColorIcon.closest('.js-palette-color')
|
||||
: null;
|
||||
const selectedColorData = selectedPaletteNode && Blaze.getData(selectedPaletteNode);
|
||||
const color = selectedColorData?.color || getFallbackLabelColor();
|
||||
// #5514: a named swatch (has the check icon) wins; otherwise fall back to
|
||||
// the custom color-wheel hex, then to the default palette color.
|
||||
let color = selectedColorData?.color;
|
||||
if (!color) {
|
||||
const wheel = templateInstance.find('.js-label-color-wheel');
|
||||
if (wheel && isHexColor(wheel.value)) {
|
||||
color = wheel.value;
|
||||
}
|
||||
}
|
||||
color = color || getFallbackLabelColor();
|
||||
board.editLabel(this._id, name, color);
|
||||
Popup.back();
|
||||
},
|
||||
|
||||
@@ -183,6 +183,13 @@
|
||||
}
|
||||
.minicard .minicard-title {
|
||||
margin-inline-end: 1.5vw;
|
||||
/* #6444: isolate each title's bidirectional context. The title is a
|
||||
dir="auto" .viewer; under a global dir="rtl" document, non-isolated
|
||||
dir="auto" runs share bidi state, so typing a strong RTL (e.g. Arabic)
|
||||
character into another list's dir="auto" composer re-resolved the shared
|
||||
context and visibly reflowed OTHER lists' minicard titles (no data change,
|
||||
reverts on refresh). Isolation makes each title a self-contained run. */
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
.minicard .minicard-title .card-number {
|
||||
color: #b3b3b3;
|
||||
@@ -448,6 +455,10 @@
|
||||
min-height: 50px;
|
||||
margin-bottom: 20px;
|
||||
overflow-y: auto;
|
||||
/* #6444: isolate the composer's dir="auto" bidi run so typing a strong RTL
|
||||
character here cannot perturb the rendering of the surrounding minicard
|
||||
titles under a dir="rtl" document. */
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
.parent-prefix {
|
||||
color: #b3b3b3;
|
||||
|
||||
@@ -3,7 +3,8 @@ template(name="minicard")
|
||||
class="{{#if isLinkedCard}}linked-card{{/if}}"
|
||||
class="{{#if isLinkedBoard}}linked-board{{/if}}"
|
||||
class="{{agingClass}}"
|
||||
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
|
||||
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}"
|
||||
style="{{colorStyle}}")
|
||||
if canMoveCard
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
.handle
|
||||
@@ -93,11 +94,11 @@ template(name="minicard")
|
||||
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
|
||||
each labels
|
||||
unless hiddenMinicardLabelText
|
||||
span.js-card-label.card-label(class="card-label-{{color}}" title=name)
|
||||
span.js-card-label.card-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title=name)
|
||||
+viewer
|
||||
= name
|
||||
if hiddenMinicardLabelText
|
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||
.minicard-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title="{{name}}")
|
||||
|
||||
.minicard-custom-fields
|
||||
each customFieldsWD
|
||||
|
||||
@@ -6,6 +6,11 @@ import uploadProgressManager from '../../lib/uploadProgressManager';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import ChecklistItems from '/models/checklistItems';
|
||||
import Cards from '/models/cards';
|
||||
import { resolveCoverId } from '/models/lib/linkedCardCover';
|
||||
import {
|
||||
parseChecklistItemTitles,
|
||||
buildChecklistItemPayload,
|
||||
} from '/models/lib/checklistItemTitles';
|
||||
import { normalizeDependencies } from '/models/metadata/dependencies';
|
||||
|
||||
function getMinicardFlag(board, onMinicardField, legacyField, defaultValue) {
|
||||
@@ -164,8 +169,12 @@ Template.minicard.helpers({
|
||||
}
|
||||
},
|
||||
cover() {
|
||||
if (!this.coverId) return null;
|
||||
const attachment = ReactiveCache.getAttachment(this.coverId);
|
||||
// #5666: for a linked card the cover lives on the real card it points at, so
|
||||
// resolve the cover id through it (mirroring getTitle/getDue/...); a plain
|
||||
// card resolves to its own coverId.
|
||||
const coverId = resolveCoverId(this, id => ReactiveCache.getCard(id));
|
||||
if (!coverId) return null;
|
||||
const attachment = ReactiveCache.getAttachment(coverId);
|
||||
if (!attachment) return null;
|
||||
const coverLink = typeof attachment.link === 'function' ? attachment.link() : '';
|
||||
if (!coverLink) return null;
|
||||
@@ -376,6 +385,28 @@ Template.minicardChecklist.helpers({
|
||||
},
|
||||
});
|
||||
|
||||
// #6440: create the checklist item(s) for the minicard add-item form. Shared by
|
||||
// the Save-button click handler and the (rarely reached) form-submit handler.
|
||||
// The Save button needs an explicit click handler because the minicard — and so
|
||||
// the add-item <form> — is rendered inside the `a.minicard-wrapper` anchor, where
|
||||
// the native form `submit` event never reaches the Blaze event map; without this
|
||||
// the "+" add-item on the minicard did nothing. The blank-input guard and the
|
||||
// title parsing live in the pure, unit-tested models/lib/checklistItemTitles.js.
|
||||
function addMinicardChecklistItems(textarea, checklist) {
|
||||
if (!textarea || !checklist) return;
|
||||
const titles = parseChecklistItemTitles(textarea.value);
|
||||
if (titles.length === 0) return;
|
||||
const items = checklist.items();
|
||||
const lastItem = items[items.length - 1] || null;
|
||||
let sortIndex = Utils.calculateIndexData(lastItem, null).base;
|
||||
titles.forEach(title => {
|
||||
ChecklistItems.insert(buildChecklistItemPayload(title, checklist, sortIndex));
|
||||
sortIndex += 1;
|
||||
});
|
||||
textarea.value = '';
|
||||
textarea.focus();
|
||||
}
|
||||
|
||||
Template.minicardChecklist.events({
|
||||
'click .js-convert-checklist-item-to-card'(event) {
|
||||
event.stopPropagation();
|
||||
@@ -417,25 +448,26 @@ Template.minicardChecklist.events({
|
||||
}
|
||||
$form.find('.js-close-inlined-form').trigger('click');
|
||||
},
|
||||
// #6440: explicit click handler for the minicard "+" add-item Save button.
|
||||
// The native form `submit` event does not fire on the minicard (the form is
|
||||
// inside the `a.minicard-wrapper` anchor), so — unlike card-detail — we cannot
|
||||
// rely on `submit .js-add-checklist-item`. This mirrors the already-working
|
||||
// `click .js-submit-edit-checklist-item-form` above.
|
||||
'click .js-submit-add-checklist-item-form'(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const $form = $(event.currentTarget).closest('form');
|
||||
const textarea = $form.find('textarea.js-add-checklist-item')[0];
|
||||
const formData = Blaze.getData($form[0]) || Template.currentData() || {};
|
||||
addMinicardChecklistItems(textarea, formData.checklist);
|
||||
},
|
||||
// Kept for completeness (e.g. card-detail-style contexts); on the minicard this
|
||||
// rarely fires, so the click handler above is the real path. See #6440.
|
||||
'submit .js-add-checklist-item'(event, tpl) {
|
||||
event.preventDefault();
|
||||
const textarea = tpl.find('textarea.js-add-checklist-item');
|
||||
if (!textarea) return;
|
||||
const title = textarea.value.trim();
|
||||
const data = Template.currentData() || {};
|
||||
const checklist = data.checklist;
|
||||
if (!checklist || !title) return;
|
||||
const items = checklist.items();
|
||||
const lastItem = items[items.length - 1] || null;
|
||||
const sortIndex = Utils.calculateIndexData(lastItem, null).base;
|
||||
ChecklistItems.insert({
|
||||
title,
|
||||
checklistId: checklist._id,
|
||||
cardId: checklist.cardId,
|
||||
sort: sortIndex,
|
||||
});
|
||||
textarea.value = '';
|
||||
textarea.focus();
|
||||
addMinicardChecklistItems(textarea, data.checklist);
|
||||
},
|
||||
'click .js-delete-checklist-item': Popup.afterConfirm('checklistItemDelete', function () {
|
||||
Popup.back();
|
||||
|
||||
@@ -736,9 +736,28 @@ form.inlined-form .list-composer {
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
overflow-y: auto;
|
||||
/* #5439: always show a scrollbar on the list body across desktop and mobile
|
||||
browsers (overlay scrollbars on macOS/iOS/Android/Firefox otherwise auto-
|
||||
hide it). Keep these declarations in sync with models/lib/scrollbarCss.js
|
||||
(alwaysVisibleScrollbarCss), which is unit tested. */
|
||||
overflow-y: scroll;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.08);
|
||||
scrollbar-gutter: stable;
|
||||
padding: 5px 11px;
|
||||
}
|
||||
.list-body::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
.list-body::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.list-body::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border-radius: 5px;
|
||||
}
|
||||
.list-body .minicards {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -46,7 +46,7 @@ template(name="addCardForm")
|
||||
if getLabels
|
||||
.minicard-labels
|
||||
each getLabels
|
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||
.minicard-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title="{{name}}")
|
||||
textarea.minicard-composer-textarea.js-card-title(autofocus dir="auto")
|
||||
if members.get
|
||||
.minicard-members.js-minicard-composer-members
|
||||
|
||||
@@ -10,6 +10,9 @@ import { Filter } from '/client/lib/filter';
|
||||
import { MultiSelection } from '/client/lib/multiSelection';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { isLinkableCardTarget } from '/models/lib/linkedCardTarget';
|
||||
import { listCardsSelector } from '/models/lib/swimlaneFilter';
|
||||
import { sortCardsByTitle } from '/models/lib/sortCardsByTitle';
|
||||
import { isLazyCards, BoardListCardCounts, windowCountId } from '/client/lib/lazyCards';
|
||||
import autosize from 'autosize';
|
||||
|
||||
// SubsManager removed for Meteor 3 migration
|
||||
@@ -339,20 +342,36 @@ Template.listBody.helpers({
|
||||
if (!sortBy) {
|
||||
sortBy = defaultSort;
|
||||
}
|
||||
const selector = {
|
||||
listId: Template.currentData()._id,
|
||||
archived: false,
|
||||
};
|
||||
if (swimlaneId) {
|
||||
// Fallback: also show cards with no swimlaneId (null/empty) so that
|
||||
// pre-migration / orphaned cards are always visible without a DB migration.
|
||||
selector.$or = [
|
||||
{ swimlaneId },
|
||||
{ swimlaneId: null }, // null covers null AND missing field
|
||||
{ swimlaneId: '' }, // empty string from shared-lists era
|
||||
];
|
||||
// #6441: build the swimlane-membership fallback as a single `swimlaneId:
|
||||
// { $in: [...] }` clause (via the shared, unit-tested helper) instead of a
|
||||
// bare top-level `$or`, so it never competes with the board Filter's own
|
||||
// top-level `$or` (labels/members/exceptions) and the label filter applies
|
||||
// board-wide across every swimlane.
|
||||
// #6443: in the first swimlane also surface orphaned cards (a swimlaneId
|
||||
// pointing at a deleted swimlane) so they are not invisible in swimlane view.
|
||||
const list = Template.currentData();
|
||||
const selector = listCardsSelector(
|
||||
list._id,
|
||||
swimlaneId,
|
||||
list.orphanedCardsSwimlaneIds
|
||||
? list.orphanedCardsSwimlaneIds(swimlaneId)
|
||||
: undefined,
|
||||
);
|
||||
const mongoSelector = Filter.mongoSelector(selector);
|
||||
// Lazy card loading: the board publication ships no cards, so subscribe to
|
||||
// just this list/swimlane window (grows with `limit` as the user scrolls)
|
||||
// plus its reactive total count. Default mode keeps everything in minimongo
|
||||
// already, so these subscriptions are skipped.
|
||||
if (isLazyCards() && list.boardId) {
|
||||
tpl.subscribe('boardCardsWindow', list.boardId, mongoSelector, sortBy, limit);
|
||||
tpl.subscribe(
|
||||
'boardListCardCount',
|
||||
windowCountId(list._id, swimlaneId),
|
||||
list.boardId,
|
||||
mongoSelector,
|
||||
);
|
||||
}
|
||||
const ret = ReactiveCache.getCards(Filter.mongoSelector(selector), {
|
||||
const ret = ReactiveCache.getCards(mongoSelector, {
|
||||
// sort: ['sort'],
|
||||
sort: sortBy,
|
||||
limit,
|
||||
@@ -363,6 +382,13 @@ Template.listBody.helpers({
|
||||
showSpinner(swimlaneId) {
|
||||
const tpl = Template.instance();
|
||||
const list = Template.currentData();
|
||||
if (isLazyCards()) {
|
||||
// In lazy mode minimongo only holds the loaded window, so the total comes
|
||||
// from the server count published into BoardListCardCounts.
|
||||
const countDoc = BoardListCardCounts.findOne(windowCountId(list._id, swimlaneId));
|
||||
const total = countDoc ? countDoc.count : 0;
|
||||
return total > tpl.cardlimit.get();
|
||||
}
|
||||
return list.cards(swimlaneId).length > tpl.cardlimit.get();
|
||||
},
|
||||
|
||||
@@ -717,7 +743,9 @@ Template.linkCardPopup.helpers({
|
||||
if (tpl.selectedListId.get()) selector.listId = tpl.selectedListId.get();
|
||||
|
||||
const ret = ReactiveCache.getCards(selector, { sort: { sort: 1 } });
|
||||
return ret;
|
||||
// #5394: sort the Cards dropdown alphabetically by title
|
||||
// (case-insensitive) so a card can be found on boards with many cards.
|
||||
return sortCardsByTitle(ret);
|
||||
},
|
||||
|
||||
isTitleDefault(title) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
template(name="listHeader")
|
||||
.list-header.js-list-header.nodragscroll(
|
||||
class="{{#if limitToShowCardsCount}}list-header-card-count{{/if}}"
|
||||
class=colorClass)
|
||||
class=colorClass
|
||||
style=colorStyle)
|
||||
//- #459: keyboard/screen-reader list reordering (visually hidden, focusable).
|
||||
.list-move-buttons.sr-only
|
||||
a.js-list-move-left(href="#" role="button" aria-label="{{_ 'move-list-left'}}") {{_ 'move-list-left'}}
|
||||
@@ -15,7 +16,7 @@ template(name="listHeader")
|
||||
i.fa.fa-caret-left
|
||||
else
|
||||
if collapsed
|
||||
if showCardsCountForList cards.length
|
||||
if showCardsCountForList cardsCountWhole
|
||||
br
|
||||
span.cardCount {{cardsCount}}
|
||||
if isMiniScreen
|
||||
@@ -26,10 +27,10 @@ template(name="listHeader")
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
| (
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cardsCountWhole}}
|
||||
|/#{wipLimit.value})
|
||||
if showCardsCountForList cards.length
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||
if showCardsCountForList cardsCountWhole
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cardsCountWhole}}
|
||||
if hasNumberFieldsSum
|
||||
|
|
||||
span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
|
||||
@@ -47,11 +48,11 @@ template(name="listHeader")
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
| (
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cardsCountWhole}}
|
||||
|/#{wipLimit.value})
|
||||
unless collapsed
|
||||
if showCardsCountForList cards.length
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||
if showCardsCountForList cardsCountWhole
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cardsCountWhole}}
|
||||
if hasNumberFieldsSum
|
||||
|
|
||||
span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
|
||||
@@ -279,6 +280,10 @@ template(name="setListColorPopup")
|
||||
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
|
||||
if(isSelected color)
|
||||
i.fa.fa-check
|
||||
// #5514: native color wheel for picking a custom RGB / hex color
|
||||
.color-wheel-wrap
|
||||
label(for="listColorWheel") {{_ 'custom-color'}}
|
||||
input.js-list-color-wheel#listColorWheel(type="color" value=currentColorHex title="{{_ 'custom-color'}}")
|
||||
button.primary.confirm.js-submit {{_ 'save'}}
|
||||
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ import dragscroll from '@wekanteam/dragscroll';
|
||||
import { BoardSwimlaneListDialog } from '/client/lib/dialogWithBoardSwimlaneList';
|
||||
import Cards from '/models/cards';
|
||||
import { LIST_COLORS } from '/models/metadata/colors';
|
||||
import { isHexColor, toHex } from '/models/lib/contrastColor';
|
||||
import { MultiSelection } from '/client/lib/multiSelection';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { lazyListCardCount } from '/client/lib/lazyCards';
|
||||
|
||||
let listsColors;
|
||||
Meteor.startup(() => {
|
||||
@@ -59,24 +61,33 @@ Template.listHeader.helpers({
|
||||
swimlaneId = list.swimlaneId || '';
|
||||
}
|
||||
|
||||
const ret = list.cards(swimlaneId).length;
|
||||
return ret;
|
||||
// In lazy mode minimongo holds only the loaded window, so use the accurate
|
||||
// server count for the same selector; fall back to the loaded length.
|
||||
const lazyCount = lazyListCardCount(list, swimlaneId);
|
||||
return lazyCount !== null ? lazyCount : list.cards(swimlaneId).length;
|
||||
},
|
||||
|
||||
reachedWipLimit() {
|
||||
const list = Template.currentData();
|
||||
return (
|
||||
list.getWipLimit('enabled') &&
|
||||
list.getWipLimit('value') <= list.cards().length
|
||||
);
|
||||
const lazyCount = lazyListCardCount(list, undefined);
|
||||
const count = lazyCount !== null ? lazyCount : list.cards().length;
|
||||
return list.getWipLimit('enabled') && list.getWipLimit('value') <= count;
|
||||
},
|
||||
|
||||
exceededWipLimit() {
|
||||
const list = Template.currentData();
|
||||
return (
|
||||
list.getWipLimit('enabled') &&
|
||||
list.getWipLimit('value') < list.cards().length
|
||||
);
|
||||
const lazyCount = lazyListCardCount(list, undefined);
|
||||
const count = lazyCount !== null ? lazyCount : list.cards().length;
|
||||
return list.getWipLimit('enabled') && list.getWipLimit('value') < count;
|
||||
},
|
||||
|
||||
// Accurate whole-list card count (used by the badge visibility, WIP-limit
|
||||
// number and pluralization in the template). In lazy mode `cards.length`
|
||||
// (= list.cards().length) is only the loaded window, so prefer the server count.
|
||||
cardsCountWhole() {
|
||||
const list = Template.currentData();
|
||||
const lazyCount = lazyListCardCount(list, undefined);
|
||||
return lazyCount !== null ? lazyCount : list.cards().length;
|
||||
},
|
||||
|
||||
showCardsCountForList(count) {
|
||||
@@ -141,10 +152,9 @@ Template.listHeader.helpers({
|
||||
Template.listHeader.onCreated(function () {
|
||||
this.reachedWipLimit = function () {
|
||||
const list = Template.currentData();
|
||||
return (
|
||||
list.getWipLimit('enabled') &&
|
||||
list.getWipLimit('value') <= list.cards().length
|
||||
);
|
||||
const lazyCount = lazyListCardCount(list, undefined);
|
||||
const count = lazyCount !== null ? lazyCount : list.cards().length;
|
||||
return list.getWipLimit('enabled') && list.getWipLimit('value') <= count;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -305,11 +315,10 @@ Template.setWipLimitPopup.events({
|
||||
async 'click .js-enable-wip-limit'() {
|
||||
const list = Template.currentData();
|
||||
// Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list
|
||||
if (
|
||||
!list.getWipLimit('enabled') &&
|
||||
list.getWipLimit('value') < list.cards().length
|
||||
) {
|
||||
await list.setWipLimit(list.cards().length);
|
||||
const lazyCount = lazyListCardCount(list, undefined);
|
||||
const count = lazyCount !== null ? lazyCount : list.cards().length;
|
||||
if (!list.getWipLimit('enabled') && list.getWipLimit('value') < count) {
|
||||
await list.setWipLimit(count);
|
||||
}
|
||||
Meteor.call('enableWipLimit', list._id);
|
||||
},
|
||||
@@ -476,6 +485,10 @@ Template.setListColorPopup.helpers({
|
||||
return tpl.currentColor.get() === color;
|
||||
}
|
||||
},
|
||||
// #5514: current color as a '#rrggbb' hex for the native color wheel <input>.
|
||||
currentColorHex() {
|
||||
return toHex(Template.instance().currentColor.get()) || '#0079bf';
|
||||
},
|
||||
});
|
||||
|
||||
Template.setListColorPopup.events({
|
||||
@@ -483,6 +496,13 @@ Template.setListColorPopup.events({
|
||||
const paletteData = Blaze.getData(event.currentTarget);
|
||||
tpl.currentColor.set(paletteData?.color);
|
||||
},
|
||||
// #5514: picking from the native color wheel stores a custom hex.
|
||||
'input .js-list-color-wheel'(event, tpl) {
|
||||
const value = event.currentTarget.value;
|
||||
if (isHexColor(value)) {
|
||||
tpl.currentColor.set(value);
|
||||
}
|
||||
},
|
||||
async 'submit form'(event, tpl) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
|
||||
@@ -83,14 +83,23 @@ Template.userFormsLayout.onRendered(() => {
|
||||
})();
|
||||
});
|
||||
|
||||
// #4394 (security): a one-shot $('.at-signup-link').hide() does not survive
|
||||
// the useraccounts form re-rendering that happens after a failed login (e.g.
|
||||
// an LDAP "User not Found") — the links were recreated without the inline
|
||||
// display:none and reappeared even though registration / forgot-password
|
||||
// were disabled in the Admin Panel. Toggle a class on the stable <body>
|
||||
// ancestor and hide the links via CSS (userForm.css) so the rule keeps
|
||||
// applying to every freshly re-rendered copy of the links.
|
||||
Meteor.call('isDisableRegistration', (_, result) => {
|
||||
if (result) {
|
||||
document.body.classList.add('registration-disabled');
|
||||
$('.at-signup-link').hide();
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.call('isDisableForgotPassword', (_, result) => {
|
||||
if (result) {
|
||||
document.body.classList.add('forgotpwd-disabled');
|
||||
$('.at-pwd-link').hide();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -94,7 +94,7 @@ template(name="myCards")
|
||||
td
|
||||
.div
|
||||
each label in card.labelIds
|
||||
span.card-label(class="card-label-{{labelColor board label}}" title=name)
|
||||
span.card-label(class="{{labelColorClass (labelColor board label)}}" style="{{labelColorStyle (labelColor board label)}}" title=name)
|
||||
| {{labelName board label}}
|
||||
td
|
||||
if card.dueAt
|
||||
|
||||
@@ -106,38 +106,19 @@ Template.boardActions.events({
|
||||
const boardId = Session.get('currentBoard');
|
||||
const destBoardId = tpl.find('#board-id').value;
|
||||
const desc = Utils.getTriggerActionDesc(event, tpl);
|
||||
if (actionSelected === 'top') {
|
||||
const triggerId = Triggers.insert(trigger);
|
||||
const actionId = Actions.insert({
|
||||
actionType: 'moveCardToTop',
|
||||
listName,
|
||||
swimlaneName,
|
||||
boardId: destBoardId,
|
||||
desc,
|
||||
});
|
||||
Rules.insert({
|
||||
title: ruleName,
|
||||
triggerId,
|
||||
actionId,
|
||||
boardId,
|
||||
});
|
||||
}
|
||||
if (actionSelected === 'bottom') {
|
||||
const triggerId = Triggers.insert(trigger);
|
||||
const actionId = Actions.insert({
|
||||
actionType: 'moveCardToBottom',
|
||||
listName,
|
||||
swimlaneName,
|
||||
boardId: destBoardId,
|
||||
desc,
|
||||
});
|
||||
Rules.insert({
|
||||
title: ruleName,
|
||||
triggerId,
|
||||
actionId,
|
||||
boardId,
|
||||
});
|
||||
}
|
||||
// #5536: create the rule on the server (see server/rulesButton.js →
|
||||
// rules.createRule) instead of three optimistic client Collection.insert()
|
||||
// calls. The optimistic writes landed in minimongo limbo once the wizard
|
||||
// subscription stopped, so the cross-board move action rendered BLANK; and
|
||||
// the client allow/deny rules rejected them outright for non-owner members.
|
||||
// The action keeps its own destination `boardId` (the board to move to).
|
||||
Meteor.call('rules.createRule', boardId, ruleName, trigger, {
|
||||
actionType: actionSelected === 'bottom' ? 'moveCardToBottom' : 'moveCardToTop',
|
||||
listName,
|
||||
swimlaneName,
|
||||
boardId: destBoardId,
|
||||
desc,
|
||||
});
|
||||
},
|
||||
'click .js-add-gen-move-action'(event, tpl) {
|
||||
const desc = Utils.getTriggerActionDesc(event, tpl);
|
||||
@@ -203,20 +184,16 @@ Template.boardActions.events({
|
||||
const boardId = Session.get('currentBoard');
|
||||
const destBoardId = tpl.find('#board-id-link').value;
|
||||
const desc = Utils.getTriggerActionDesc(event, tpl);
|
||||
const triggerId = Triggers.insert(trigger);
|
||||
const actionId = Actions.insert({
|
||||
// #5536: create on the server so the cross-board link action does not land
|
||||
// blank in minimongo limbo / get rejected by client allow/deny. The action
|
||||
// keeps its own destination `boardId` (the board to link the card into).
|
||||
Meteor.call('rules.createRule', boardId, ruleName, trigger, {
|
||||
actionType: 'linkCard',
|
||||
listName,
|
||||
swimlaneName,
|
||||
boardId: destBoardId,
|
||||
desc,
|
||||
});
|
||||
Rules.insert({
|
||||
title: ruleName,
|
||||
triggerId,
|
||||
actionId,
|
||||
boardId,
|
||||
});
|
||||
},
|
||||
'click .js-sort-list-action'(event, tpl) {
|
||||
const data = Template.currentData();
|
||||
|
||||
@@ -100,8 +100,15 @@ template(name="cardActions")
|
||||
option(value="receivedAt") {{_'r-df-received-at'}}
|
||||
div.trigger-dropdown
|
||||
input(id="reldate-days",type="number",value="0")
|
||||
div.trigger-dropdown
|
||||
select(id="reldate-unit")
|
||||
option(value="minutes") {{_'r-unit-minutes'}}
|
||||
option(value="hours") {{_'r-unit-hours'}}
|
||||
option(value="days",selected="selected") {{_'r-unit-days'}}
|
||||
option(value="weeks") {{_'r-unit-weeks'}}
|
||||
option(value="months") {{_'r-unit-months'}}
|
||||
div.trigger-text
|
||||
| {{_'r-days-after'}}
|
||||
| {{_'r-later'}}
|
||||
div.trigger-button.js-set-reldate-action.js-goto-rules
|
||||
i.fa.fa-plus
|
||||
|
||||
|
||||
@@ -238,12 +238,16 @@ Template.cardActions.events({
|
||||
const boardId = Session.get('currentBoard');
|
||||
const desc = Utils.getTriggerActionDesc(event, tpl);
|
||||
const dateField = tpl.find('#reldate-datefield').value;
|
||||
// `days` keeps its name for backward compatibility: it is the numeric
|
||||
// amount, interpreted together with `unit` (minutes/hours/days/weeks/months).
|
||||
const days = parseInt(tpl.find('#reldate-days').value, 10) || 0;
|
||||
const unit = tpl.find('#reldate-unit').value;
|
||||
const triggerId = Triggers.insert(trigger);
|
||||
const actionId = Actions.insert({
|
||||
actionType: 'setDateRelative',
|
||||
dateField,
|
||||
days,
|
||||
unit,
|
||||
boardId,
|
||||
desc,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import Actions from '/models/actions';
|
||||
import Rules from '/models/rules';
|
||||
import Triggers from '/models/triggers';
|
||||
|
||||
function boardRuleIds() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
@@ -62,9 +60,10 @@ Template.rulesList.events({
|
||||
getSelected().forEach(ruleId => {
|
||||
const rule = ReactiveCache.getRule(ruleId);
|
||||
if (rule) {
|
||||
Rules.remove(rule._id);
|
||||
Actions.remove(rule.actionId);
|
||||
Triggers.remove(rule.triggerId);
|
||||
// Delete the rule + its trigger + action server-side (see
|
||||
// server/rulesButton.js `rules.deleteRule`): three client-side
|
||||
// Collection.remove() calls were rejected with 403 "Access denied".
|
||||
Meteor.call('rules.deleteRule', rule._id);
|
||||
}
|
||||
});
|
||||
setSelected([]);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import Actions from '/models/actions';
|
||||
import Rules from '/models/rules';
|
||||
import Triggers from '/models/triggers';
|
||||
|
||||
Template.rulesMain.onCreated(function () {
|
||||
this.rulesCurrentTab = new ReactiveVar('rulesList');
|
||||
@@ -83,9 +80,12 @@ function sanitizeObject(obj) {
|
||||
Template.rulesMain.events({
|
||||
'click .js-delete-rule'() {
|
||||
const rule = Template.currentData();
|
||||
Rules.remove(rule._id);
|
||||
Actions.remove(rule.actionId);
|
||||
Triggers.remove(rule.triggerId);
|
||||
// Delete the rule + its trigger + action in one server call. Doing this
|
||||
// client-side as three separate Collection.remove() calls failed with 403
|
||||
// "Access denied" whenever a trigger/action document's boardId did not
|
||||
// resolve to a board in the allow() rule; the method authorizes once and
|
||||
// removes all three server-side.
|
||||
Meteor.call('rules.deleteRule', rule._id);
|
||||
},
|
||||
'click .js-goto-trigger'(event, tpl) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -12,7 +12,7 @@ template(name="rulesWorkflow")
|
||||
.workflow-node.workflow-node-when
|
||||
span.workflow-node-label {{_ 'r-when'}}
|
||||
span.workflow-node-text= builderTriggerLabel
|
||||
a.workflow-slot-clear.js-clear-when(title="{{_ 'remove'}}") ✕
|
||||
a.workflow-slot-clear.js-clear-when(title="{{_ 'r-remove'}}") ✕
|
||||
else
|
||||
span.workflow-slot-placeholder {{_ 'r-when'}}: {{_ 'r-drop-trigger'}}
|
||||
.workflow-arrow
|
||||
@@ -22,7 +22,7 @@ template(name="rulesWorkflow")
|
||||
.workflow-node.workflow-node-then
|
||||
span.workflow-node-label {{_ 'r-action'}}
|
||||
span.workflow-node-text= builderActionLabel
|
||||
a.workflow-slot-clear.js-clear-then(title="{{_ 'remove'}}") ✕
|
||||
a.workflow-slot-clear.js-clear-then(title="{{_ 'r-remove'}}") ✕
|
||||
else
|
||||
span.workflow-slot-placeholder {{_ 'r-action'}}: {{_ 'r-drop-action'}}
|
||||
.workflow-builder-controls
|
||||
|
||||
@@ -12,31 +12,44 @@ import Triggers from '/models/triggers';
|
||||
// can be built entirely by dragging. Parameterized rules (specific list, label,
|
||||
// member, schedule time, …) are still created with the form builder on the
|
||||
// List view.
|
||||
// Each palette entry carries an i18n `labelKey` (translated at render time via
|
||||
// TAPi18n.__) instead of a hardcoded English string, so the whole workflow view
|
||||
// follows the UI language. `labelParams` is passed to TAPi18n.__ for labels that
|
||||
// need a placeholder (e.g. the scheduled time). Existing rule keys are reused
|
||||
// where they fit; workflow-only labels use new `r-w-*` keys (see en.i18n.json).
|
||||
const TRIGGER_PALETTE = [
|
||||
{ label: 'Card is created', doc: { activityType: 'createCard', listName: '*', swimlaneName: '*', cardTitle: '*', userId: '*' } },
|
||||
{ label: 'Card is moved', doc: { activityType: 'moveCard', listName: '*', oldListName: '*', swimlaneName: '*', cardTitle: '*', userId: '*' } },
|
||||
{ label: 'Card is archived', doc: { activityType: 'archivedCard', userId: '*' } },
|
||||
{ label: 'Card is unarchived', doc: { activityType: 'restoredCard', userId: '*' } },
|
||||
{ label: 'Any label is added', doc: { activityType: 'addedLabel', labelId: '*', userId: '*' } },
|
||||
{ label: 'Any label is removed', doc: { activityType: 'removedLabel', labelId: '*', userId: '*' } },
|
||||
{ label: 'A member is added', doc: { activityType: 'joinMember', username: '*', userId: '*' } },
|
||||
{ label: 'A member is removed', doc: { activityType: 'unjoinMember', username: '*', userId: '*' } },
|
||||
{ label: 'A checklist is added', doc: { activityType: 'addChecklist', checklistName: '*', userId: '*' } },
|
||||
{ label: 'An attachment is added', doc: { activityType: 'addAttachment', userId: '*' } },
|
||||
{ label: 'Every day at 09:00', doc: { activityType: 'scheduledTrigger', scheduleKind: 'calendar', scheduleType: 'daily', atTime: '09:00', listName: '*', swimlaneName: '*' } },
|
||||
{ labelKey: 'r-w-card-created', doc: { activityType: 'createCard', listName: '*', swimlaneName: '*', cardTitle: '*', userId: '*' } },
|
||||
{ labelKey: 'r-when-a-card-is-moved', doc: { activityType: 'moveCard', listName: '*', oldListName: '*', swimlaneName: '*', cardTitle: '*', userId: '*' } },
|
||||
{ labelKey: 'r-w-card-archived', doc: { activityType: 'archivedCard', userId: '*' } },
|
||||
{ labelKey: 'r-w-card-unarchived', doc: { activityType: 'restoredCard', userId: '*' } },
|
||||
{ labelKey: 'r-w-label-added', doc: { activityType: 'addedLabel', labelId: '*', userId: '*' } },
|
||||
{ labelKey: 'r-w-label-removed', doc: { activityType: 'removedLabel', labelId: '*', userId: '*' } },
|
||||
{ labelKey: 'r-w-member-added', doc: { activityType: 'joinMember', username: '*', userId: '*' } },
|
||||
{ labelKey: 'r-w-member-removed', doc: { activityType: 'unjoinMember', username: '*', userId: '*' } },
|
||||
{ labelKey: 'r-w-checklist-added', doc: { activityType: 'addChecklist', checklistName: '*', userId: '*' } },
|
||||
{ labelKey: 'r-w-attachment-added', doc: { activityType: 'addAttachment', userId: '*' } },
|
||||
{ labelKey: 'r-w-every-day-at', labelParams: { time: '09:00' }, doc: { activityType: 'scheduledTrigger', scheduleKind: 'calendar', scheduleType: 'daily', atTime: '09:00', listName: '*', swimlaneName: '*' } },
|
||||
];
|
||||
|
||||
const ACTION_PALETTE = [
|
||||
{ label: 'Move card to top', doc: { actionType: 'moveCardToTop', listName: '*', swimlaneName: '*' } },
|
||||
{ label: 'Move card to bottom', doc: { actionType: 'moveCardToBottom', listName: '*', swimlaneName: '*' } },
|
||||
{ label: 'Archive card', doc: { actionType: 'archive' } },
|
||||
{ label: 'Unarchive card', doc: { actionType: 'unarchive' } },
|
||||
{ label: 'Mark card complete', doc: { actionType: 'markCardComplete' } },
|
||||
{ label: 'Mark card incomplete', doc: { actionType: 'markCardIncomplete' } },
|
||||
{ label: 'Remove all members', doc: { actionType: 'removeMember', username: '*' } },
|
||||
{ label: 'Set received date to now', doc: { actionType: 'setDate', dateField: 'receivedAt' } },
|
||||
{ labelKey: 'r-d-move-to-top-gen', doc: { actionType: 'moveCardToTop', listName: '*', swimlaneName: '*' } },
|
||||
{ labelKey: 'r-d-move-to-bottom-gen', doc: { actionType: 'moveCardToBottom', listName: '*', swimlaneName: '*' } },
|
||||
{ labelKey: 'r-d-archive', doc: { actionType: 'archive' } },
|
||||
{ labelKey: 'r-d-unarchive', doc: { actionType: 'unarchive' } },
|
||||
{ labelKey: 'r-mark-complete', doc: { actionType: 'markCardComplete' } },
|
||||
{ labelKey: 'r-mark-incomplete', doc: { actionType: 'markCardIncomplete' } },
|
||||
{ labelKey: 'r-remove-all', doc: { actionType: 'removeMember', username: '*' } },
|
||||
{ labelKey: 'r-w-set-received-now', doc: { actionType: 'setDate', dateField: 'receivedAt' } },
|
||||
];
|
||||
|
||||
// Translate a palette entry's label for display / storage in the current UI
|
||||
// language (matches the classic Rules view, which also stores already-translated
|
||||
// description text at creation time).
|
||||
function paletteLabel(entry) {
|
||||
if (!entry) return '';
|
||||
return TAPi18n.__(entry.labelKey, entry.labelParams);
|
||||
}
|
||||
|
||||
Template.rulesWorkflow.onCreated(function () {
|
||||
this.builderTrigger = new ReactiveVar(null);
|
||||
this.builderAction = new ReactiveVar(null);
|
||||
@@ -52,24 +65,22 @@ Template.rulesWorkflow.helpers({
|
||||
return Utils.getCurrentBoard();
|
||||
},
|
||||
triggerPalette() {
|
||||
return TRIGGER_PALETTE.map((t, idx) => ({ idx, label: t.label }));
|
||||
return TRIGGER_PALETTE.map((t, idx) => ({ idx, label: paletteLabel(t) }));
|
||||
},
|
||||
actionPalette() {
|
||||
return ACTION_PALETTE.map((a, idx) => ({ idx, label: a.label }));
|
||||
return ACTION_PALETTE.map((a, idx) => ({ idx, label: paletteLabel(a) }));
|
||||
},
|
||||
builderTrigger() {
|
||||
return Template.instance().builderTrigger.get();
|
||||
},
|
||||
builderTriggerLabel() {
|
||||
const t = Template.instance().builderTrigger.get();
|
||||
return t ? t.label : '';
|
||||
return paletteLabel(Template.instance().builderTrigger.get());
|
||||
},
|
||||
builderAction() {
|
||||
return Template.instance().builderAction.get();
|
||||
},
|
||||
builderActionLabel() {
|
||||
const a = Template.instance().builderAction.get();
|
||||
return a ? a.label : '';
|
||||
return paletteLabel(Template.instance().builderAction.get());
|
||||
},
|
||||
createDisabled() {
|
||||
const tpl = Template.instance();
|
||||
@@ -96,9 +107,11 @@ function persistRule(tpl) {
|
||||
if (!t || !a) return;
|
||||
const boardId = Session.get('currentBoard');
|
||||
const titleField = tpl.find('.js-workflow-rule-title');
|
||||
const title = (titleField.value || '').trim() || `${t.label} → ${a.label}`;
|
||||
const triggerId = Triggers.insert({ ...t.doc, boardId, desc: t.label });
|
||||
const actionId = Actions.insert({ ...a.doc, boardId, desc: a.label });
|
||||
const tLabel = paletteLabel(t);
|
||||
const aLabel = paletteLabel(a);
|
||||
const title = (titleField.value || '').trim() || `${tLabel} → ${aLabel}`;
|
||||
const triggerId = Triggers.insert({ ...t.doc, boardId, desc: tLabel });
|
||||
const actionId = Actions.insert({ ...a.doc, boardId, desc: aLabel });
|
||||
Rules.insert({ title, triggerId, actionId, boardId });
|
||||
tpl.builderTrigger.set(null);
|
||||
tpl.builderAction.set(null);
|
||||
@@ -140,7 +153,7 @@ Template.rulesWorkflow.events({
|
||||
const a = ACTION_PALETTE[tpl.dragItem.idx];
|
||||
const boardId = Session.get('currentBoard');
|
||||
const oldActionId = rule.actionId;
|
||||
const newActionId = Actions.insert({ ...a.doc, boardId, desc: a.label });
|
||||
const newActionId = Actions.insert({ ...a.doc, boardId, desc: paletteLabel(a) });
|
||||
Rules.update(ruleId, { $set: { actionId: newActionId } });
|
||||
if (oldActionId) Actions.remove(oldActionId);
|
||||
tpl.dragItem = null;
|
||||
@@ -163,8 +176,9 @@ Template.rulesWorkflow.events({
|
||||
const ruleId = node && node.dataset.ruleId;
|
||||
const rule = ReactiveCache.getRule(ruleId);
|
||||
if (!rule) return;
|
||||
Rules.remove(rule._id);
|
||||
Actions.remove(rule.actionId);
|
||||
Triggers.remove(rule.triggerId);
|
||||
// Delete the rule + its trigger + action server-side (see
|
||||
// server/rulesButton.js `rules.deleteRule`): three client-side
|
||||
// Collection.remove() calls were rejected with 403 "Access denied".
|
||||
Meteor.call('rules.deleteRule', rule._id);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
template(name="adminFeatures")
|
||||
.setting-content
|
||||
unless currentUser.isAdmin
|
||||
| {{_ 'error-notAuthorized'}}
|
||||
else
|
||||
.content-title.ext-box
|
||||
span
|
||||
span.emoji-icon
|
||||
i.fa.fa-flask
|
||||
| {{_ 'features'}}
|
||||
.content-body
|
||||
.side-menu
|
||||
ul
|
||||
li(class="{{#if isPerformanceActive}}active{{/if}}")
|
||||
a.js-features-menu(data-id="performance")
|
||||
span.emoji-icon
|
||||
i.fa.fa-tachometer
|
||||
| {{_ 'features-performance'}}
|
||||
.main-body
|
||||
if isPerformanceActive
|
||||
ul.setting-detail
|
||||
li.layout-form
|
||||
.title {{_ 'cards-loading'}}
|
||||
.form-group
|
||||
select#cards-loading.js-cards-loading
|
||||
each cardsLoadingOptions
|
||||
if this.selected
|
||||
option(value="{{value}}" selected) {{_ label}}
|
||||
else
|
||||
option(value="{{value}}") {{_ label}}
|
||||
p.description {{_ 'cards-loading-description'}}
|
||||
p.description {{_ 'cards-loading-lazy-note'}}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import Settings from '/models/settings';
|
||||
|
||||
// Admin Panel / Features — a home for optional / performance / tier-gated
|
||||
// capabilities. First entry: "Card loading" (all | lazy), which also mirrors the
|
||||
// CARDS_LOADING env var. Grouped by category in the side menu so more feature
|
||||
// toggles (and, later, pricing-tier gating) can be added here.
|
||||
Template.adminFeatures.onCreated(function () {
|
||||
this.activePane = new ReactiveVar('performance');
|
||||
Meteor.subscribe('setting');
|
||||
});
|
||||
|
||||
Template.adminFeatures.helpers({
|
||||
isPerformanceActive() {
|
||||
return Template.instance().activePane.get() === 'performance';
|
||||
},
|
||||
cardsLoadingOptions() {
|
||||
const current = (ReactiveCache.getCurrentSetting() || {}).cardsLoading || 'all';
|
||||
return [
|
||||
{ value: 'all', label: 'cards-loading-all', selected: current === 'all' },
|
||||
{ value: 'lazy', label: 'cards-loading-lazy', selected: current === 'lazy' },
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
Template.adminFeatures.events({
|
||||
'click .js-features-menu'(event, tpl) {
|
||||
tpl.activePane.set(event.currentTarget.dataset.id);
|
||||
},
|
||||
'change .js-cards-loading'(event) {
|
||||
const value = event.currentTarget.value === 'lazy' ? 'lazy' : 'all';
|
||||
const setting = ReactiveCache.getCurrentSetting();
|
||||
if (setting) {
|
||||
Settings.update(setting._id, { $set: { cardsLoading: value } });
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -51,6 +51,16 @@ template(name="attachments")
|
||||
span.emoji-icon
|
||||
i.fa.fa-cloud
|
||||
| {{_ 'gcs-storage'}}
|
||||
li(class="{{#if isDatabaseMigrationActive}}active{{/if}}")
|
||||
a.js-attachments-menu(data-id="database-migration")
|
||||
span.emoji-icon
|
||||
i.fa.fa-exchange
|
||||
| {{_ 'database-migration'}}
|
||||
li(class="{{#if isBackupActive}}active{{/if}}")
|
||||
a.js-attachments-menu(data-id="backup")
|
||||
span.emoji-icon
|
||||
i.fa.fa-archive
|
||||
| {{_ 'backup'}}
|
||||
|
||||
.main-body
|
||||
if loading.get
|
||||
@@ -475,6 +485,101 @@ template(name="attachments")
|
||||
if cloudTestError.gcs
|
||||
p.form-text.text-danger {{_ 'cloud-connection-failed'}}: {{cloudTestError.gcs}}
|
||||
|
||||
else if isDatabaseMigrationActive
|
||||
ul#attachment-database-migration-setting.setting-detail
|
||||
li
|
||||
h3 {{_ 'database-migration'}}
|
||||
.description {{_ 'database-migration-description'}}
|
||||
.form-group
|
||||
button.js-migrate-to-ferretdb.btn.btn-primary(type="button" disabled=migrateRunning) {{_ 'database-migrate-to-ferretdb'}}
|
||||
button.js-migrate-to-mongodb.btn.btn-primary(type="button" disabled=migrateRunning) {{_ 'database-migrate-to-mongodb'}}
|
||||
if migrateStatus
|
||||
.move-progress-section
|
||||
.move-progress-counter
|
||||
| {{_ 'database-migration-phase'}}: {{migrateStatus.phase}}
|
||||
if migrateStatus.collection
|
||||
| — {{migrateStatus.collection}} ({{migrateStatus.collDone}}/{{migrateStatus.collTotal}}), {{migrateStatus.collectionsDone}}/{{migrateStatus.collectionsTotal}}
|
||||
if migrateStatus.error
|
||||
p.form-text.text-danger {{migrateStatus.error}}
|
||||
if migrateSuccess
|
||||
p.form-text.text-success {{_ 'database-migration-done'}}
|
||||
|
||||
else if isBackupActive
|
||||
ul#attachment-backup-setting.setting-detail
|
||||
li
|
||||
h3 {{_ 'backup'}}
|
||||
.description {{_ 'backup-description'}}
|
||||
.form-group
|
||||
label
|
||||
input.js-backup-attachments(type="checkbox" checked="checked")
|
||||
| {{_ 'attachments'}}
|
||||
label
|
||||
input.js-backup-avatars(type="checkbox" checked="checked")
|
||||
| {{_ 'avatars'}}
|
||||
label
|
||||
input.js-backup-data(type="checkbox" checked="checked")
|
||||
| {{_ 'backup-data'}}
|
||||
.form-group
|
||||
label {{_ 'backup-storage'}}
|
||||
select.wekan-form-control.js-backup-storage
|
||||
option(value="filesystem") {{_ 'filesystem-storage'}}
|
||||
option(value="s3") {{_ 's3-minio-storage'}}
|
||||
option(value="azure") {{_ 'azure-blob-storage'}}
|
||||
option(value="gcs") {{_ 'gcs-storage'}}
|
||||
.form-group
|
||||
button.js-run-backup.btn.btn-primary(type="button" disabled=backupRunning) {{_ 'backup-now'}}
|
||||
if backupStatus
|
||||
p.form-text {{_ 'database-migration-phase'}}: {{backupStatus.phase}} {{backupStatus.detail}}
|
||||
if backupStatus.error
|
||||
p.form-text.text-danger {{backupStatus.error}}
|
||||
if backupSuccess
|
||||
p.form-text.text-success {{_ 'backup-done'}}: {{backupStatus.file}}
|
||||
|
||||
h4 {{_ 'backup-schedule'}}
|
||||
.form-group
|
||||
label {{_ 'backup-frequency'}}
|
||||
select.wekan-form-control.js-backup-frequency
|
||||
option(value="off" selected=scheduleOff) {{_ 'backup-frequency-off'}}
|
||||
option(value="daily" selected=scheduleDaily) {{_ 'backup-frequency-daily'}}
|
||||
option(value="weekly" selected=scheduleWeekly) {{_ 'backup-frequency-weekly'}}
|
||||
option(value="monthly" selected=scheduleMonthly) {{_ 'backup-frequency-monthly'}}
|
||||
.form-group
|
||||
label {{_ 'backup-time'}}
|
||||
input.wekan-form-control.js-backup-time(type="time" value=scheduleTime)
|
||||
.form-group
|
||||
label {{_ 'backup-day-of-week'}}
|
||||
input.wekan-form-control.js-backup-dow(type="text" value=scheduleDow placeholder="Sunday")
|
||||
.form-group
|
||||
label {{_ 'backup-day-of-month'}}
|
||||
input.wekan-form-control.js-backup-dom(type="number" min="1" max="28" value=scheduleDom)
|
||||
.form-group
|
||||
button.js-save-backup-schedule.btn.btn-primary(type="button") {{_ 'save'}}
|
||||
|
||||
h4 {{_ 'backup-restore'}}
|
||||
.form-group
|
||||
button.js-list-backups.btn.btn-secondary(type="button") {{_ 'backup-list'}}
|
||||
if backupList
|
||||
table.gridfs-stats-table
|
||||
tr
|
||||
th
|
||||
th {{_ 'backup-storage'}}
|
||||
th {{_ 'backup-datetime'}}
|
||||
th {{_ 'backup-path'}}
|
||||
each backupList
|
||||
tr
|
||||
td
|
||||
input.js-backup-select(type="radio" name="backup-select" value=path)
|
||||
td {{storage}}
|
||||
td {{datetime}}
|
||||
td {{path}}
|
||||
.form-group
|
||||
label {{_ 'backup-restore-mode'}}
|
||||
select.wekan-form-control.js-restore-mode
|
||||
option(value="add-missing") {{_ 'backup-restore-add-missing'}}
|
||||
option(value="replace-all") {{_ 'backup-restore-replace-all'}}
|
||||
.form-group
|
||||
button.js-restore-backup.btn.btn-warning(type="button" disabled=backupRunning) {{_ 'backup-restore'}}
|
||||
|
||||
template(name="moveAttachments")
|
||||
.move-storage-form
|
||||
.move-storage-selects
|
||||
|
||||
@@ -267,6 +267,14 @@ Template.attachments.onCreated(function () {
|
||||
this.cloudTestResults = new ReactiveVar({});
|
||||
this.cloudTestErrors = new ReactiveVar({});
|
||||
this.loading = new ReactiveVar(false);
|
||||
this.migrateStatus = new ReactiveVar(null);
|
||||
this.migratePoll = null;
|
||||
this.backupStatus = new ReactiveVar(null);
|
||||
this.backupList = new ReactiveVar(null);
|
||||
this.backupSchedule = new ReactiveVar(null);
|
||||
this.selectedBackup = new ReactiveVar('');
|
||||
this.backupPoll = null;
|
||||
Meteor.call('getBackupSchedule', (err, s) => { if (!err && s) this.backupSchedule.set(s); });
|
||||
|
||||
this.autorun(() => {
|
||||
const ready = this.storageSettingsSubscription.ready();
|
||||
@@ -279,6 +287,54 @@ Template.attachments.onCreated(function () {
|
||||
});
|
||||
});
|
||||
|
||||
Template.attachments.onDestroyed(function () {
|
||||
if (this.migratePoll) {
|
||||
Meteor.clearTimeout(this.migratePoll);
|
||||
this.migratePoll = null;
|
||||
}
|
||||
if (this.backupPoll) {
|
||||
Meteor.clearTimeout(this.backupPoll);
|
||||
this.backupPoll = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Text-data database migration (MongoDB <-> FerretDB v1 SQLite) helpers.
|
||||
function pollMigrateStatus(tpl) {
|
||||
Meteor.call('migrateTextDatabaseStatus', (err, status) => {
|
||||
if (err || !status) return;
|
||||
tpl.migrateStatus.set(status);
|
||||
if (status.running) {
|
||||
tpl.migratePoll = Meteor.setTimeout(() => pollMigrateStatus(tpl), 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function startDbMigration(tpl, direction) {
|
||||
const which = direction === 'toFerretDB' ? 'FerretDB v1 (SQLite)' : 'MongoDB';
|
||||
// eslint-disable-next-line no-alert
|
||||
if (!window.confirm(TAPi18n.__('database-migration-confirm', { db: which }))) {
|
||||
return;
|
||||
}
|
||||
tpl.migrateStatus.set({ running: true, phase: 'starting', direction });
|
||||
Meteor.call('migrateTextDatabase', direction, error => {
|
||||
if (error) {
|
||||
tpl.migrateStatus.set({ phase: 'error', error: error.reason || error.message, success: false });
|
||||
} else {
|
||||
pollMigrateStatus(tpl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function pollBackupStatus(tpl) {
|
||||
Meteor.call('backupStatus', (err, status) => {
|
||||
if (err || !status) return;
|
||||
tpl.backupStatus.set(status);
|
||||
if (status.running) {
|
||||
tpl.backupPoll = Meteor.setTimeout(() => pollBackupStatus(tpl), 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Template.attachments.helpers({
|
||||
loading() {
|
||||
return Template.instance().loading;
|
||||
@@ -298,6 +354,44 @@ Template.attachments.helpers({
|
||||
isGcsActive() {
|
||||
return Template.instance().activeSection.get() === 'gcs';
|
||||
},
|
||||
isDatabaseMigrationActive() {
|
||||
return Template.instance().activeSection.get() === 'database-migration';
|
||||
},
|
||||
migrateStatus() {
|
||||
return Template.instance().migrateStatus.get();
|
||||
},
|
||||
migrateRunning() {
|
||||
const s = Template.instance().migrateStatus.get();
|
||||
return !!(s && s.running);
|
||||
},
|
||||
migrateSuccess() {
|
||||
const s = Template.instance().migrateStatus.get();
|
||||
return !!(s && s.success === true);
|
||||
},
|
||||
isBackupActive() {
|
||||
return Template.instance().activeSection.get() === 'backup';
|
||||
},
|
||||
backupStatus() {
|
||||
return Template.instance().backupStatus.get();
|
||||
},
|
||||
backupRunning() {
|
||||
const s = Template.instance().backupStatus.get();
|
||||
return !!(s && s.running);
|
||||
},
|
||||
backupSuccess() {
|
||||
const s = Template.instance().backupStatus.get();
|
||||
return !!(s && s.success === true && s.phase === 'completed');
|
||||
},
|
||||
backupList() {
|
||||
return Template.instance().backupList.get();
|
||||
},
|
||||
scheduleOff() { const s = Template.instance().backupSchedule.get(); return !s || !s.frequency || s.frequency === 'off'; },
|
||||
scheduleDaily() { const s = Template.instance().backupSchedule.get(); return !!(s && s.frequency === 'daily'); },
|
||||
scheduleWeekly() { const s = Template.instance().backupSchedule.get(); return !!(s && s.frequency === 'weekly'); },
|
||||
scheduleMonthly() { const s = Template.instance().backupSchedule.get(); return !!(s && s.frequency === 'monthly'); },
|
||||
scheduleTime() { const s = Template.instance().backupSchedule.get(); return (s && s.time) || '04:00'; },
|
||||
scheduleDow() { const s = Template.instance().backupSchedule.get(); return (s && s.dayOfWeek) || 'Sunday'; },
|
||||
scheduleDom() { const s = Template.instance().backupSchedule.get(); return (s && s.dayOfMonth) || 1; },
|
||||
avatarsUploadBlocked() {
|
||||
const settings = Template.instance().attachmentStorageSettings.get();
|
||||
return settings?.limitSettings?.avatarsUploadBlocked === true;
|
||||
@@ -561,6 +655,66 @@ Template.attachments.events({
|
||||
|
||||
tpl.activeSection.set(targetID);
|
||||
},
|
||||
'click .js-migrate-to-ferretdb'(event, tpl) {
|
||||
event.preventDefault();
|
||||
startDbMigration(tpl, 'toFerretDB');
|
||||
},
|
||||
'click .js-migrate-to-mongodb'(event, tpl) {
|
||||
event.preventDefault();
|
||||
startDbMigration(tpl, 'toMongoDB');
|
||||
},
|
||||
'click .js-run-backup'(event, tpl) {
|
||||
event.preventDefault();
|
||||
const opts = {
|
||||
attachments: tpl.$('.js-backup-attachments').is(':checked'),
|
||||
avatars: tpl.$('.js-backup-avatars').is(':checked'),
|
||||
data: tpl.$('.js-backup-data').is(':checked'),
|
||||
};
|
||||
const storage = tpl.$('.js-backup-storage').val() || 'filesystem';
|
||||
tpl.backupStatus.set({ running: true, phase: 'starting' });
|
||||
Meteor.call('runBackup', opts, storage, error => {
|
||||
if (error) tpl.backupStatus.set({ phase: 'error', error: error.reason || error.message, success: false });
|
||||
else pollBackupStatus(tpl);
|
||||
});
|
||||
},
|
||||
'click .js-save-backup-schedule'(event, tpl) {
|
||||
event.preventDefault();
|
||||
const schedule = {
|
||||
enabled: (tpl.$('.js-backup-frequency').val() || 'off') !== 'off',
|
||||
frequency: tpl.$('.js-backup-frequency').val() || 'off',
|
||||
time: tpl.$('.js-backup-time').val() || '04:00',
|
||||
dayOfWeek: tpl.$('.js-backup-dow').val() || 'Sunday',
|
||||
dayOfMonth: parseInt(tpl.$('.js-backup-dom').val(), 10) || 1,
|
||||
attachments: tpl.$('.js-backup-attachments').is(':checked'),
|
||||
avatars: tpl.$('.js-backup-avatars').is(':checked'),
|
||||
data: tpl.$('.js-backup-data').is(':checked'),
|
||||
storage: tpl.$('.js-backup-storage').val() || 'filesystem',
|
||||
};
|
||||
Meteor.call('saveBackupSchedule', schedule, (error, saved) => {
|
||||
if (!error && saved) tpl.backupSchedule.set(saved);
|
||||
});
|
||||
},
|
||||
'click .js-list-backups'(event, tpl) {
|
||||
event.preventDefault();
|
||||
Meteor.call('listBackups', (error, list) => {
|
||||
if (!error) tpl.backupList.set(list || []);
|
||||
});
|
||||
},
|
||||
'change .js-backup-select'(event, tpl) {
|
||||
tpl.selectedBackup.set($(event.currentTarget).val());
|
||||
},
|
||||
'click .js-restore-backup'(event, tpl) {
|
||||
event.preventDefault();
|
||||
const zipPath = tpl.selectedBackup.get();
|
||||
if (!zipPath) { window.alert(TAPi18n.__('backup-restore-select-first')); return; }
|
||||
const mode = tpl.$('.js-restore-mode').val() || 'add-missing';
|
||||
if (!window.confirm(TAPi18n.__('backup-restore-confirm'))) return;
|
||||
tpl.backupStatus.set({ running: true, phase: 'restore' });
|
||||
Meteor.call('restoreBackup', zipPath, mode, error => {
|
||||
if (error) tpl.backupStatus.set({ phase: 'error', error: error.reason || error.message, success: false });
|
||||
else pollBackupStatus(tpl);
|
||||
});
|
||||
},
|
||||
'change select.js-attachment-limit-unit'(event, tpl) {
|
||||
const fieldName = event.currentTarget.dataset.field;
|
||||
const selectedUnit = event.currentTarget.value;
|
||||
|
||||
@@ -29,15 +29,31 @@ template(name='statistics')
|
||||
tr
|
||||
th {{_ 'Node_version'}}
|
||||
td {{statistics.process.nodeVersion}}
|
||||
tr
|
||||
th {{_ 'Database_type'}}
|
||||
td {{statistics.mongo.databaseType}}
|
||||
tr
|
||||
th {{_ 'MongoDB_version'}}
|
||||
td {{statistics.mongo.mongoVersion}}
|
||||
tr
|
||||
th {{_ 'Database_commit'}}
|
||||
td {{statistics.mongo.databaseCommit}}
|
||||
if statistics.mongo.ferretdbVersion
|
||||
tr
|
||||
th {{_ 'FerretDB_version'}}
|
||||
td {{statistics.mongo.ferretdbVersion}}
|
||||
tr
|
||||
th {{_ 'FerretDB_commit'}}
|
||||
td {{statistics.mongo.ferretdbCommit}}
|
||||
tr
|
||||
th {{_ 'MongoDB_storage_engine'}}
|
||||
td {{statistics.mongo.mongoStorageEngine}}
|
||||
tr
|
||||
th {{_ 'MongoDB_Oplog_enabled'}}
|
||||
td {{formatBoolean statistics.mongo.mongoOplogEnabled}}
|
||||
tr
|
||||
th {{_ 'Reactivity_mode'}}
|
||||
td {{statistics.mongo.reactivity}}
|
||||
tr
|
||||
th {{_ 'OS_Type'}}
|
||||
td {{statistics.os.type}}
|
||||
|
||||
@@ -378,3 +378,62 @@ table td:first-child {
|
||||
.roles-settings .roles-description {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Admin Panel > People > Domains table: search + column sort + pagination,
|
||||
matching the Board Table view / other admin tables. */
|
||||
.domain-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.domain-controls input.js-domain-search {
|
||||
flex: 1 1 auto;
|
||||
height: 30px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid #bbb;
|
||||
border-radius: 0.4vw;
|
||||
}
|
||||
|
||||
.domain-controls button.js-domain-search-button {
|
||||
height: 30px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #bbb;
|
||||
background: #fff;
|
||||
border-radius: 0.4vw;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.domain-pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.domain-pagination button {
|
||||
min-width: 32px;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #bbb;
|
||||
background: #fff;
|
||||
border-radius: 0.4vw;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.domain-pagination button.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.domain-page-info {
|
||||
min-width: 72px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
th.js-domain-sort {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -122,7 +122,7 @@ template(name="people")
|
||||
else if templatesSetting.get
|
||||
+templatesGeneral
|
||||
else if domainSetting.get
|
||||
+domainGeneral(domainList=domainList)
|
||||
+domainGeneral
|
||||
|
||||
|
||||
template(name="orgGeneral")
|
||||
@@ -239,11 +239,18 @@ template(name="selectAllUser")
|
||||
input.allUserChkBox(type="checkbox", id="chkSelectAll")
|
||||
|
||||
template(name="domainGeneral")
|
||||
.domain-controls
|
||||
input.js-domain-search(type="text" placeholder="{{_ 'search'}}" aria-label="{{_ 'search'}}")
|
||||
button.js-domain-search-button(type="button") {{_ 'search'}}
|
||||
.domain-pagination
|
||||
button.js-domain-prev-page(type="button" class="{{#unless hasPrevPage}}disabled{{/unless}}" aria-label="{{_ 'previous'}}") {{_ 'previous'}}
|
||||
span.domain-page-info {{currentPage}} / {{totalPages}}
|
||||
button.js-domain-next-page(type="button" class="{{#unless hasNextPage}}disabled{{/unless}}" aria-label="{{_ 'next'}}") {{_ 'next'}}
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th {{_ 'domain'}}
|
||||
th {{_ 'domain-user-count'}}
|
||||
th.js-domain-sort(scope="col" data-sort="domain") {{_ 'domain'}} {{domainSortIndicator 'domain'}}
|
||||
th.js-domain-sort(scope="col" data-sort="count") {{_ 'domain-user-count'}} {{domainSortIndicator 'count'}}
|
||||
tbody
|
||||
if domainList.length
|
||||
each domainList
|
||||
|
||||
@@ -14,6 +14,7 @@ import InviteToBoardRolesSettings, {
|
||||
const orgsPerPage = 25;
|
||||
const teamsPerPage = 25;
|
||||
const usersPerPage = 25;
|
||||
const domainsPerPage = 25;
|
||||
let userOrgsTeamsAction = ""; //poosible actions 'addOrg', 'addTeam', 'removeOrg' or 'removeTeam' when adding or modifying a user
|
||||
let selectedUserChkBoxUserIds = [];
|
||||
|
||||
@@ -28,9 +29,9 @@ Template.people.onCreated(function () {
|
||||
this.lockedUsersSetting = new ReactiveVar(false);
|
||||
this.rolesSetting = new ReactiveVar(false);
|
||||
this.templatesSetting = new ReactiveVar(false);
|
||||
// #5850: Admin Panel > People > Domains tab.
|
||||
// #5850: Admin Panel > People > Domains tab. The domains table itself
|
||||
// (domainGeneral) now owns its data via getDomainsWithUserCountsPage.
|
||||
this.domainSetting = new ReactiveVar(false);
|
||||
this.domains = new ReactiveVar([]);
|
||||
this.subscribe('inviteToBoardRolesSettings');
|
||||
this.findOrgsOptions = new ReactiveVar({});
|
||||
this.findTeamsOptions = new ReactiveVar({});
|
||||
@@ -205,12 +206,9 @@ Template.people.onCreated(function () {
|
||||
this.templatesSetting.set('templates-setting' === targetID);
|
||||
this.domainSetting.set('domains-setting' === targetID);
|
||||
|
||||
// #5850: load the domains + per-domain user counts when the tab opens.
|
||||
if ('domains-setting' === targetID) {
|
||||
Meteor.call('getDomainsWithUserCounts', (err, res) => {
|
||||
if (!err) this.domains.set(res || []);
|
||||
});
|
||||
}
|
||||
// The Domains table (domainGeneral) now fetches its own single page from
|
||||
// getDomainsWithUserCountsPage (server-side search / sort / pagination), so
|
||||
// the parent no longer eagerly loads every domain into the browser.
|
||||
|
||||
// When switching to locked users tab, refresh the locked users list
|
||||
if ('locked-users-setting' === targetID) {
|
||||
@@ -292,9 +290,6 @@ Template.people.helpers({
|
||||
domainSetting() {
|
||||
return Template.instance().domainSetting;
|
||||
},
|
||||
domainList() {
|
||||
return Template.instance().domains.get();
|
||||
},
|
||||
orgList() {
|
||||
const tpl = Template.instance();
|
||||
// The 'org' publication already returns only the current page (server-side
|
||||
@@ -1897,3 +1892,96 @@ Template.settingsUserPopup.helpers({
|
||||
return Template.instance().errorMessage.get();
|
||||
},
|
||||
});
|
||||
|
||||
// Admin Panel > People > Domains table. Self-contained (like the Board Table
|
||||
// view): it keeps its own search / sort / page state and fetches only ONE page
|
||||
// from the server method getDomainsWithUserCountsPage, so the whole domain list
|
||||
// is never loaded into the browser.
|
||||
Template.domainGeneral.onCreated(function () {
|
||||
this.searchQuery = new ReactiveVar('');
|
||||
this.sortField = new ReactiveVar('domain'); // domain | count
|
||||
this.sortDirection = new ReactiveVar(1); // 1 ascending, -1 descending
|
||||
this.page = new ReactiveVar(1);
|
||||
this.pageData = new ReactiveVar({ rows: [], total: 0, totalPages: 1 });
|
||||
|
||||
this.autorun(() => {
|
||||
const params = {
|
||||
search: this.searchQuery.get(),
|
||||
sortField: this.sortField.get(),
|
||||
sortDirection: this.sortDirection.get(),
|
||||
page: this.page.get(),
|
||||
perPage: domainsPerPage,
|
||||
};
|
||||
Meteor.call('getDomainsWithUserCountsPage', params, (err, res) => {
|
||||
if (!err && res) {
|
||||
this.pageData.set(res);
|
||||
// Server clamps the page into range; mirror that so the controls agree.
|
||||
if (typeof res.page === 'number' && res.page !== this.page.get()) {
|
||||
this.page.set(res.page);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Template.domainGeneral.helpers({
|
||||
domainList() {
|
||||
return Template.instance().pageData.get().rows;
|
||||
},
|
||||
currentPage() {
|
||||
return Template.instance().page.get();
|
||||
},
|
||||
totalPages() {
|
||||
return Template.instance().pageData.get().totalPages || 1;
|
||||
},
|
||||
hasPrevPage() {
|
||||
return Template.instance().page.get() > 1;
|
||||
},
|
||||
hasNextPage() {
|
||||
const tpl = Template.instance();
|
||||
return tpl.page.get() < (tpl.pageData.get().totalPages || 1);
|
||||
},
|
||||
// Excel-like sort arrow on the active column, matching the Board Table view.
|
||||
domainSortIndicator(field) {
|
||||
const tpl = Template.instance();
|
||||
if (tpl.sortField.get() !== field) return '';
|
||||
return tpl.sortDirection.get() === 1 ? '▲' : '▼';
|
||||
},
|
||||
});
|
||||
|
||||
Template.domainGeneral.events({
|
||||
'click .js-domain-search-button'(event, tpl) {
|
||||
event.preventDefault();
|
||||
tpl.searchQuery.set(tpl.$('.js-domain-search').val() || '');
|
||||
tpl.page.set(1);
|
||||
},
|
||||
'keydown .js-domain-search'(event, tpl) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
tpl.searchQuery.set(tpl.$('.js-domain-search').val() || '');
|
||||
tpl.page.set(1);
|
||||
}
|
||||
},
|
||||
'click .js-domain-prev-page'(event, tpl) {
|
||||
event.preventDefault();
|
||||
const current = tpl.page.get();
|
||||
if (current > 1) tpl.page.set(current - 1);
|
||||
},
|
||||
'click .js-domain-next-page'(event, tpl) {
|
||||
event.preventDefault();
|
||||
const current = tpl.page.get();
|
||||
if (current < (tpl.pageData.get().totalPages || 1)) tpl.page.set(current + 1);
|
||||
},
|
||||
'click .js-domain-sort'(event, tpl) {
|
||||
event.preventDefault();
|
||||
const field = event.currentTarget.dataset.sort;
|
||||
if (!field) return;
|
||||
if (tpl.sortField.get() === field) {
|
||||
tpl.sortDirection.set(tpl.sortDirection.get() * -1);
|
||||
} else {
|
||||
tpl.sortField.set(field);
|
||||
tpl.sortDirection.set(1);
|
||||
}
|
||||
tpl.page.set(1);
|
||||
},
|
||||
});
|
||||
@@ -14,6 +14,11 @@ template(name="settingHeaderBar")
|
||||
i.fa.fa-users
|
||||
span {{_ 'people'}}
|
||||
|
||||
a.setting-header-btn.informations(class=isFeaturesActive href="{{pathFor 'admin-features'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-flask
|
||||
span {{_ 'features'}}
|
||||
|
||||
a.setting-header-btn.informations(class=isAdminReportsActive href="{{pathFor 'admin-reports'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-file-text-o
|
||||
|
||||
@@ -7,6 +7,9 @@ Template.settingHeaderBar.helpers({
|
||||
isPeopleActive() {
|
||||
return FlowRouter.getRouteName() === 'people' ? 'active' : '';
|
||||
},
|
||||
isFeaturesActive() {
|
||||
return FlowRouter.getRouteName() === 'admin-features' ? 'active' : '';
|
||||
},
|
||||
isAdminReportsActive() {
|
||||
return FlowRouter.getRouteName() === 'admin-reports' ? 'active' : '';
|
||||
},
|
||||
|
||||
@@ -55,9 +55,14 @@ template(name='homeSidebar')
|
||||
i.fa.fa-comment-o
|
||||
| {{_ 'activities'}}
|
||||
|
||||
a.flex.js-toggle-show-activities(title="{{_ 'show-activities'}}")
|
||||
i.fa(class="{{#if showActivities}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
span {{_ 'show-activities'}}
|
||||
if showActivities
|
||||
a.flex.activities-eye-toggle.js-toggle-show-activities(title="{{_ 'hide-activities'}}" aria-label="{{_ 'hide-activities'}}")
|
||||
i.fa.fa-eye.eye-icon
|
||||
span {{_ 'hide-activities'}}
|
||||
else
|
||||
a.flex.activities-eye-toggle.js-toggle-show-activities(title="{{_ 'show-activities'}}" aria-label="{{_ 'show-activities'}}")
|
||||
i.fa.fa-eye-slash.eye-slash-icon
|
||||
span {{_ 'show-activities'}}
|
||||
+activities(mode="board")
|
||||
|
||||
template(name="membersWidget")
|
||||
@@ -823,7 +828,7 @@ template(name="labelsWidget")
|
||||
| {{_ 'labels'}}
|
||||
.board-widget-content
|
||||
each currentBoard.labels
|
||||
a.card-label(class="card-label-{{color}}"
|
||||
a.card-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}"
|
||||
class="{{#if currentUser.isNotCommentOnly}}{{#if currentUser.isNotWorker}}js-label{{/if}}{{/if}}")
|
||||
span.card-label-name
|
||||
+viewer
|
||||
|
||||
@@ -29,7 +29,7 @@ template(name="filterSidebar")
|
||||
each currentBoard.labels
|
||||
li
|
||||
a.name.js-toggle-label-filter(data-filter-id="{{_id}}")
|
||||
span.card-label.square(class="card-label-{{color}}")
|
||||
span.card-label.square(class="{{labelColorClass color}}" style="{{labelColorStyle color}}")
|
||||
span.sidebar-list-item-description
|
||||
if name
|
||||
= name
|
||||
@@ -188,7 +188,7 @@ template(name="multiselectionSidebar")
|
||||
each currentBoard.labels
|
||||
li
|
||||
a.name.js-toggle-label-multiselection
|
||||
span.card-label.square(class="card-label-{{color}}")
|
||||
span.card-label.square(class="{{labelColorClass color}}" style="{{labelColorStyle color}}")
|
||||
span.sidebar-list-item-description
|
||||
if name
|
||||
= name
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
template(name="swimlaneHeader")
|
||||
.swimlane-header-wrap.js-swimlane-header(class=colorClass title="{{_ 'rename'}}")
|
||||
.swimlane-header-wrap.js-swimlane-header(class=colorClass style=colorStyle title="{{_ 'rename'}}")
|
||||
if this.isTemplateContainer
|
||||
+swimlaneFixedHeader(this)
|
||||
else
|
||||
@@ -120,6 +120,10 @@ template(name="setSwimlaneColorPopup")
|
||||
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
|
||||
if(isSelected color)
|
||||
i.fa.fa-check
|
||||
// #5514: native color wheel for picking a custom RGB / hex color
|
||||
.flush-left.color-wheel-wrap
|
||||
label(for="swimlaneColorWheel") {{_ 'custom-color'}}
|
||||
input.js-swimlane-color-wheel#swimlaneColorWheel(type="color" value=currentColorHex title="{{_ 'custom-color'}}")
|
||||
// Buttons aligned left too
|
||||
.flush-left
|
||||
button.primary.confirm.js-submit(style="margin-inline-start:0") {{_ 'save'}}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { SWIMLANE_COLORS } from '/models/metadata/colors';
|
||||
import { isHexColor, toHex } from '/models/lib/contrastColor';
|
||||
import Swimlanes from '/models/swimlanes';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
const { calculateIndexData } = Utils;
|
||||
@@ -166,6 +167,10 @@ Template.setSwimlaneColorPopup.helpers({
|
||||
isSelected(color) {
|
||||
return Template.instance().currentColor.get() === color;
|
||||
},
|
||||
// #5514: current color as a '#rrggbb' hex for the native color wheel <input>.
|
||||
currentColorHex() {
|
||||
return toHex(Template.instance().currentColor.get()) || '#0079bf';
|
||||
},
|
||||
});
|
||||
|
||||
Template.setSwimlaneColorPopup.events({
|
||||
@@ -178,6 +183,13 @@ Template.setSwimlaneColorPopup.events({
|
||||
const paletteData = Blaze.getData(event.currentTarget);
|
||||
tpl.currentColor.set(paletteData?.color);
|
||||
},
|
||||
// #5514: picking from the native color wheel stores a custom hex.
|
||||
'input .js-swimlane-color-wheel'(event, tpl) {
|
||||
const value = event.currentTarget.value;
|
||||
if (isHexColor(value)) {
|
||||
tpl.currentColor.set(value);
|
||||
}
|
||||
},
|
||||
async 'click .js-submit'(event, tpl) {
|
||||
event.preventDefault();
|
||||
await tpl.currentSwimlane.setColor(tpl.currentColor.get());
|
||||
|
||||
@@ -98,6 +98,19 @@ body.mobile-mode .auth-layout .auth-dialog .at-form .password-input-container .p
|
||||
margin-top: 2vh;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
/* #4394 (security): when registration / forgot-password are disabled in the
|
||||
Admin Panel, keep the corresponding links hidden even after useraccounts
|
||||
re-renders the form on a failed login. The class lives on the stable <body>
|
||||
ancestor (set in layouts.js), so this rule re-hides every recreated copy of
|
||||
the link — unlike a one-shot inline display:none that the re-render dropped. */
|
||||
body.registration-disabled .at-signup-link,
|
||||
body.registration-disabled .at-signUp {
|
||||
display: none !important;
|
||||
}
|
||||
body.forgotpwd-disabled .at-pwd-link,
|
||||
body.forgotpwd-disabled .at-forgotPwd {
|
||||
display: none !important;
|
||||
}
|
||||
.auth-layout .auth-dialog .at-form .at-signup-link .at-signUp,
|
||||
.auth-layout .auth-dialog .at-form .at-signin-link .at-signUp,
|
||||
.auth-layout .auth-dialog .at-form .at-forgotPwd .at-signUp,
|
||||
|
||||
@@ -29,6 +29,27 @@ import {
|
||||
calendar,
|
||||
} from '/imports/lib/dateUtils';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { isHexColor, contrastText } from '/models/lib/contrastColor';
|
||||
|
||||
// #5514: a color (label / list / swimlane / card) may be either a named palette
|
||||
// color rendered via a CSS class (e.g. `card-label-green`) or a custom
|
||||
// '#rrggbb' hex chosen from the color wheel. For a hex value there is no CSS
|
||||
// class, so we emit an inline `background-color` plus an automatically readable
|
||||
// text `color` (contrastText). These helpers let templates keep the class path
|
||||
// for named colors and switch to inline styles for hex.
|
||||
|
||||
// Class for a label chip: `card-label-<name>` for named colors, empty for hex.
|
||||
Blaze.registerHelper('labelColorClass', color =>
|
||||
isHexColor(color) ? '' : `card-label-${color}`,
|
||||
);
|
||||
|
||||
// Inline style for a label chip: background + readable text for a hex color,
|
||||
// empty for a named color (its CSS class already sets both).
|
||||
Blaze.registerHelper('labelColorStyle', color =>
|
||||
isHexColor(color)
|
||||
? `background-color:${color};color:${contrastText(color)};`
|
||||
: '',
|
||||
);
|
||||
|
||||
Blaze.registerHelper('currentBoard', () => {
|
||||
const ret = Utils.getCurrentBoard();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import '/client/components/settings/adminFeatures.jade';
|
||||
import '/client/components/settings/adminReports.jade';
|
||||
import '/client/components/settings/attachments.jade';
|
||||
import '/client/components/settings/attachmentSettings.jade';
|
||||
@@ -11,6 +12,7 @@ import '/client/components/settings/settingBody.jade';
|
||||
import '/client/components/settings/settingHeader.jade';
|
||||
import '/client/components/settings/translationBody.jade';
|
||||
|
||||
import '/client/components/settings/adminFeatures.js';
|
||||
import '/client/components/settings/adminReports.js';
|
||||
import '/client/components/settings/attachments.js';
|
||||
import '/client/components/settings/connectionMethod.js';
|
||||
|
||||
@@ -18,6 +18,84 @@ window.ExportHtml = Popup => {
|
||||
}
|
||||
};
|
||||
|
||||
// The board is rendered with infinite scroll — each list only keeps ~10 cards
|
||||
// in the DOM (client/components/lists/listBody.js). A naive DOM-clone export
|
||||
// would therefore drop every card past the fold. Before cloning we lift every
|
||||
// list's `cardlimit` so the whole board renders, then wait for Blaze to flush.
|
||||
const expandAllCardsForExport = async () => {
|
||||
let changed = false;
|
||||
Array.from(document.querySelectorAll('.list-body')).forEach(el => {
|
||||
try {
|
||||
let view = Blaze.getView(el);
|
||||
while (view) {
|
||||
if (view.template && view.template.viewName === 'Template.listBody' && view.templateInstance) {
|
||||
const inst = view.templateInstance();
|
||||
if (inst && inst.cardlimit && inst.cardlimit.get() < 1e6) {
|
||||
inst.cardlimit.set(1e6); // render all cards in this list
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
view = view.parentView;
|
||||
}
|
||||
} catch (e) { /* best effort per list */ }
|
||||
});
|
||||
if (changed) {
|
||||
// Let the reactive re-render settle before we snapshot the DOM.
|
||||
await new Promise(resolve => Tracker.afterFlush(() => setTimeout(resolve, 800)));
|
||||
}
|
||||
};
|
||||
|
||||
// Write the zip to disk WITHOUT holding the whole archive in memory. When the
|
||||
// browser supports the File System Access API we pipe JSZip's internal stream
|
||||
// straight to the chosen file, chunk by chunk, applying backpressure so a
|
||||
// board with many attachments/covers never buffers the entire .zip in RAM.
|
||||
// Older browsers fall back to the previous in-memory blob download.
|
||||
const saveZipStreaming = async (zip, filename) => {
|
||||
const genOpts = {
|
||||
type: 'uint8array',
|
||||
streamFiles: true,
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: { level: 6 },
|
||||
};
|
||||
if (typeof window.showSaveFilePicker === 'function') {
|
||||
let handle = null;
|
||||
try {
|
||||
handle = await window.showSaveFilePicker({
|
||||
suggestedName: filename,
|
||||
types: [{ description: 'Zip archive', accept: { 'application/zip': ['.zip'] } }],
|
||||
});
|
||||
} catch (e) {
|
||||
// User dismissed the save dialog — nothing to do.
|
||||
if (e && e.name === 'AbortError') return;
|
||||
handle = null; // any other failure → fall back to blob download
|
||||
}
|
||||
if (handle) {
|
||||
const writable = await handle.createWritable();
|
||||
await new Promise((resolve, reject) => {
|
||||
const helper = zip.generateInternalStream(genOpts);
|
||||
helper
|
||||
.on('data', data => {
|
||||
// Pause the zip stream until this chunk is flushed to disk.
|
||||
helper.pause();
|
||||
writable.write(data).then(() => helper.resume(), reject);
|
||||
})
|
||||
.on('error', reject)
|
||||
.on('end', () => { writable.close().then(resolve, reject); })
|
||||
.resume();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Fallback: build the blob in memory (older browsers only).
|
||||
const content = await zip.generateAsync({
|
||||
type: 'blob',
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: { level: 6 },
|
||||
});
|
||||
saveAs(content, filename);
|
||||
};
|
||||
|
||||
const getPageHtmlString = (clonedElement = null) => {
|
||||
const element = clonedElement || window.document.querySelector('html');
|
||||
return `<!doctype html>${element.outerHTML}`;
|
||||
@@ -526,6 +604,10 @@ body { font-family: sans-serif !important; }
|
||||
const zipFilename = boardTitle || boardSlug;
|
||||
const zipDirName = zipFilename; // Directory name inside the ZIP
|
||||
|
||||
// Render EVERY card first (infinite scroll otherwise leaves most cards out
|
||||
// of the DOM), then snapshot.
|
||||
await expandAllCardsForExport();
|
||||
|
||||
// Clone the HTML element to process for export without modifying live DOM
|
||||
const htmlElement = window.document.querySelector('html');
|
||||
const clonedHtmlElement = htmlElement.cloneNode(true);
|
||||
@@ -549,8 +631,8 @@ body { font-family: sans-serif !important; }
|
||||
|
||||
addBoardHTMLToZip(boardSlug, zip, clonedHtmlElement, zipDirName);
|
||||
|
||||
const content = await zip.generateAsync({ type: 'blob' });
|
||||
saveAs(content, `${zipFilename}.zip`);
|
||||
// Stream the .zip straight to disk (low memory) with a blob fallback.
|
||||
await saveZipStreaming(zip, `${zipFilename}.zip`);
|
||||
// No page reload - impersonation session is preserved!
|
||||
};
|
||||
};
|
||||
+19
-29
@@ -19,8 +19,9 @@ import {
|
||||
now,
|
||||
createDate,
|
||||
fromNow,
|
||||
calendar
|
||||
calendar
|
||||
} from '/imports/lib/dateUtils';
|
||||
import { weekRange } from '/models/lib/weekStart';
|
||||
// Sidebar is imported late to avoid circular dependency (sidebar.js needs its
|
||||
// jade template loaded first, but router.js → filter.js would load it too early)
|
||||
let _Sidebar;
|
||||
@@ -119,44 +120,33 @@ class DateFilter {
|
||||
// weeks up to today +/- offset. This considers the user's preferred
|
||||
// start of week day (as defined by Meteor).
|
||||
relativeWeek(offset, week) {
|
||||
if (this._filterState == 'thisweek') {
|
||||
// #4881: toggle per-week so clicking the same button again clears it, but
|
||||
// clicking the OTHER week switches to it instead of just resetting (the old
|
||||
// code reset on either state, so you could not go straight from "this week"
|
||||
// to "next week").
|
||||
const targetState = week === 'next' ? 'nextweek' : 'thisweek';
|
||||
if (this._filterState === targetState) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._filterState == 'nextweek') {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// getStartDayOfWeek returns the offset from Sunday of the user's
|
||||
// preferred starting day of the week. This date should be added
|
||||
// to the moment start of week to get the real start of week date.
|
||||
// The default is 1, meaning Monday.
|
||||
// #4881: the old window came from startOf(now(), 'week'), but native
|
||||
// dateUtils.startOf() has no 'week' case, so it returned "now" unchanged and
|
||||
// the window started at today + startDayOfWeek — i.e. it selected NEXT
|
||||
// week's cards for "this week" and ignored the configured start weekday.
|
||||
// weekRange() computes the correct window that CONTAINS today for the user's
|
||||
// configured start day of week (0=Sunday..6=Saturday, default Monday).
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const weekStartDay = currentUser ? currentUser.getStartDayOfWeek() : 1;
|
||||
const weekOffset = week === 'next' ? 1 : 0;
|
||||
const { start: WeekStart, end: WeekEnd } = weekRange(now(), weekStartDay, weekOffset);
|
||||
|
||||
if (week === 'this') {
|
||||
// Create week start and end dates
|
||||
var WeekStart = startOf(add(startOf(now(), 'week'), weekStartDay, 'days'), 'day');
|
||||
var WeekEnd = endOf(add(WeekStart, 6, 'days'), 'day');
|
||||
|
||||
this._updateState('thisweek');
|
||||
} else if (week === 'next') {
|
||||
// Create next week start and end dates
|
||||
var WeekStart = startOf(add(startOf(now(), 'week'), weekStartDay + 7, 'days'), 'day');
|
||||
var WeekEnd = endOf(add(WeekStart, 6, 'days'), 'day');
|
||||
|
||||
this._updateState('nextweek');
|
||||
}
|
||||
|
||||
var startDate = WeekStart;
|
||||
var endDate = WeekEnd;
|
||||
this._updateState(targetState);
|
||||
|
||||
if (offset >= 0) {
|
||||
this._filter = { $gte: startDate, $lte: endDate };
|
||||
this._filter = { $gte: WeekStart, $lte: WeekEnd };
|
||||
} else {
|
||||
this._filter = { $lte: startDate, $gte: endDate };
|
||||
this._filter = { $lte: WeekStart, $gte: WeekEnd };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { listCardsSelector } from '/models/lib/swimlaneFilter';
|
||||
const { windowCountId: windowCountIdImpl, resolveCardsLoadingMode } = require('/models/lib/cardsLoading');
|
||||
|
||||
// Client-only mirror of the server `boardListCardCounts` docs published by the
|
||||
// 'boardListCardCount' publication (one doc per list/swimlane window, holding
|
||||
// the total card count so the board knows whether more cards remain to scroll
|
||||
// in). Only used in lazy card-loading mode.
|
||||
export const BoardListCardCounts = new Mongo.Collection('boardListCardCounts');
|
||||
|
||||
// Effective card-loading mode. Prefers the admin Setting doc (reactive, so an
|
||||
// Admin Panel change is picked up), falls back to the public env value, then to
|
||||
// 'all'. See server/cards-loading.js and Admin Panel / Features.
|
||||
export function cardsLoadingMode() {
|
||||
const setting = ReactiveCache.getCurrentSetting();
|
||||
const fromDoc = setting && setting.cardsLoading;
|
||||
const fromEnv =
|
||||
Meteor.settings && Meteor.settings.public && Meteor.settings.public.cardsLoading;
|
||||
return resolveCardsLoadingMode(fromDoc || fromEnv);
|
||||
}
|
||||
|
||||
export function isLazyCards() {
|
||||
return cardsLoadingMode() === 'lazy';
|
||||
}
|
||||
|
||||
// Stable id for one (list, swimlane) window's count doc (shared, unit-tested).
|
||||
export function windowCountId(listId, swimlaneId) {
|
||||
return windowCountIdImpl(listId, swimlaneId);
|
||||
}
|
||||
|
||||
// Accurate total card count for `list.cards(swimlaneId)` in lazy mode, where
|
||||
// minimongo holds only the loaded window. Builds the SAME selector
|
||||
// list.cards(swimlaneId) uses (listCardsSelector + the board Filter), subscribes
|
||||
// (deduped) to the server-side count for it, and returns that count. Returns null
|
||||
// when not in lazy mode or the count has not arrived yet, so callers can fall
|
||||
// back to the loaded-window length. Because the selector is identical to the one
|
||||
// list.cards() runs against minimongo, the server count is exactly the accurate
|
||||
// version of whatever list.cards(swimlaneId).length would report.
|
||||
export function lazyListCardCount(list, swimlaneId) {
|
||||
if (!isLazyCards() || !list || !list.boardId) return null;
|
||||
const selector = listCardsSelector(
|
||||
list._id,
|
||||
swimlaneId,
|
||||
list.orphanedCardsSwimlaneIds ? list.orphanedCardsSwimlaneIds(swimlaneId) : undefined,
|
||||
);
|
||||
const mongoSelector = Filter.mongoSelector(selector);
|
||||
const id = windowCountId(list._id, swimlaneId);
|
||||
const inst = typeof Template !== 'undefined' && Template.instance ? Template.instance() : null;
|
||||
if (inst && inst.subscribe) {
|
||||
inst.subscribe('boardListCardCount', id, list.boardId, mongoSelector);
|
||||
}
|
||||
const doc = BoardListCardCounts.findOne(id);
|
||||
return doc ? doc.count : null;
|
||||
}
|
||||
+27
-101
@@ -4,6 +4,7 @@ import { Tracker } from 'meteor/tracker';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { computePopupOffset } from '/client/lib/popupOffset';
|
||||
|
||||
window.Popup = new (class {
|
||||
constructor() {
|
||||
@@ -222,108 +223,33 @@ window.Popup = new (class {
|
||||
return () => {
|
||||
Utils.windowResizeDep.depend();
|
||||
|
||||
if (Utils.isMiniScreen()) return { left: 0, top: 0 };
|
||||
// #5667: read the current page scroll and the opener's document offset,
|
||||
// then delegate the geometry to the pure, unit-tested computePopupOffset.
|
||||
// It lays the popup out in VIEWPORT coordinates — so a date-picker opened
|
||||
// low on a scrolled page stays fully visible instead of extending past the
|
||||
// visible area — and returns document coordinates for the absolute style.
|
||||
const hasOpener = !!($element && $element.length);
|
||||
const offset = hasOpener ? $element.offset() : null;
|
||||
const opener = hasOpener
|
||||
? { top: offset.top, left: offset.left, height: $element.outerHeight() }
|
||||
: null;
|
||||
|
||||
const viewportWidth = $(window).width();
|
||||
const viewportHeight = $(window).height();
|
||||
const viewportPadding = 10;
|
||||
// Calculate actual popup width based on CSS: min(380px, 55vw)
|
||||
const popupWidth = Math.min(380, viewportWidth * 0.55);
|
||||
|
||||
// Card details popup: dock it to the top of the viewport so it overlays
|
||||
// the global and board header bars (its z-index already sits above them)
|
||||
// instead of opening downward from the clicked minicard.
|
||||
if (popupName === 'cardDetailsPopup') {
|
||||
const cardWidth = Math.min(600, viewportWidth * 0.8);
|
||||
const centeredLeft = (viewportWidth - cardWidth) / 2;
|
||||
// top is forced to 0 in CSS (.pop-over[data-popup="cardDetailsPopup"])
|
||||
// so the card overlays the header bars; use the full viewport height.
|
||||
return {
|
||||
left: Math.max(viewportPadding, centeredLeft),
|
||||
top: 0,
|
||||
maxHeight: viewportHeight,
|
||||
};
|
||||
}
|
||||
|
||||
// If the opener element is missing (e.g., programmatic open), fallback to viewport origin
|
||||
if (!$element || $element.length === 0) {
|
||||
return {
|
||||
left: viewportPadding,
|
||||
top: viewportPadding,
|
||||
maxHeight: viewportHeight - viewportPadding * 2,
|
||||
};
|
||||
}
|
||||
|
||||
const offset = $element.offset();
|
||||
const openerTop = offset.top;
|
||||
const openerBottom = offset.top + $element.outerHeight();
|
||||
const clampedLeft = Math.max(
|
||||
viewportPadding,
|
||||
Math.min(offset.left, viewportWidth - popupWidth - viewportPadding),
|
||||
);
|
||||
|
||||
// Check if this is an admin panel edit popup
|
||||
const isAdminEditPopup = $element.hasClass('edit-user') ||
|
||||
$element.hasClass('edit-org') ||
|
||||
$element.hasClass('edit-team');
|
||||
|
||||
if (isAdminEditPopup) {
|
||||
// Center the popup horizontally and use full height
|
||||
const centeredLeft = (viewportWidth - popupWidth) / 2;
|
||||
|
||||
return {
|
||||
left: Math.max(viewportPadding, centeredLeft), // Ensure popup doesn't go off screen
|
||||
top: viewportPadding, // Start from top with small margin
|
||||
maxHeight: viewportHeight - viewportPadding * 2, // Use full height minus small margins
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate available heights around the opener
|
||||
const spaceBelow = viewportHeight - openerBottom - viewportPadding;
|
||||
const spaceAbove = openerTop - viewportPadding;
|
||||
const preferBelow = spaceBelow >= spaceAbove;
|
||||
|
||||
// For language popup, don't use dynamic height to avoid overlapping board
|
||||
const isLanguagePopup = $element.hasClass('js-change-language');
|
||||
let availableHeight;
|
||||
|
||||
if (isLanguagePopup) {
|
||||
// For language popup, position content area below right vertical scrollbar
|
||||
const languageTop = openerBottom;
|
||||
const languageAvailable = Math.max(
|
||||
0,
|
||||
viewportHeight - languageTop - viewportPadding,
|
||||
);
|
||||
const calculatedHeight = Math.min(
|
||||
languageAvailable,
|
||||
viewportHeight * 0.5,
|
||||
); // Max 50% of viewport
|
||||
|
||||
return {
|
||||
left: clampedLeft,
|
||||
top: languageTop,
|
||||
maxHeight: Math.max(calculatedHeight, 0),
|
||||
};
|
||||
} else {
|
||||
// For other popups, choose the side with more space and keep popup fully in viewport.
|
||||
if (preferBelow) {
|
||||
availableHeight = Math.max(0, Math.min(spaceBelow, viewportHeight * 0.8));
|
||||
return {
|
||||
left: clampedLeft,
|
||||
top: openerBottom,
|
||||
maxHeight: availableHeight,
|
||||
};
|
||||
}
|
||||
|
||||
availableHeight = Math.max(0, Math.min(spaceAbove, viewportHeight * 0.8));
|
||||
const top = Math.max(viewportPadding, openerTop - availableHeight);
|
||||
|
||||
return {
|
||||
left: clampedLeft,
|
||||
top,
|
||||
maxHeight: availableHeight,
|
||||
};
|
||||
}
|
||||
return computePopupOffset({
|
||||
viewportWidth: $(window).width(),
|
||||
viewportHeight: $(window).height(),
|
||||
scrollTop: $(window).scrollTop() || 0,
|
||||
scrollLeft: $(window).scrollLeft() || 0,
|
||||
opener,
|
||||
popupName,
|
||||
isMiniScreen: Utils.isMiniScreen(),
|
||||
isAdminEditPopup:
|
||||
hasOpener &&
|
||||
($element.hasClass('edit-user') ||
|
||||
$element.hasClass('edit-org') ||
|
||||
$element.hasClass('edit-team')),
|
||||
isLanguagePopup: hasOpener && $element.hasClass('js-change-language'),
|
||||
viewportPadding: 10,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
'use strict';
|
||||
|
||||
// Pure geometry for Popup._getOffset — extracted so the positioning math is
|
||||
// unit-testable without a DOM / Meteor (see tests/popupOffset.test.cjs).
|
||||
//
|
||||
// #5667 ("Problem when scrolling in a map with date fields"): a date-picker
|
||||
// calendar (due/start/end date, or a date custom field) opened low on the screen
|
||||
// appeared partly outside the visible area, and — because the pop-over is
|
||||
// `position: absolute` (document coordinates) — trying to scroll it into view
|
||||
// moved it along with the page, so the calendar could never be seen fully.
|
||||
//
|
||||
// Root cause: _getOffset computed the space above / below the opener (and the
|
||||
// clamped `top`) from the opener's DOCUMENT offset (`$el.offset()`) mixed with
|
||||
// the VIEWPORT height, ignoring the page scroll. On a scrolled page that placed
|
||||
// the anchored popup outside the visible viewport.
|
||||
//
|
||||
// Fix: do the space / clamp math in VIEWPORT coordinates (subtract
|
||||
// scrollTop/scrollLeft), then convert the chosen top/left back to DOCUMENT
|
||||
// coordinates (add the scroll back) for the absolute style. The popup box is then
|
||||
// always laid out fully within the visible viewport. When the page is not
|
||||
// scrolled (scrollTop === scrollLeft === 0) the output is identical to the
|
||||
// previous behaviour, so nothing regresses in the common case.
|
||||
|
||||
function computePopupOffset(params) {
|
||||
const {
|
||||
viewportWidth,
|
||||
viewportHeight,
|
||||
scrollTop = 0,
|
||||
scrollLeft = 0,
|
||||
// Opener document offset { top, left, height }, or null for a programmatic
|
||||
// open with no reference element.
|
||||
opener = null,
|
||||
popupName,
|
||||
isMiniScreen = false,
|
||||
isAdminEditPopup = false,
|
||||
isLanguagePopup = false,
|
||||
viewportPadding = 10,
|
||||
} = params;
|
||||
|
||||
if (isMiniScreen) return { left: 0, top: 0 };
|
||||
|
||||
// Actual popup width from CSS: min(380px, 55vw).
|
||||
const popupWidth = Math.min(380, viewportWidth * 0.55);
|
||||
|
||||
// Card details popup: docked to the top of the viewport (CSS also forces
|
||||
// top:0) so it overlays the header bars instead of opening from the minicard.
|
||||
if (popupName === 'cardDetailsPopup') {
|
||||
const cardWidth = Math.min(600, viewportWidth * 0.8);
|
||||
const centeredLeft = (viewportWidth - cardWidth) / 2;
|
||||
return {
|
||||
left: Math.max(viewportPadding, centeredLeft),
|
||||
top: scrollTop, // viewport top in document coords
|
||||
maxHeight: viewportHeight,
|
||||
};
|
||||
}
|
||||
|
||||
// No opener element: fall back to the top-left of the VISIBLE viewport.
|
||||
if (!opener) {
|
||||
return {
|
||||
left: viewportPadding + scrollLeft,
|
||||
top: viewportPadding + scrollTop,
|
||||
maxHeight: viewportHeight - viewportPadding * 2,
|
||||
};
|
||||
}
|
||||
|
||||
// Opener position in viewport coordinates (this is the #5667 fix: the previous
|
||||
// code used the raw document offset here).
|
||||
const openerTopVp = opener.top - scrollTop;
|
||||
const openerBottomVp = openerTopVp + opener.height;
|
||||
const openerLeftVp = opener.left - scrollLeft;
|
||||
|
||||
const clampedLeftVp = Math.max(
|
||||
viewportPadding,
|
||||
Math.min(openerLeftVp, viewportWidth - popupWidth - viewportPadding),
|
||||
);
|
||||
const clampedLeft = clampedLeftVp + scrollLeft;
|
||||
|
||||
// Admin panel edit popups: horizontally centered, anchored to the viewport top.
|
||||
if (isAdminEditPopup) {
|
||||
const centeredLeft = (viewportWidth - popupWidth) / 2;
|
||||
return {
|
||||
left: Math.max(viewportPadding, centeredLeft),
|
||||
top: viewportPadding + scrollTop,
|
||||
maxHeight: viewportHeight - viewportPadding * 2,
|
||||
};
|
||||
}
|
||||
|
||||
const spaceBelow = viewportHeight - openerBottomVp - viewportPadding;
|
||||
const spaceAbove = openerTopVp - viewportPadding;
|
||||
const preferBelow = spaceBelow >= spaceAbove;
|
||||
|
||||
// Language popup: fixed-ish height below the opener, capped at 50% viewport.
|
||||
if (isLanguagePopup) {
|
||||
const languageTopVp = openerBottomVp;
|
||||
const languageAvailable = Math.max(
|
||||
0,
|
||||
viewportHeight - languageTopVp - viewportPadding,
|
||||
);
|
||||
const calculatedHeight = Math.min(languageAvailable, viewportHeight * 0.5);
|
||||
return {
|
||||
left: clampedLeft,
|
||||
top: languageTopVp + scrollTop,
|
||||
maxHeight: Math.max(calculatedHeight, 0),
|
||||
};
|
||||
}
|
||||
|
||||
// Other popups: open on the side with more room and stay fully in the viewport.
|
||||
if (preferBelow) {
|
||||
const maxHeight = Math.max(0, Math.min(spaceBelow, viewportHeight * 0.8));
|
||||
return { left: clampedLeft, top: openerBottomVp + scrollTop, maxHeight };
|
||||
}
|
||||
|
||||
const maxHeight = Math.max(0, Math.min(spaceAbove, viewportHeight * 0.8));
|
||||
const topVp = Math.max(viewportPadding, openerTopVp - maxHeight);
|
||||
return { left: clampedLeft, top: topVp + scrollTop, maxHeight };
|
||||
}
|
||||
|
||||
module.exports = { computePopupOffset };
|
||||
+16
-6
@@ -4,16 +4,26 @@ import { Tracker } from 'meteor/tracker';
|
||||
import { findWhere, where, uniqBy, groupBy, indexBy, debounce, once } from '/imports/lib/collectionHelpers';
|
||||
import Settings from '/models/settings';
|
||||
import Users from '/models/users';
|
||||
import { computeBoardBackground } from '/models/lib/boardBackground';
|
||||
|
||||
export const Utils = {
|
||||
async setBackgroundImage(url) {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
if (currentBoard.backgroundImageURL !== undefined) {
|
||||
$(".board-wrapper").css({"background":"url(" + currentBoard.backgroundImageURL + ")","background-size":"cover"});
|
||||
$(".swimlane,.swimlane .list,.swimlane .list .list-body,.swimlane .list:first-child .list-body").css({"background-color":"transparent"});
|
||||
$(".minicard").css({"opacity": "0.9"});
|
||||
} else if (currentBoard["background-color"]) {
|
||||
await currentBoard.setColor(currentBoard["background-color"]);
|
||||
const bg = computeBoardBackground(currentBoard);
|
||||
const swimlaneSelector =
|
||||
".swimlane,.swimlane .list,.swimlane .list .list-body,.swimlane .list:first-child .list-body";
|
||||
if (bg.type === "image") {
|
||||
$(".board-wrapper").css({ "background": "url(" + bg.url + ")", "background-size": "cover" });
|
||||
$(swimlaneSelector).css({ "background-color": "transparent" });
|
||||
$(".minicard").css({ "opacity": "0.9" });
|
||||
} else {
|
||||
// #4978: clear any inline background left over from a previously shown
|
||||
// board, so switching image -> color or image -> plain (e.g. via the
|
||||
// favorites bar) actually removes the old image. The board's color, when
|
||||
// any, is applied reactively via the `.board-wrapper` colorClass.
|
||||
$(".board-wrapper").css({ "background": "", "background-size": "" });
|
||||
$(swimlaneSelector).css({ "background-color": "" });
|
||||
$(".minicard").css({ "opacity": "" });
|
||||
}
|
||||
},
|
||||
/** returns the current board id
|
||||
|
||||
@@ -576,6 +576,30 @@ FlowRouter.route('/translation', {
|
||||
},
|
||||
});
|
||||
|
||||
FlowRouter.route('/admin-features', {
|
||||
name: 'admin-features',
|
||||
triggersEnter: [
|
||||
AccountsTemplates.ensureSignedIn,
|
||||
() => {
|
||||
Session.set('currentBoard', null);
|
||||
Session.set('currentList', null);
|
||||
Session.set('currentCard', null);
|
||||
Session.set('popupCardId', null);
|
||||
Session.set('popupCardBoardId', null);
|
||||
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
EscapeActions.executeAll();
|
||||
},
|
||||
],
|
||||
action() {
|
||||
this.render('defaultLayout', {
|
||||
headerBar: 'settingHeaderBar',
|
||||
content: 'adminFeatures',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
FlowRouter.route('*', {
|
||||
action() {
|
||||
this.render('defaultLayout', { content: 'notFound' });
|
||||
|
||||
@@ -0,0 +1,803 @@
|
||||
# WeKan with FerretDB v1 + embedded SQLite (no PostgreSQL, no MongoDB)
|
||||
#
|
||||
# Use this compose file instead of docker-compose.yml for a light, self-contained
|
||||
# database: FerretDB v1 (the wekan/FerretDB fork, https://github.com/wekan/FerretDB)
|
||||
# with its embedded pure-Go SQLite backend, which speaks the MongoDB wire protocol.
|
||||
# There is no separate PostgreSQL or MongoDB server — FerretDB stores everything in
|
||||
# SQLite files on a volume, and WeKan stores attachments/avatars on the filesystem.
|
||||
# For the PostgreSQL-backed FerretDB 2 option instead, see
|
||||
# docker-compose-ferretdb-v2-postgresql.yml.
|
||||
#
|
||||
# The FerretDB binary is downloaded (once, then cached on the volume) from the
|
||||
# newest wekan/FerretDB release: https://github.com/wekan/FerretDB/releases
|
||||
# The ferretdb.zip there contains ferretdb/<arch>/ferretdb-<arch> for every
|
||||
# platform; the container picks the binary matching its architecture (amd64,
|
||||
# arm64, ppc64le, s390x, riscv64, ...).
|
||||
#
|
||||
# Setup:
|
||||
# docker compose -f docker-compose-ferretdb-v1-sqlite.yml up -d
|
||||
#
|
||||
# To stop:
|
||||
# docker compose -f docker-compose-ferretdb-v1-sqlite.yml stop
|
||||
|
||||
version: '3.8'
|
||||
services:
|
||||
|
||||
# FerretDB v1 with the embedded SQLite backend (MongoDB-compatible on :27017).
|
||||
# A tiny Debian container fetches the wekan/FerretDB v1 release binary for its
|
||||
# architecture (cached on the volume so restarts do not re-download) and runs it.
|
||||
# SQLite data lives in /data/sqlite on the ferretdb-data volume.
|
||||
ferretdb:
|
||||
image: debian:bookworm-slim
|
||||
container_name: wekan-ferretdb
|
||||
restart: always
|
||||
environment:
|
||||
DO_NOT_TRACK: "1"
|
||||
FERRETDB_TELEMETRY: disable
|
||||
# Pin a specific release instead of the newest by setting e.g.
|
||||
# FERRETDB_RELEASE=download/v1.24.2
|
||||
FERRETDB_RELEASE: ${FERRETDB_RELEASE:-latest/download}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
BIN=/data/bin/ferretdb
|
||||
if [ ! -x "$$BIN" ]; then
|
||||
echo "Fetching FerretDB v1 from the wekan/FerretDB release ..."
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq --no-install-recommends curl unzip ca-certificates >/dev/null
|
||||
arch="$$(dpkg --print-architecture)"
|
||||
case "$$arch" in ppc64el) arch=ppc64le ;; esac
|
||||
mkdir -p /data/bin
|
||||
curl -fSL --retry 5 -o /tmp/ferretdb.zip \
|
||||
"https://github.com/wekan/FerretDB/releases/$${FERRETDB_RELEASE}/ferretdb.zip"
|
||||
unzip -o -j /tmp/ferretdb.zip "ferretdb/$${arch}/ferretdb-$${arch}" -d /data/bin
|
||||
mv "/data/bin/ferretdb-$${arch}" "$$BIN"
|
||||
chmod +x "$$BIN"
|
||||
rm -f /tmp/ferretdb.zip
|
||||
fi
|
||||
mkdir -p /data/files/db
|
||||
exec "$$BIN" \
|
||||
--handler=sqlite \
|
||||
--sqlite-url=file:/data/files/db/ \
|
||||
--listen-addr=0.0.0.0:27017 \
|
||||
--telemetry=disable
|
||||
volumes:
|
||||
- ferretdb-data:/data
|
||||
networks:
|
||||
- wekan-tier
|
||||
# Expose only on localhost by default; remove to keep it internal to the network.
|
||||
ports:
|
||||
- 127.0.0.1:27017:27017
|
||||
|
||||
wekan:
|
||||
#-------------------------------------------------------------------------------------
|
||||
# ==== WEKAN FROM GITHUB/QUAY/DOCKER HUB ====
|
||||
# All of GitHub, Quay and Docker Hub have latest, but because
|
||||
# latest tag changes when is newest release,
|
||||
# when upgrading would be better to use version tag.
|
||||
# a) Using specific version tag is better:
|
||||
# image: ghcr.io/wekan/wekan:v6.89
|
||||
# image: quay.io/wekan/wekan:v6.89
|
||||
# image: wekanteam/wekan:v6.89
|
||||
# b) GitHub Container registry.
|
||||
# b1) Latest release tag:
|
||||
image: ghcr.io/wekan/wekan:latest
|
||||
# b2) Newest git commit automatic build:
|
||||
#image: ghcr.io/wekan/wekan:main
|
||||
# c) Quay:
|
||||
#image: quay.io/wekan/wekan:latest
|
||||
# d) Docker Hub:
|
||||
#image: wekanteam/wekan:latest
|
||||
#-------------------------------------------------------------------------------------
|
||||
container_name: wekan-app
|
||||
# On CentOS 7 there is seccomp issue with glibc 6,
|
||||
# so CentOS 7 users shoud use these security_opt seccomp:unconfined
|
||||
# settings to get WeKan working. See:
|
||||
# - https://github.com/wekan/wekan/issues/4585
|
||||
# - https://github.com/wekan/wekan/issues/4587
|
||||
#security_opt:
|
||||
# - seccomp:unconfined
|
||||
restart: always
|
||||
networks:
|
||||
- wekan-tier
|
||||
#-------------------------------------------------------------------------------------
|
||||
# ==== BUILD wekan-app DOCKER CONTAINER FROM SOURCE, if you uncomment these ====
|
||||
# ==== and use commands: docker compose up -d --build
|
||||
#build:
|
||||
# context: .
|
||||
# dockerfile: Dockerfile
|
||||
#-------------------------------------------------------------------------------------
|
||||
ports:
|
||||
# Docker outsideport:insideport. Do not add anything extra here.
|
||||
# For example, if you want to have wekan on port 3001,
|
||||
# use 3001:8080 . Do not add any extra address etc here, that way it does not work.
|
||||
# remove port mapping if you use nginx reverse proxy, port 8080 is already exposed to wekan-tier network
|
||||
- 80:8080
|
||||
environment:
|
||||
#-------------------- USING MONGODB CHANGE STREAMS WITH REPLICA SETS AT CURRENT DATABASE --------------------
|
||||
# If you would not like to use Change Streams and replica set for improving speed, change to use polling:
|
||||
#- METEOR_REACTIVITY_ORDER=polling
|
||||
# https://forums.meteor.com/t/meteor-3-5-beta-change-streams-performance-improvements/64461#change-streams-setup-3
|
||||
# https://github.com/meteor/meteor/blob/release-3.5/v3-docs/docs/performance/change-streams-observer-driver.md#choosing-the-reactivity-driver-order
|
||||
# Keep oplog/polling fallback available for deployments that cannot use Change Streams:
|
||||
# https://github.com/wekan/wekan/issues/6307#issuecomment-4299349231
|
||||
# FerretDB has no MongoDB change streams / replica-set oplog, so use polling:
|
||||
- METEOR_REACTIVITY_ORDER=polling
|
||||
- DDP_TRANSPORT=uws
|
||||
#- DDP_TRANSPORT=sockjs
|
||||
#-----------------------------------------------------------------
|
||||
# ==== WRITEABLE PATH FOR FILE UPLOADS ====
|
||||
- WRITABLE_PATH=/data
|
||||
#-----------------------------------------------------------------
|
||||
# ==== AWS S3 FOR FILES ====
|
||||
# Any region. For example:
|
||||
# us-standard,us-west-1,us-west-2,
|
||||
# eu-west-1,eu-central-1,
|
||||
# ap-southeast-1,ap-northeast-1,sa-east-1
|
||||
#
|
||||
#- S3='{"s3":{"key": "xxx", "secret": "xxx", "bucket": "xxx", "region": "xxx"}}'
|
||||
#- S3_SECRET_FILE=/run/secrets/s3_secret
|
||||
#-----------------------------------------------------------------
|
||||
# ==== MONGO_URL — points to FerretDB (MongoDB-compatible) ====
|
||||
#- MONGO_URL=mongodb://wekan-ferretdb:27017/wekan
|
||||
- MONGO_URL=mongodb://ferretdb:27017/wekan
|
||||
#- MONGO_PASSWORD_FILE=/run/secrets/mongo_password
|
||||
#---------------------------------------------------------------
|
||||
# ==== ROOT_URL SETTING ====
|
||||
# Change ROOT_URL to your real Wekan URL, for example:
|
||||
# If you have Caddy/Nginx/Apache providing SSL
|
||||
# - https://example.com
|
||||
# - https://boards.example.com
|
||||
# This can be problematic with avatars https://github.com/wekan/wekan/issues/1776
|
||||
# - https://example.com/wekan
|
||||
# If without https, can be only wekan node, no need for Caddy/Nginx/Apache if you don't need them
|
||||
# - http://example.com
|
||||
# - http://boards.example.com
|
||||
# - http://192.168.1.100 <=== using at local LAN
|
||||
- ROOT_URL=http://localhost # <=== using only at same laptop/desktop where Wekan is installed
|
||||
#---------------------------------------------------------------
|
||||
# ==== EMAIL SETTINGS ====
|
||||
# Email settings are only at MAIL_URL and MAIL_FROM.
|
||||
# Admin Panel has test button, but it's not used for settings.
|
||||
# see https://github.com/wekan/wekan/blob/main/docs/Email/Troubleshooting-Mail.md
|
||||
# For SSL in email, change smtp:// to smtps://
|
||||
# NOTE: Special characters need to be url-encoded in MAIL_URL.
|
||||
# You can encode those characters for example at: https://www.urlencoder.org
|
||||
#- MAIL_URL=smtp://user:pass@mailserver.example.com:25/
|
||||
- MAIL_URL=smtp://<mail_url>:25/?ignoreTLS=true&tls={rejectUnauthorized:false}
|
||||
- MAIL_FROM=Wekan Notifications <noreply.wekan@mydomain.com>
|
||||
# Currently MAIL_SERVICE is not in use.
|
||||
#- MAIL_SERVICE=Outlook365
|
||||
#- MAIL_SERVICE_USER=firstname.lastname@hotmail.com
|
||||
#- MAIL_SERVICE_PASSWORD=SecretPassword
|
||||
#- MAIL_SERVICE_PASSWORD_FILE=/run/secrets/mail_service_password
|
||||
#---------------------------------------------------------------
|
||||
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
|
||||
# Add more Node heap, this is done by default at Dockerfile:
|
||||
# - NODE_OPTIONS="--max_old_space_size=4096"
|
||||
# Add more stack, this is done at Dockerfile:
|
||||
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
|
||||
#---------------------------------------------------------------
|
||||
# ==== OPTIONAL: MONGO OPLOG SETTINGS =====
|
||||
# https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-378343587
|
||||
# HIGHLY RECOMMENDED for pub/sub performance!
|
||||
# MongoDB oplog is used by Meteor for real-time data synchronization.
|
||||
# Without oplog, Meteor falls back to polling which increases:
|
||||
# - CPU usage by 3-5x
|
||||
# - Network traffic significantly
|
||||
# - Latency from 50ms to 2000ms
|
||||
# Must configure MongoDB replica set first
|
||||
# See: https://blog.meteor.com/tuning-meteor-mongo-livedata-for-scalability-13fe9deb8908
|
||||
# FerretDB emulates OpLog via --replication-set-name=rs0.
|
||||
# HIGHLY RECOMMENDED — enables real-time DDP updates (sub-100ms latency).
|
||||
#- MONGO_OPLOG_URL=mongodb://wekan-ferretdb:27017/local?replicaSet=rs0
|
||||
# MONGO_OPLOG_URL intentionally unset: FerretDB v1 has no replica-set oplog (WeKan uses polling).
|
||||
#---------------------------------------------------------------
|
||||
# ==== OPTIONAL: KADIRA PERFORMANCE MONITORING FOR METEOR ====
|
||||
# https://github.com/edemaine/kadira-compose
|
||||
# https://github.com/meteor/meteor-apm-agent
|
||||
# https://blog.meteor.com/kadira-apm-is-now-open-source-490469ffc85f
|
||||
#- APM_OPTIONS_ENDPOINT=http://<kadira-ip>:11011
|
||||
#- APM_APP_ID=
|
||||
#- APM_APP_SECRET=
|
||||
#---------------------------------------------------------------
|
||||
# ==== OPTIONAL: LOGS AND STATS ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Features/Logs.md
|
||||
#
|
||||
# Daily export of Wekan changes as JSON to Logstash and ElasticSearch / Kibana (ELK)
|
||||
# https://github.com/wekan/wekan-logstash
|
||||
#
|
||||
# Statistics Python script for Wekan Dashboard
|
||||
# https://github.com/wekan/wekan-stats
|
||||
#
|
||||
# Console, file, and zulip logger on database changes https://github.com/wekan/wekan/pull/1010
|
||||
# with fix to replace console.log by winston logger https://github.com/wekan/wekan/pull/1033
|
||||
# but there could be bug https://github.com/wekan/wekan/issues/1094
|
||||
#
|
||||
# There is Feature Request: Logging date and time of all activity with summary reports,
|
||||
# and requesting reason for changing card to other column https://github.com/wekan/wekan/issues/1598
|
||||
#---------------------------------------------------------------
|
||||
# ==== NUMBER OF SEARCH RESULTS PER PAGE BY DEFAULT ====
|
||||
#- RESULTS_PER_PAGE=20
|
||||
#---------------------------------------------------------------
|
||||
# ==== AFTER OIDC LOGIN, ADD USERS AUTOMATICALLY TO THIS BOARD ID ====
|
||||
# https://github.com/wekan/wekan/pull/5098
|
||||
#- DEFAULT_BOARD_ID=abcd1234
|
||||
#---------------------------------------------------------------
|
||||
# ==== WEKAN API AND EXPORT BOARD ====
|
||||
# Wekan Export Board works when WITH_API=true.
|
||||
# https://github.com/wekan/wekan/blob/main/docs/API/REST-API.md
|
||||
# https://github.com/wekan/wekan-gogs
|
||||
# If you disable Wekan API with false, Export Board does not work.
|
||||
- WITH_API=true
|
||||
#---------------------------------------------------------------
|
||||
# ==== PASSWORD BRUTE FORCE PROTECTION ====
|
||||
#https://atmospherejs.com/lucasantoniassi/accounts-lockout
|
||||
#Defaults below. Uncomment to change. wekan/server/accounts-lockout.js
|
||||
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3
|
||||
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60
|
||||
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15
|
||||
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3
|
||||
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60
|
||||
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15
|
||||
#---------------------------------------------------------------
|
||||
# ==== ACCOUNT OPTIONS ====
|
||||
# https://docs.meteor.com/api/accounts-multi.html#AccountsCommon-config
|
||||
# Defaults below. Uncomment to change. wekan/server/accounts-common.js
|
||||
# - ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90
|
||||
#---------------------------------------------------------------
|
||||
# ==== RICH TEXT EDITOR IN CARD COMMENTS ====
|
||||
# https://github.com/wekan/wekan/pull/2560
|
||||
- RICHER_CARD_COMMENT_EDITOR=false
|
||||
#---------------------------------------------------------------
|
||||
# ==== CARD LOADING: all (default) or lazy ====
|
||||
# 'all' = load every card of a board into the browser (default).
|
||||
# 'lazy' = each list loads only the cards currently visible
|
||||
# (infinite scroll) plus a live count, so boards with
|
||||
# thousands of cards use less memory/bandwidth.
|
||||
# Also changeable at runtime in Admin Panel / Features.
|
||||
- CARDS_LOADING=all
|
||||
#---------------------------------------------------------------
|
||||
# ==== CARD OPENED, SEND WEBHOOK MESSAGE ====
|
||||
# https://github.com/wekan/wekan/issues/2518
|
||||
- CARD_OPENED_WEBHOOK_ENABLED=false
|
||||
#---------------------------------------------------------------
|
||||
# ==== Allow configuration to validate uploaded attachments ====
|
||||
#-ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM=/usr/local/bin/avscan {file}
|
||||
#-ATTACHMENTS_UPLOAD_MIME_TYPES=image/*,text/*
|
||||
#-ATTACHMENTS_UPLOAD_MAX_SIZE=5000000
|
||||
#---------------------------------------------------------------
|
||||
# ==== Allow configuration to validate uploaded avatars ====
|
||||
#-AVATARS_UPLOAD_EXTERNAL_PROGRAM=/usr/local/bin/avscan {file}
|
||||
#-AVATARS_UPLOAD_MIME_TYPES=image/*
|
||||
#-AVATARS_UPLOAD_MAX_SIZE=500000
|
||||
#---------------------------------------------------------------
|
||||
# ==== Allow to shrink attached/pasted image ====
|
||||
# https://github.com/wekan/wekan/pull/2544
|
||||
#- MAX_IMAGE_PIXEL=1024
|
||||
#- IMAGE_COMPRESS_RATIO=80
|
||||
#---------------------------------------------------------------
|
||||
# ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE =====
|
||||
# Number of days after a notification is read before we remove it.
|
||||
# Default: 2
|
||||
#- NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2
|
||||
#---------------------------------------------------------------
|
||||
# ==== BIGEVENTS DUE ETC NOTIFICATIONS =====
|
||||
# https://github.com/wekan/wekan/pull/2541
|
||||
# Introduced a system env var BIGEVENTS_PATTERN default as "NONE",
|
||||
# so any activityType matches the pattern, system will send out
|
||||
# notifications to all board members no matter they are watching
|
||||
# or tracking the board or not. Owner of the wekan server can
|
||||
# disable the feature by setting this variable to "NONE" or
|
||||
# change the pattern to any valid regex. i.e. '|' delimited
|
||||
# activityType names.
|
||||
# a) Example
|
||||
#- BIGEVENTS_PATTERN=due
|
||||
# b) All
|
||||
#- BIGEVENTS_PATTERN=received|start|due|end
|
||||
# c) Disabled
|
||||
- BIGEVENTS_PATTERN=NONE
|
||||
#---------------------------------------------------------------
|
||||
# ==== NOTIFY ON ASSIGN =====
|
||||
# Notify the user directly when they are added as a card member or
|
||||
# assignee. Set false to disable.
|
||||
#- NOTIFY_ON_ASSIGN=true
|
||||
#---------------------------------------------------------------
|
||||
# ==== EMAIL DUE DATE NOTIFICATION =====
|
||||
# https://github.com/wekan/wekan/pull/2536
|
||||
# System timelines will be showing any user modification for
|
||||
# dueat startat endat receivedat, also notification to
|
||||
# the watchers and if any card is due, about due or past due.
|
||||
#
|
||||
# Notify due days, default is None, 2 days before and on the event day
|
||||
#- NOTIFY_DUE_DAYS_BEFORE_AND_AFTER=2,0
|
||||
#
|
||||
# Notify due at hour of day. Default every morning at 8am. Can be 0-23.
|
||||
# If env variable has parsing error, use default. Notification sent to watchers.
|
||||
#- NOTIFY_DUE_AT_HOUR_OF_DAY=8
|
||||
#-----------------------------------------------------------------
|
||||
# ==== EMAIL NOTIFICATION TIMEOUT, ms =====
|
||||
# Default: 30000 ms = 30s
|
||||
#- EMAIL_NOTIFICATION_TIMEOUT=30000
|
||||
#-----------------------------------------------------------------
|
||||
# ==== CORS =====
|
||||
# CORS: Set Access-Control-Allow-Origin header.
|
||||
#- CORS=*
|
||||
# CORS_ALLOW_HEADERS: Set Access-Control-Allow-Headers header. "Authorization,Content-Type" is required for cross-origin use of the API.
|
||||
#- CORS_ALLOW_HEADERS=Authorization,Content-Type
|
||||
# CORS_EXPOSE_HEADERS: Set Access-Control-Expose-Headers header. This is not needed for typical CORS situations
|
||||
#- CORS_EXPOSE_HEADERS=*
|
||||
#-----------------------------------------------------------------
|
||||
# ==== MATOMO INTEGRATION ====
|
||||
# Optional: Integration with Matomo https://matomo.org that is installed to your server
|
||||
# The address of the server where Matomo is hosted.
|
||||
#- MATOMO_ADDRESS=https://example.com/matomo
|
||||
# The value of the site ID given in Matomo server for Wekan
|
||||
#- MATOMO_SITE_ID=1
|
||||
# The option do not track which enables users to not be tracked by matomo
|
||||
#- MATOMO_DO_NOT_TRACK=true
|
||||
# The option that allows matomo to retrieve the username:
|
||||
#- MATOMO_WITH_USERNAME=true
|
||||
#-----------------------------------------------------------------
|
||||
# ==== BROWSER POLICY AND TRUSTED IFRAME URL ====
|
||||
# Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside.
|
||||
# Setting this to false is not recommended, it also disables all other browser policy protections
|
||||
# and allows all iframing etc. See wekan/server/policy.js
|
||||
- BROWSER_POLICY_ENABLED=true
|
||||
# When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside.
|
||||
#- TRUSTED_URL=https://intra.example.com
|
||||
#-----------------------------------------------------------------
|
||||
# ==== METRICS ALLOWED IP ADDRESSES ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Features/Metrics.md
|
||||
#- METRICS_ALLOWED_IP_ADDRESSES=192.168.0.100,192.168.0.200
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OUTGOING WEBHOOKS ====
|
||||
# What to send to Outgoing Webhook, or leave out. If commented out the default values will be: cardId,listId,oldListId,boardId,comment,user,card,commentId,swimlaneId,customerField,customFieldValue
|
||||
#- WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,board,list,swimlane,commentId
|
||||
#-----------------------------------------------------------------
|
||||
# ==== Debug OIDC OAuth2 etc ====
|
||||
#- DEBUG=true
|
||||
#---------------------------------------------
|
||||
# ==== AUTOLOGIN WITH OIDC/OAUTH2 ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Login/autologin.md
|
||||
#- OIDC_REDIRECTION_ENABLED=true
|
||||
# OIDC RP-initiated logout endpoint (end_session_endpoint). When set, the
|
||||
# "Log Out" menu redirects to this endpoint so the identity provider session
|
||||
# is also ended and the user is returned to Wekan (ROOT_URL) via
|
||||
# post_logout_redirect_uri, instead of landing on the provider home page
|
||||
# (which errors for non-admin users). See https://github.com/wekan/wekan/issues/6158
|
||||
# Keycloak example:
|
||||
#- OAUTH2_LOGOUT_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/logout
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 ORACLE on premise identity manager OIM ====
|
||||
#- ORACLE_OIM_ENABLED=true
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 AZURE ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Login/Azure/Azure.md
|
||||
# 1) Register the application with Azure. Make sure you capture
|
||||
# the application ID as well as generate a secret key.
|
||||
# 2) Configure the environment variables. This differs slightly
|
||||
# by installation type, but make sure you have the following:
|
||||
#- OAUTH2_ENABLED=true
|
||||
# Optional OAuth2 CA Cert, see https://github.com/wekan/wekan/issues/3299
|
||||
#- OAUTH2_CA_CERT=ABCD1234
|
||||
# Use OAuth2 ADFS additional changes. Also needs OAUTH2_ENABLED=true setting.
|
||||
#- OAUTH2_ADFS_ENABLED=false
|
||||
# Azure AD B2C. https://github.com/wekan/wekan/issues/5242
|
||||
#- OAUTH2_B2C_ENABLED=false
|
||||
# SECURITY (GHSA-mp7g-hj5q-gxhq): Link an OIDC login to a pre-existing
|
||||
# Wekan account (password/LDAP) with the same email. OFF by default; when
|
||||
# false an OIDC login whose email already exists is rejected instead of
|
||||
# merged, preventing account takeover via spoofed email claims. Only
|
||||
# enable if you fully trust your OIDC provider's email claims, and even
|
||||
# then the provider must send email_verified=true for the merge to happen.
|
||||
#- OAUTH2_MERGE_EXISTING_USERS=false
|
||||
# When false, OAuth2/OIDC login is refused for users that do not already
|
||||
# have a Wekan account (matched by verified email).
|
||||
#- OAUTH2_AUTO_REGISTRATION=true
|
||||
# Comma/space separated OAuth2/OIDC group names whose members become Wekan
|
||||
# admins. Empty = off.
|
||||
#- OAUTH2_ADMIN_GROUPS=
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
# Application GUID captured during app registration:
|
||||
#- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx
|
||||
# Secret key generated during app registration:
|
||||
#- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
#- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
|
||||
#- OAUTH2_SERVER_URL=https://login.microsoftonline.com/
|
||||
#- OAUTH2_AUTH_ENDPOINT=/oauth2/v2.0/authorize
|
||||
#- OAUTH2_USERINFO_ENDPOINT=https://graph.microsoft.com/oidc/userinfo
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/oauth2/v2.0/token
|
||||
# The claim name you want to map to the unique ID field:
|
||||
#- OAUTH2_ID_MAP=email
|
||||
# The claim name you want to map to the username field:
|
||||
#- OAUTH2_USERNAME_MAP=email
|
||||
# The claim name you want to map to the full name field:
|
||||
#- OAUTH2_FULLNAME_MAP=name
|
||||
# The claim name you want to map to the email field:
|
||||
#- OAUTH2_EMAIL_MAP=email
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 Nextcloud ====
|
||||
# 1) Register the application with Nextcloud: https://your.nextcloud/index.php/settings/admin/security
|
||||
# Make sure you capture the application ID as well as generate a secret key.
|
||||
# Use https://your.wekan/_oauth/oidc for the redirect URI.
|
||||
# 2) Configure the environment variables. This differs slightly
|
||||
# by installation type, but make sure you have the following:
|
||||
#- OAUTH2_ENABLED=true
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
# Application GUID captured during app registration:
|
||||
#- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx
|
||||
# Secret key generated during app registration:
|
||||
#- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
#- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
|
||||
#- OAUTH2_SERVER_URL=https://your-nextcloud.tld
|
||||
#- OAUTH2_AUTH_ENDPOINT=/index.php/apps/oauth2/authorize
|
||||
#- OAUTH2_USERINFO_ENDPOINT=/ocs/v2.php/cloud/user?format=json
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/index.php/apps/oauth2/api/v1/token
|
||||
# The claim name you want to map to the unique ID field:
|
||||
#- OAUTH2_ID_MAP=id
|
||||
# The claim name you want to map to the username field:
|
||||
#- OAUTH2_USERNAME_MAP=id
|
||||
# The claim name you want to map to the full name field:
|
||||
#- OAUTH2_FULLNAME_MAP=display-name
|
||||
# The claim name you want to map to the email field:
|
||||
#- OAUTH2_EMAIL_MAP=email
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 KEYCLOAK ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Login/Keycloak/Keycloak.md <== MAPPING INFO, REQUIRED
|
||||
#- OAUTH2_ENABLED=true
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
#- OAUTH2_CLIENT_ID=<Keycloak create Client ID>
|
||||
#- OAUTH2_SERVER_URL=<Keycloak server url - https://keycloak.example.com>
|
||||
#- OAUTH2_AUTH_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/auth
|
||||
#- OAUTH2_USERINFO_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/userinfo
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/token
|
||||
#- OAUTH2_SECRET=<keycloak client secret>
|
||||
#- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
|
||||
#- OAUTH2_ID_MAP=sub
|
||||
#- OAUTH2_USERNAME_MAP=preferred_username
|
||||
#- OAUTH2_EMAIL_MAP=email
|
||||
#- OAUTH2_FULLNAME_MAP=name
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 DOORKEEPER ====
|
||||
# https://github.com/wekan/wekan/issues/1874
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Login/OAuth2.md2
|
||||
# Enable the OAuth2 connection
|
||||
#- OAUTH2_ENABLED=true
|
||||
# OAuth2 docs: https://github.com/wekan/wekan/blob/main/docs/Login/OAuth2.md
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
# OAuth2 Client ID.
|
||||
#- OAUTH2_CLIENT_ID=abcde12345
|
||||
# OAuth2 Secret.
|
||||
#- OAUTH2_SECRET=54321abcde
|
||||
#- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
|
||||
# OAuth2 Server URL.
|
||||
#- OAUTH2_SERVER_URL=https://chat.example.com
|
||||
# OAuth2 Authorization Endpoint.
|
||||
#- OAUTH2_AUTH_ENDPOINT=/oauth/authorize
|
||||
# OAuth2 Userinfo Endpoint.
|
||||
#- OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
|
||||
# OAuth2 Token Endpoint.
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/oauth/token
|
||||
# OAUTH2 ID Token Whitelist Fields.
|
||||
#- OAUTH2_ID_TOKEN_WHITELIST_FIELDS=""
|
||||
# OAUTH2 Request Permissions.
|
||||
#- OAUTH2_REQUEST_PERMISSIONS=openid profile email
|
||||
# OAuth2 ID Mapping
|
||||
#- OAUTH2_ID_MAP=
|
||||
# OAuth2 Username Mapping
|
||||
#- OAUTH2_USERNAME_MAP=
|
||||
# OAuth2 Fullname Mapping
|
||||
#- OAUTH2_FULLNAME_MAP=
|
||||
# OAuth2 Email Mapping
|
||||
#- OAUTH2_EMAIL_MAP=
|
||||
#-----------------------------------------------------------------
|
||||
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Login/LDAP.md
|
||||
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Snap/Supported-settings-keys.md
|
||||
# Most settings work both on Snap and Docker below.
|
||||
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
|
||||
#
|
||||
# The default authentication method used if a user does not exist to create and authenticate. Can be set as ldap.
|
||||
# (this is set properly in the Admin Panel, changing this item does not remove Password login option)
|
||||
#- DEFAULT_AUTHENTICATION_METHOD=ldap
|
||||
#
|
||||
# Enable or not the connection by the LDAP
|
||||
#- LDAP_ENABLE=true
|
||||
#
|
||||
# The port of the LDAP server
|
||||
#- LDAP_PORT=389
|
||||
#
|
||||
# The host server for the LDAP server
|
||||
#- LDAP_HOST=localhost
|
||||
#
|
||||
#-----------------------------------------------------------------
|
||||
# ==== LDAP AD Simple Auth ====
|
||||
#
|
||||
# Set to true, if you want to connect with Active Directory by Simple Authentication.
|
||||
# When using AD Simple Auth, LDAP_BASEDN is not needed.
|
||||
#
|
||||
# Example:
|
||||
#- LDAP_AD_SIMPLE_AUTH=true
|
||||
#
|
||||
# === LDAP User Authentication ===
|
||||
#
|
||||
# a) Option to login to the LDAP server with the user's own username and password, instead of
|
||||
# an administrator key. Default: false (use administrator key).
|
||||
#
|
||||
# b) When using AD Simple Auth, set to true, when login user is used for binding,
|
||||
# and LDAP_BASEDN is not needed.
|
||||
#
|
||||
# Example:
|
||||
#- LDAP_USER_AUTHENTICATION=true
|
||||
#
|
||||
# Which field is used to find the user for the user authentication. Default: uid.
|
||||
#- LDAP_USER_AUTHENTICATION_FIELD=uid
|
||||
#
|
||||
# === LDAP Default Domain ===
|
||||
#
|
||||
# a) In case AD SimpleAuth is configured, the default domain is appended to the given
|
||||
# loginname for creating the correct username for the bind request to AD.
|
||||
#
|
||||
# b) The default domain of the ldap it is used to create email if the field is not map
|
||||
# correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
|
||||
#
|
||||
# Example :
|
||||
#- LDAP_DEFAULT_DOMAIN=mydomain.com
|
||||
#
|
||||
#-----------------------------------------------------------------
|
||||
# ==== LDAP BASEDN Auth ====
|
||||
#
|
||||
# The base DN for the LDAP Tree
|
||||
#- LDAP_BASEDN=ou=user,dc=example,dc=org
|
||||
#
|
||||
#-----------------------------------------------------------------
|
||||
# Fallback on the default authentication method
|
||||
#- LDAP_LOGIN_FALLBACK=false
|
||||
#
|
||||
# Reconnect to the server if the connection is lost
|
||||
#- LDAP_RECONNECT=true
|
||||
#
|
||||
# Overall timeout, in milliseconds
|
||||
#- LDAP_TIMEOUT=10000
|
||||
#
|
||||
# Specifies the timeout for idle LDAP connections in milliseconds
|
||||
#- LDAP_IDLE_TIMEOUT=10000
|
||||
#
|
||||
# Connection timeout, in milliseconds
|
||||
#- LDAP_CONNECT_TIMEOUT=10000
|
||||
#
|
||||
# If the LDAP needs a user account to search
|
||||
#- LDAP_AUTHENTIFICATION=true
|
||||
#
|
||||
# The search user DN - You need quotes when you have spaces in parameters
|
||||
# 2 examples:
|
||||
#- LDAP_AUTHENTIFICATION_USERDN=CN=ldap admin,CN=users,DC=domainmatter,DC=lan
|
||||
#- LDAP_AUTHENTIFICATION_USERDN=CN=wekan_adm,OU=serviceaccounts,OU=admin,OU=prod,DC=mydomain,DC=com
|
||||
#
|
||||
# The password for the search user
|
||||
#- LDAP_AUTHENTIFICATION_PASSWORD=pwd
|
||||
#- LDAP_AUTHENTIFICATION_PASSWORD_FILE=/run/secrets/ldap_auth_password
|
||||
#
|
||||
# Enable logs for the module
|
||||
#- LDAP_LOG_ENABLED=true
|
||||
#
|
||||
# If the sync of the users should be done in the background
|
||||
#- LDAP_BACKGROUND_SYNC=false
|
||||
#
|
||||
# LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds
|
||||
# The format must be as specified in:
|
||||
# https://bunkat.github.io/later/parsers.html#text
|
||||
#- LDAP_BACKGROUND_SYNC_INTERVAL=every 1 hours
|
||||
# At which interval does the background task sync in milliseconds.
|
||||
# Leave this unset, so it uses default, and does not crash.
|
||||
# https://github.com/wekan/wekan/issues/2354#issuecomment-515305722
|
||||
- LDAP_BACKGROUND_SYNC_INTERVAL=''
|
||||
#
|
||||
#- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
|
||||
#
|
||||
#- LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false
|
||||
#
|
||||
# LDAP_BACKGROUND_SYNC_DISABLE_NONEXISTANT_USERS : When true, the LDAP
|
||||
# background sync disables users no longer present in LDAP (and re-enables
|
||||
# them when they reappear).
|
||||
#- LDAP_BACKGROUND_SYNC_DISABLE_NONEXISTANT_USERS=false
|
||||
#
|
||||
# If using LDAPS: LDAP_ENCRYPTION=ssl
|
||||
#- LDAP_ENCRYPTION=false
|
||||
#
|
||||
# The certification for the LDAPS server. Certificate needs to be included in this docker compose.yml file.
|
||||
#- LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+G2FIdAgIC...-----END CERTIFICATE-----
|
||||
#
|
||||
# Reject Unauthorized Certificate
|
||||
#- LDAP_REJECT_UNAUTHORIZED=false
|
||||
#
|
||||
# Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
#- LDAP_USER_SEARCH_FILTER=
|
||||
#
|
||||
# base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
|
||||
#- LDAP_USER_SEARCH_SCOPE=one
|
||||
#
|
||||
# Which field is used to find the user, like uid / sAMAccountName
|
||||
#- LDAP_USER_SEARCH_FIELD=sAMAccountName
|
||||
#
|
||||
# Used for pagination (0=unlimited)
|
||||
#- LDAP_SEARCH_PAGE_SIZE=0
|
||||
#
|
||||
# The limit number of entries (0=unlimited)
|
||||
#- LDAP_SEARCH_SIZE_LIMIT=0
|
||||
#
|
||||
# Enable the login restriction group filter. Note the authenticated ldap user must be able to query all relevant group data with own login data from ldap.
|
||||
# When true, only members of LDAP_GROUP_FILTER_GROUP_NAME are allowed to log in.
|
||||
# NOTE: This flag ONLY controls the login restriction. Admin status sync
|
||||
# (LDAP_SYNC_ADMIN_STATUS / LDAP_SYNC_ADMIN_GROUPS) and group->role sync
|
||||
# (LDAP_SYNC_GROUP_ROLES) query LDAP groups independently and do NOT require
|
||||
# this flag to be true. The group filter metadata below
|
||||
# (LDAP_GROUP_FILTER_OBJECTCLASS, LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE,
|
||||
# LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT, LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE)
|
||||
# must still be configured for any group search to work.
|
||||
#- LDAP_GROUP_FILTER_ENABLE=false
|
||||
#
|
||||
# The object class for filtering. Example: group
|
||||
#- LDAP_GROUP_FILTER_OBJECTCLASS=
|
||||
#
|
||||
# The attribute of a group identifying it. Example: cn
|
||||
#- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
|
||||
#
|
||||
# The attribute inside a group object listing its members. Example: member
|
||||
#- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
|
||||
#
|
||||
# The format of the value of LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE. Example: 'dn' if the users dn is saved as value into the attribute.
|
||||
#- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
|
||||
#
|
||||
# The group name (id) that matches all users.
|
||||
#- LDAP_GROUP_FILTER_GROUP_NAME=
|
||||
#
|
||||
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier). Example: guid
|
||||
#- LDAP_UNIQUE_IDENTIFIER_FIELD=
|
||||
#
|
||||
# LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8
|
||||
#- LDAP_UTF8_NAMES_SLUGIFY=true
|
||||
#
|
||||
# LDAP_USERNAME_FIELD : Which field contains the ldap username. username / sAMAccountName
|
||||
#- LDAP_USERNAME_FIELD=sAMAccountName
|
||||
#
|
||||
# LDAP_FULLNAME_FIELD : Which field contains the ldap fullname. fullname / sAMAccountName
|
||||
#- LDAP_FULLNAME_FIELD=fullname
|
||||
#
|
||||
#- LDAP_MERGE_EXISTING_USERS=false
|
||||
#
|
||||
# Allow existing account matching by e-mail address when username does not match
|
||||
#- LDAP_EMAIL_MATCH_ENABLE=true
|
||||
#
|
||||
# LDAP_EMAIL_MATCH_REQUIRE : require existing account matching by e-mail address when username does match
|
||||
#- LDAP_EMAIL_MATCH_REQUIRE=true
|
||||
#
|
||||
# LDAP_EMAIL_MATCH_VERIFIED : require existing account email address to be verified for matching
|
||||
#- LDAP_EMAIL_MATCH_VERIFIED=true
|
||||
#
|
||||
# LDAP_EMAIL_FIELD : which field contains the LDAP e-mail address
|
||||
#- LDAP_EMAIL_FIELD=mail
|
||||
#-----------------------------------------------------------------
|
||||
#- LDAP_SYNC_USER_DATA=false
|
||||
#
|
||||
#- LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
|
||||
#
|
||||
#- LDAP_SYNC_GROUP_ROLES=
|
||||
#
|
||||
# The default domain of the ldap it is used to create email if the field is not map correctly
|
||||
# with the LDAP_SYNC_USER_DATA_FIELDMAP is defined in setting LDAP_DEFAULT_DOMAIN above.
|
||||
#
|
||||
# Enable/Disable syncing of admin status based on ldap groups:
|
||||
# NOTE: Admin status sync and group->role sync (LDAP_SYNC_GROUP_ROLES) query
|
||||
# LDAP groups on their own. They no longer require LDAP_GROUP_FILTER_ENABLE=true,
|
||||
# which only controls the login restriction filter. The group filter metadata
|
||||
# (LDAP_GROUP_FILTER_OBJECTCLASS, LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE,
|
||||
# LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT, LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE)
|
||||
# must still be configured for the group search to work.
|
||||
#- LDAP_SYNC_ADMIN_STATUS=true
|
||||
#
|
||||
# Comma separated list of admin group names to sync.
|
||||
#- LDAP_SYNC_ADMIN_GROUPS=group1,group2
|
||||
#
|
||||
# When true, sync a user's LDAP groups as Wekan Organizations.
|
||||
#- LDAP_SYNC_ORGANIZATIONS=false
|
||||
#
|
||||
# Comma separated allowlist of LDAP group names to sync as Organizations. Empty = all of the user's groups.
|
||||
#- LDAP_SYNC_ORGANIZATIONS_GROUPS=
|
||||
#
|
||||
# When true, sync a user's LDAP groups as Wekan Teams.
|
||||
#- LDAP_SYNC_TEAMS=false
|
||||
#
|
||||
# Comma separated allowlist of LDAP group names to sync as Teams. Empty = all of the user's groups.
|
||||
#- LDAP_SYNC_TEAMS_GROUPS=
|
||||
#---------------------------------------------------------------------
|
||||
# Login to LDAP automatically with HTTP header.
|
||||
# In below example for siteminder, at right side of = is header name.
|
||||
#- HEADER_LOGIN_ID=HEADERUID
|
||||
#- HEADER_LOGIN_FIRSTNAME=HEADERFIRSTNAME
|
||||
#- HEADER_LOGIN_LASTNAME=HEADERLASTNAME
|
||||
#- HEADER_LOGIN_EMAIL=HEADEREMAILADDRESS
|
||||
# SECURITY (GHSA-jggc-qvfc-jr6x): comma-separated allowlist of source IPs
|
||||
# permitted to use header login. The source IP is the real TCP peer of the
|
||||
# connection (your reverse proxy), NOT the spoofable X-Forwarded-For header.
|
||||
# REQUIRED when header login is enabled: if empty/unset, header login fails
|
||||
# CLOSED and authenticates no one.
|
||||
#- HEADER_LOGIN_TRUSTED_IPS=127.0.0.1,10.0.0.2
|
||||
# Optional: if WeKan sits behind MULTIPLE proxy hops, list the intermediate
|
||||
# proxy IPs here. X-Forwarded-For is then honored ONLY when the immediate
|
||||
# TCP peer is one of these trusted proxies, and the right-most hop that is
|
||||
# not itself a trusted proxy (the real client) is matched against
|
||||
# HEADER_LOGIN_TRUSTED_IPS above.
|
||||
#- HEADER_LOGIN_TRUSTED_PROXIES=10.0.0.1,10.0.0.2
|
||||
#---------------------------------------------------------------------
|
||||
# ==== LOGOUT TIMER, probably does not work yet ====
|
||||
# LOGOUT_WITH_TIMER : Enables or not the option logout with timer
|
||||
# example : LOGOUT_WITH_TIMER=true
|
||||
#- LOGOUT_WITH_TIMER=
|
||||
#
|
||||
# LOGOUT_IN : The number of days
|
||||
# example : LOGOUT_IN=1
|
||||
#- LOGOUT_IN=
|
||||
#
|
||||
# LOGOUT_ON_HOURS : The number of hours
|
||||
# example : LOGOUT_ON_HOURS=9
|
||||
#- LOGOUT_ON_HOURS=
|
||||
#
|
||||
# LOGOUT_ON_MINUTES : The number of minutes
|
||||
# example : LOGOUT_ON_MINUTES=55
|
||||
#- LOGOUT_ON_MINUTES=
|
||||
#-------------------------------------------------------------------
|
||||
# Hide password login form
|
||||
# - PASSWORD_LOGIN_ENABLED=true
|
||||
#-------------------------------------------------------------------
|
||||
#- CAS_ENABLED=true
|
||||
#- CAS_BASE_URL=https://cas.example.com/cas
|
||||
#- CAS_LOGIN_URL=https://cas.example.com/login
|
||||
#- CAS_VALIDATE_URL=https://cas.example.com/cas/p3/serviceValidate
|
||||
#---------------------------------------------------------------------
|
||||
#- SAML_ENABLED=true
|
||||
#- SAML_PROVIDER=
|
||||
#- SAML_ENTRYPOINT=
|
||||
#- SAML_ISSUER=
|
||||
#- SAML_CERT=
|
||||
#- SAML_IDPSLO_REDIRECTURL=
|
||||
#- SAML_PRIVATE_KEYFILE=
|
||||
#- SAML_PUBLIC_CERTFILE=
|
||||
#- SAML_IDENTIFIER_FORMAT=
|
||||
#- SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE=
|
||||
#- SAML_ATTRIBUTES=
|
||||
#---------------------------------------------------------------------
|
||||
# Wait spinner to use
|
||||
# - WAIT_SPINNER=Bounce
|
||||
#---------------------------------------------------------------------
|
||||
# Wait for MongoDB to be healthy (replica-set primary elected) before
|
||||
# starting WeKan, so index creation does not crash with "Topology is closed".
|
||||
# If you use FerretDB instead, comment out the wekandb block below and
|
||||
# uncomment the wekan-ferretdb block.
|
||||
depends_on:
|
||||
ferretdb:
|
||||
condition: service_started
|
||||
# wekan-migrate:
|
||||
# condition: service_completed_successfully
|
||||
# wekan-ferretdb:
|
||||
# condition: service_healthy
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- wekan-files:/data:rw
|
||||
|
||||
volumes:
|
||||
wekan-files:
|
||||
driver: local
|
||||
ferretdb-data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
wekan-tier:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,781 @@
|
||||
# WeKan with FerretDB 2 + PostgreSQL (DocumentDB extension)
|
||||
#
|
||||
# Use this compose file instead of docker-compose.yml on platforms where
|
||||
# MongoDB Community Edition is not available, or when you prefer PostgreSQL
|
||||
# as the backing store. For a lighter, single-container, no-PostgreSQL option,
|
||||
# see docker-compose-ferretdb-v1-sqlite.yml (FerretDB v1 with the embedded
|
||||
# SQLite backend, using the wekan/FerretDB release binaries).
|
||||
#
|
||||
# Supported on all platforms where Node.js 22 runs:
|
||||
# linux/amd64, linux/arm64, linux/arm/v7, linux/ppc64le, linux/s390x
|
||||
#
|
||||
# Based on: docs/Databases/FerretDB2-PostgreSQL.md
|
||||
#
|
||||
# Setup:
|
||||
# docker compose -f docker-compose-ferretdb-v2-postgresql.yml up -d
|
||||
#
|
||||
# To stop:
|
||||
# docker compose -f docker-compose-ferretdb-v2-postgresql.yml stop
|
||||
|
||||
version: '3.8'
|
||||
services:
|
||||
|
||||
# PostgreSQL 17 with the DocumentDB extension (FerretDB 2 backing store).
|
||||
postgres-documentdb:
|
||||
image: ghcr.io/ferretdb/postgres-documentdb:17
|
||||
container_name: wekan-postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: ferretdb
|
||||
POSTGRES_PASSWORD: ${FERRETDB_POSTGRES_PASSWORD:-ferretdb_secret}
|
||||
POSTGRES_DB: ferretdb
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- wekan-tier
|
||||
|
||||
# FerretDB 2 - MongoDB-compatible protocol layer over PostgreSQL (on :27017).
|
||||
ferretdb:
|
||||
image: ghcr.io/ferretdb/ferretdb:2
|
||||
container_name: wekan-ferretdb
|
||||
restart: always
|
||||
environment:
|
||||
FERRETDB_POSTGRESQL_URL: postgres://ferretdb:${FERRETDB_POSTGRES_PASSWORD:-ferretdb_secret}@postgres-documentdb/ferretdb
|
||||
depends_on:
|
||||
- postgres-documentdb
|
||||
networks:
|
||||
- wekan-tier
|
||||
# Expose only on localhost by default; remove to keep it internal to the network.
|
||||
ports:
|
||||
- 127.0.0.1:27017:27017
|
||||
|
||||
wekan:
|
||||
#-------------------------------------------------------------------------------------
|
||||
# ==== WEKAN FROM GITHUB/QUAY/DOCKER HUB ====
|
||||
# All of GitHub, Quay and Docker Hub have latest, but because
|
||||
# latest tag changes when is newest release,
|
||||
# when upgrading would be better to use version tag.
|
||||
# a) Using specific version tag is better:
|
||||
# image: ghcr.io/wekan/wekan:v6.89
|
||||
# image: quay.io/wekan/wekan:v6.89
|
||||
# image: wekanteam/wekan:v6.89
|
||||
# b) GitHub Container registry.
|
||||
# b1) Latest release tag:
|
||||
image: ghcr.io/wekan/wekan:latest
|
||||
# b2) Newest git commit automatic build:
|
||||
#image: ghcr.io/wekan/wekan:main
|
||||
# c) Quay:
|
||||
#image: quay.io/wekan/wekan:latest
|
||||
# d) Docker Hub:
|
||||
#image: wekanteam/wekan:latest
|
||||
#-------------------------------------------------------------------------------------
|
||||
container_name: wekan-app
|
||||
# On CentOS 7 there is seccomp issue with glibc 6,
|
||||
# so CentOS 7 users shoud use these security_opt seccomp:unconfined
|
||||
# settings to get WeKan working. See:
|
||||
# - https://github.com/wekan/wekan/issues/4585
|
||||
# - https://github.com/wekan/wekan/issues/4587
|
||||
#security_opt:
|
||||
# - seccomp:unconfined
|
||||
restart: always
|
||||
networks:
|
||||
- wekan-tier
|
||||
#-------------------------------------------------------------------------------------
|
||||
# ==== BUILD wekan-app DOCKER CONTAINER FROM SOURCE, if you uncomment these ====
|
||||
# ==== and use commands: docker compose up -d --build
|
||||
#build:
|
||||
# context: .
|
||||
# dockerfile: Dockerfile
|
||||
#-------------------------------------------------------------------------------------
|
||||
ports:
|
||||
# Docker outsideport:insideport. Do not add anything extra here.
|
||||
# For example, if you want to have wekan on port 3001,
|
||||
# use 3001:8080 . Do not add any extra address etc here, that way it does not work.
|
||||
# remove port mapping if you use nginx reverse proxy, port 8080 is already exposed to wekan-tier network
|
||||
- 80:8080
|
||||
environment:
|
||||
#-------------------- USING MONGODB CHANGE STREAMS WITH REPLICA SETS AT CURRENT DATABASE --------------------
|
||||
# If you would not like to use Change Streams and replica set for improving speed, change to use polling:
|
||||
#- METEOR_REACTIVITY_ORDER=polling
|
||||
# https://forums.meteor.com/t/meteor-3-5-beta-change-streams-performance-improvements/64461#change-streams-setup-3
|
||||
# https://github.com/meteor/meteor/blob/release-3.5/v3-docs/docs/performance/change-streams-observer-driver.md#choosing-the-reactivity-driver-order
|
||||
# Keep oplog/polling fallback available for deployments that cannot use Change Streams:
|
||||
# https://github.com/wekan/wekan/issues/6307#issuecomment-4299349231
|
||||
# FerretDB has no MongoDB change streams / replica-set oplog, so use polling:
|
||||
- METEOR_REACTIVITY_ORDER=polling
|
||||
- DDP_TRANSPORT=uws
|
||||
#- DDP_TRANSPORT=sockjs
|
||||
#-----------------------------------------------------------------
|
||||
# ==== WRITEABLE PATH FOR FILE UPLOADS ====
|
||||
- WRITABLE_PATH=/data
|
||||
#-----------------------------------------------------------------
|
||||
# ==== AWS S3 FOR FILES ====
|
||||
# Any region. For example:
|
||||
# us-standard,us-west-1,us-west-2,
|
||||
# eu-west-1,eu-central-1,
|
||||
# ap-southeast-1,ap-northeast-1,sa-east-1
|
||||
#
|
||||
#- S3='{"s3":{"key": "xxx", "secret": "xxx", "bucket": "xxx", "region": "xxx"}}'
|
||||
#- S3_SECRET_FILE=/run/secrets/s3_secret
|
||||
#-----------------------------------------------------------------
|
||||
# ==== MONGO_URL — points to FerretDB (MongoDB-compatible) ====
|
||||
#- MONGO_URL=mongodb://wekan-ferretdb:27017/wekan
|
||||
- MONGO_URL=mongodb://ferretdb:${FERRETDB_POSTGRES_PASSWORD:-ferretdb_secret}@ferretdb:27017/wekan?authMechanism=PLAIN
|
||||
#- MONGO_PASSWORD_FILE=/run/secrets/mongo_password
|
||||
#---------------------------------------------------------------
|
||||
# ==== ROOT_URL SETTING ====
|
||||
# Change ROOT_URL to your real Wekan URL, for example:
|
||||
# If you have Caddy/Nginx/Apache providing SSL
|
||||
# - https://example.com
|
||||
# - https://boards.example.com
|
||||
# This can be problematic with avatars https://github.com/wekan/wekan/issues/1776
|
||||
# - https://example.com/wekan
|
||||
# If without https, can be only wekan node, no need for Caddy/Nginx/Apache if you don't need them
|
||||
# - http://example.com
|
||||
# - http://boards.example.com
|
||||
# - http://192.168.1.100 <=== using at local LAN
|
||||
- ROOT_URL=http://localhost # <=== using only at same laptop/desktop where Wekan is installed
|
||||
#---------------------------------------------------------------
|
||||
# ==== EMAIL SETTINGS ====
|
||||
# Email settings are only at MAIL_URL and MAIL_FROM.
|
||||
# Admin Panel has test button, but it's not used for settings.
|
||||
# see https://github.com/wekan/wekan/blob/main/docs/Email/Troubleshooting-Mail.md
|
||||
# For SSL in email, change smtp:// to smtps://
|
||||
# NOTE: Special characters need to be url-encoded in MAIL_URL.
|
||||
# You can encode those characters for example at: https://www.urlencoder.org
|
||||
#- MAIL_URL=smtp://user:pass@mailserver.example.com:25/
|
||||
- MAIL_URL=smtp://<mail_url>:25/?ignoreTLS=true&tls={rejectUnauthorized:false}
|
||||
- MAIL_FROM=Wekan Notifications <noreply.wekan@mydomain.com>
|
||||
# Currently MAIL_SERVICE is not in use.
|
||||
#- MAIL_SERVICE=Outlook365
|
||||
#- MAIL_SERVICE_USER=firstname.lastname@hotmail.com
|
||||
#- MAIL_SERVICE_PASSWORD=SecretPassword
|
||||
#- MAIL_SERVICE_PASSWORD_FILE=/run/secrets/mail_service_password
|
||||
#---------------------------------------------------------------
|
||||
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
|
||||
# Add more Node heap, this is done by default at Dockerfile:
|
||||
# - NODE_OPTIONS="--max_old_space_size=4096"
|
||||
# Add more stack, this is done at Dockerfile:
|
||||
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
|
||||
#---------------------------------------------------------------
|
||||
# ==== OPTIONAL: MONGO OPLOG SETTINGS =====
|
||||
# https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-378343587
|
||||
# HIGHLY RECOMMENDED for pub/sub performance!
|
||||
# MongoDB oplog is used by Meteor for real-time data synchronization.
|
||||
# Without oplog, Meteor falls back to polling which increases:
|
||||
# - CPU usage by 3-5x
|
||||
# - Network traffic significantly
|
||||
# - Latency from 50ms to 2000ms
|
||||
# Must configure MongoDB replica set first
|
||||
# See: https://blog.meteor.com/tuning-meteor-mongo-livedata-for-scalability-13fe9deb8908
|
||||
# FerretDB emulates OpLog via --replication-set-name=rs0.
|
||||
# HIGHLY RECOMMENDED — enables real-time DDP updates (sub-100ms latency).
|
||||
#- MONGO_OPLOG_URL=mongodb://wekan-ferretdb:27017/local?replicaSet=rs0
|
||||
# MONGO_OPLOG_URL intentionally unset: FerretDB v1 has no replica-set oplog (WeKan uses polling).
|
||||
#---------------------------------------------------------------
|
||||
# ==== OPTIONAL: KADIRA PERFORMANCE MONITORING FOR METEOR ====
|
||||
# https://github.com/edemaine/kadira-compose
|
||||
# https://github.com/meteor/meteor-apm-agent
|
||||
# https://blog.meteor.com/kadira-apm-is-now-open-source-490469ffc85f
|
||||
#- APM_OPTIONS_ENDPOINT=http://<kadira-ip>:11011
|
||||
#- APM_APP_ID=
|
||||
#- APM_APP_SECRET=
|
||||
#---------------------------------------------------------------
|
||||
# ==== OPTIONAL: LOGS AND STATS ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Features/Logs.md
|
||||
#
|
||||
# Daily export of Wekan changes as JSON to Logstash and ElasticSearch / Kibana (ELK)
|
||||
# https://github.com/wekan/wekan-logstash
|
||||
#
|
||||
# Statistics Python script for Wekan Dashboard
|
||||
# https://github.com/wekan/wekan-stats
|
||||
#
|
||||
# Console, file, and zulip logger on database changes https://github.com/wekan/wekan/pull/1010
|
||||
# with fix to replace console.log by winston logger https://github.com/wekan/wekan/pull/1033
|
||||
# but there could be bug https://github.com/wekan/wekan/issues/1094
|
||||
#
|
||||
# There is Feature Request: Logging date and time of all activity with summary reports,
|
||||
# and requesting reason for changing card to other column https://github.com/wekan/wekan/issues/1598
|
||||
#---------------------------------------------------------------
|
||||
# ==== NUMBER OF SEARCH RESULTS PER PAGE BY DEFAULT ====
|
||||
#- RESULTS_PER_PAGE=20
|
||||
#---------------------------------------------------------------
|
||||
# ==== AFTER OIDC LOGIN, ADD USERS AUTOMATICALLY TO THIS BOARD ID ====
|
||||
# https://github.com/wekan/wekan/pull/5098
|
||||
#- DEFAULT_BOARD_ID=abcd1234
|
||||
#---------------------------------------------------------------
|
||||
# ==== WEKAN API AND EXPORT BOARD ====
|
||||
# Wekan Export Board works when WITH_API=true.
|
||||
# https://github.com/wekan/wekan/blob/main/docs/API/REST-API.md
|
||||
# https://github.com/wekan/wekan-gogs
|
||||
# If you disable Wekan API with false, Export Board does not work.
|
||||
- WITH_API=true
|
||||
#---------------------------------------------------------------
|
||||
# ==== PASSWORD BRUTE FORCE PROTECTION ====
|
||||
#https://atmospherejs.com/lucasantoniassi/accounts-lockout
|
||||
#Defaults below. Uncomment to change. wekan/server/accounts-lockout.js
|
||||
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3
|
||||
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60
|
||||
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15
|
||||
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3
|
||||
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60
|
||||
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15
|
||||
#---------------------------------------------------------------
|
||||
# ==== ACCOUNT OPTIONS ====
|
||||
# https://docs.meteor.com/api/accounts-multi.html#AccountsCommon-config
|
||||
# Defaults below. Uncomment to change. wekan/server/accounts-common.js
|
||||
# - ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90
|
||||
#---------------------------------------------------------------
|
||||
# ==== RICH TEXT EDITOR IN CARD COMMENTS ====
|
||||
# https://github.com/wekan/wekan/pull/2560
|
||||
- RICHER_CARD_COMMENT_EDITOR=false
|
||||
#---------------------------------------------------------------
|
||||
# ==== CARD LOADING: all (default) or lazy ====
|
||||
# 'all' = load every card of a board into the browser (default).
|
||||
# 'lazy' = each list loads only the cards currently visible
|
||||
# (infinite scroll) plus a live count, so boards with
|
||||
# thousands of cards use less memory/bandwidth.
|
||||
# Also changeable at runtime in Admin Panel / Features.
|
||||
- CARDS_LOADING=all
|
||||
#---------------------------------------------------------------
|
||||
# ==== CARD OPENED, SEND WEBHOOK MESSAGE ====
|
||||
# https://github.com/wekan/wekan/issues/2518
|
||||
- CARD_OPENED_WEBHOOK_ENABLED=false
|
||||
#---------------------------------------------------------------
|
||||
# ==== Allow configuration to validate uploaded attachments ====
|
||||
#-ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM=/usr/local/bin/avscan {file}
|
||||
#-ATTACHMENTS_UPLOAD_MIME_TYPES=image/*,text/*
|
||||
#-ATTACHMENTS_UPLOAD_MAX_SIZE=5000000
|
||||
#---------------------------------------------------------------
|
||||
# ==== Allow configuration to validate uploaded avatars ====
|
||||
#-AVATARS_UPLOAD_EXTERNAL_PROGRAM=/usr/local/bin/avscan {file}
|
||||
#-AVATARS_UPLOAD_MIME_TYPES=image/*
|
||||
#-AVATARS_UPLOAD_MAX_SIZE=500000
|
||||
#---------------------------------------------------------------
|
||||
# ==== Allow to shrink attached/pasted image ====
|
||||
# https://github.com/wekan/wekan/pull/2544
|
||||
#- MAX_IMAGE_PIXEL=1024
|
||||
#- IMAGE_COMPRESS_RATIO=80
|
||||
#---------------------------------------------------------------
|
||||
# ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE =====
|
||||
# Number of days after a notification is read before we remove it.
|
||||
# Default: 2
|
||||
#- NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2
|
||||
#---------------------------------------------------------------
|
||||
# ==== BIGEVENTS DUE ETC NOTIFICATIONS =====
|
||||
# https://github.com/wekan/wekan/pull/2541
|
||||
# Introduced a system env var BIGEVENTS_PATTERN default as "NONE",
|
||||
# so any activityType matches the pattern, system will send out
|
||||
# notifications to all board members no matter they are watching
|
||||
# or tracking the board or not. Owner of the wekan server can
|
||||
# disable the feature by setting this variable to "NONE" or
|
||||
# change the pattern to any valid regex. i.e. '|' delimited
|
||||
# activityType names.
|
||||
# a) Example
|
||||
#- BIGEVENTS_PATTERN=due
|
||||
# b) All
|
||||
#- BIGEVENTS_PATTERN=received|start|due|end
|
||||
# c) Disabled
|
||||
- BIGEVENTS_PATTERN=NONE
|
||||
#---------------------------------------------------------------
|
||||
# ==== NOTIFY ON ASSIGN =====
|
||||
# Notify the user directly when they are added as a card member or
|
||||
# assignee. Set false to disable.
|
||||
#- NOTIFY_ON_ASSIGN=true
|
||||
#---------------------------------------------------------------
|
||||
# ==== EMAIL DUE DATE NOTIFICATION =====
|
||||
# https://github.com/wekan/wekan/pull/2536
|
||||
# System timelines will be showing any user modification for
|
||||
# dueat startat endat receivedat, also notification to
|
||||
# the watchers and if any card is due, about due or past due.
|
||||
#
|
||||
# Notify due days, default is None, 2 days before and on the event day
|
||||
#- NOTIFY_DUE_DAYS_BEFORE_AND_AFTER=2,0
|
||||
#
|
||||
# Notify due at hour of day. Default every morning at 8am. Can be 0-23.
|
||||
# If env variable has parsing error, use default. Notification sent to watchers.
|
||||
#- NOTIFY_DUE_AT_HOUR_OF_DAY=8
|
||||
#-----------------------------------------------------------------
|
||||
# ==== EMAIL NOTIFICATION TIMEOUT, ms =====
|
||||
# Default: 30000 ms = 30s
|
||||
#- EMAIL_NOTIFICATION_TIMEOUT=30000
|
||||
#-----------------------------------------------------------------
|
||||
# ==== CORS =====
|
||||
# CORS: Set Access-Control-Allow-Origin header.
|
||||
#- CORS=*
|
||||
# CORS_ALLOW_HEADERS: Set Access-Control-Allow-Headers header. "Authorization,Content-Type" is required for cross-origin use of the API.
|
||||
#- CORS_ALLOW_HEADERS=Authorization,Content-Type
|
||||
# CORS_EXPOSE_HEADERS: Set Access-Control-Expose-Headers header. This is not needed for typical CORS situations
|
||||
#- CORS_EXPOSE_HEADERS=*
|
||||
#-----------------------------------------------------------------
|
||||
# ==== MATOMO INTEGRATION ====
|
||||
# Optional: Integration with Matomo https://matomo.org that is installed to your server
|
||||
# The address of the server where Matomo is hosted.
|
||||
#- MATOMO_ADDRESS=https://example.com/matomo
|
||||
# The value of the site ID given in Matomo server for Wekan
|
||||
#- MATOMO_SITE_ID=1
|
||||
# The option do not track which enables users to not be tracked by matomo
|
||||
#- MATOMO_DO_NOT_TRACK=true
|
||||
# The option that allows matomo to retrieve the username:
|
||||
#- MATOMO_WITH_USERNAME=true
|
||||
#-----------------------------------------------------------------
|
||||
# ==== BROWSER POLICY AND TRUSTED IFRAME URL ====
|
||||
# Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside.
|
||||
# Setting this to false is not recommended, it also disables all other browser policy protections
|
||||
# and allows all iframing etc. See wekan/server/policy.js
|
||||
- BROWSER_POLICY_ENABLED=true
|
||||
# When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside.
|
||||
#- TRUSTED_URL=https://intra.example.com
|
||||
#-----------------------------------------------------------------
|
||||
# ==== METRICS ALLOWED IP ADDRESSES ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Features/Metrics.md
|
||||
#- METRICS_ALLOWED_IP_ADDRESSES=192.168.0.100,192.168.0.200
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OUTGOING WEBHOOKS ====
|
||||
# What to send to Outgoing Webhook, or leave out. If commented out the default values will be: cardId,listId,oldListId,boardId,comment,user,card,commentId,swimlaneId,customerField,customFieldValue
|
||||
#- WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,board,list,swimlane,commentId
|
||||
#-----------------------------------------------------------------
|
||||
# ==== Debug OIDC OAuth2 etc ====
|
||||
#- DEBUG=true
|
||||
#---------------------------------------------
|
||||
# ==== AUTOLOGIN WITH OIDC/OAUTH2 ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Login/autologin.md
|
||||
#- OIDC_REDIRECTION_ENABLED=true
|
||||
# OIDC RP-initiated logout endpoint (end_session_endpoint). When set, the
|
||||
# "Log Out" menu redirects to this endpoint so the identity provider session
|
||||
# is also ended and the user is returned to Wekan (ROOT_URL) via
|
||||
# post_logout_redirect_uri, instead of landing on the provider home page
|
||||
# (which errors for non-admin users). See https://github.com/wekan/wekan/issues/6158
|
||||
# Keycloak example:
|
||||
#- OAUTH2_LOGOUT_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/logout
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 ORACLE on premise identity manager OIM ====
|
||||
#- ORACLE_OIM_ENABLED=true
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 AZURE ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Login/Azure/Azure.md
|
||||
# 1) Register the application with Azure. Make sure you capture
|
||||
# the application ID as well as generate a secret key.
|
||||
# 2) Configure the environment variables. This differs slightly
|
||||
# by installation type, but make sure you have the following:
|
||||
#- OAUTH2_ENABLED=true
|
||||
# Optional OAuth2 CA Cert, see https://github.com/wekan/wekan/issues/3299
|
||||
#- OAUTH2_CA_CERT=ABCD1234
|
||||
# Use OAuth2 ADFS additional changes. Also needs OAUTH2_ENABLED=true setting.
|
||||
#- OAUTH2_ADFS_ENABLED=false
|
||||
# Azure AD B2C. https://github.com/wekan/wekan/issues/5242
|
||||
#- OAUTH2_B2C_ENABLED=false
|
||||
# SECURITY (GHSA-mp7g-hj5q-gxhq): Link an OIDC login to a pre-existing
|
||||
# Wekan account (password/LDAP) with the same email. OFF by default; when
|
||||
# false an OIDC login whose email already exists is rejected instead of
|
||||
# merged, preventing account takeover via spoofed email claims. Only
|
||||
# enable if you fully trust your OIDC provider's email claims, and even
|
||||
# then the provider must send email_verified=true for the merge to happen.
|
||||
#- OAUTH2_MERGE_EXISTING_USERS=false
|
||||
# When false, OAuth2/OIDC login is refused for users that do not already
|
||||
# have a Wekan account (matched by verified email).
|
||||
#- OAUTH2_AUTO_REGISTRATION=true
|
||||
# Comma/space separated OAuth2/OIDC group names whose members become Wekan
|
||||
# admins. Empty = off.
|
||||
#- OAUTH2_ADMIN_GROUPS=
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
# Application GUID captured during app registration:
|
||||
#- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx
|
||||
# Secret key generated during app registration:
|
||||
#- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
#- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
|
||||
#- OAUTH2_SERVER_URL=https://login.microsoftonline.com/
|
||||
#- OAUTH2_AUTH_ENDPOINT=/oauth2/v2.0/authorize
|
||||
#- OAUTH2_USERINFO_ENDPOINT=https://graph.microsoft.com/oidc/userinfo
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/oauth2/v2.0/token
|
||||
# The claim name you want to map to the unique ID field:
|
||||
#- OAUTH2_ID_MAP=email
|
||||
# The claim name you want to map to the username field:
|
||||
#- OAUTH2_USERNAME_MAP=email
|
||||
# The claim name you want to map to the full name field:
|
||||
#- OAUTH2_FULLNAME_MAP=name
|
||||
# The claim name you want to map to the email field:
|
||||
#- OAUTH2_EMAIL_MAP=email
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 Nextcloud ====
|
||||
# 1) Register the application with Nextcloud: https://your.nextcloud/index.php/settings/admin/security
|
||||
# Make sure you capture the application ID as well as generate a secret key.
|
||||
# Use https://your.wekan/_oauth/oidc for the redirect URI.
|
||||
# 2) Configure the environment variables. This differs slightly
|
||||
# by installation type, but make sure you have the following:
|
||||
#- OAUTH2_ENABLED=true
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
# Application GUID captured during app registration:
|
||||
#- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx
|
||||
# Secret key generated during app registration:
|
||||
#- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
#- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
|
||||
#- OAUTH2_SERVER_URL=https://your-nextcloud.tld
|
||||
#- OAUTH2_AUTH_ENDPOINT=/index.php/apps/oauth2/authorize
|
||||
#- OAUTH2_USERINFO_ENDPOINT=/ocs/v2.php/cloud/user?format=json
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/index.php/apps/oauth2/api/v1/token
|
||||
# The claim name you want to map to the unique ID field:
|
||||
#- OAUTH2_ID_MAP=id
|
||||
# The claim name you want to map to the username field:
|
||||
#- OAUTH2_USERNAME_MAP=id
|
||||
# The claim name you want to map to the full name field:
|
||||
#- OAUTH2_FULLNAME_MAP=display-name
|
||||
# The claim name you want to map to the email field:
|
||||
#- OAUTH2_EMAIL_MAP=email
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 KEYCLOAK ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Login/Keycloak/Keycloak.md <== MAPPING INFO, REQUIRED
|
||||
#- OAUTH2_ENABLED=true
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
#- OAUTH2_CLIENT_ID=<Keycloak create Client ID>
|
||||
#- OAUTH2_SERVER_URL=<Keycloak server url - https://keycloak.example.com>
|
||||
#- OAUTH2_AUTH_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/auth
|
||||
#- OAUTH2_USERINFO_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/userinfo
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/token
|
||||
#- OAUTH2_SECRET=<keycloak client secret>
|
||||
#- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
|
||||
#- OAUTH2_ID_MAP=sub
|
||||
#- OAUTH2_USERNAME_MAP=preferred_username
|
||||
#- OAUTH2_EMAIL_MAP=email
|
||||
#- OAUTH2_FULLNAME_MAP=name
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 DOORKEEPER ====
|
||||
# https://github.com/wekan/wekan/issues/1874
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Login/OAuth2.md2
|
||||
# Enable the OAuth2 connection
|
||||
#- OAUTH2_ENABLED=true
|
||||
# OAuth2 docs: https://github.com/wekan/wekan/blob/main/docs/Login/OAuth2.md
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
# OAuth2 Client ID.
|
||||
#- OAUTH2_CLIENT_ID=abcde12345
|
||||
# OAuth2 Secret.
|
||||
#- OAUTH2_SECRET=54321abcde
|
||||
#- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
|
||||
# OAuth2 Server URL.
|
||||
#- OAUTH2_SERVER_URL=https://chat.example.com
|
||||
# OAuth2 Authorization Endpoint.
|
||||
#- OAUTH2_AUTH_ENDPOINT=/oauth/authorize
|
||||
# OAuth2 Userinfo Endpoint.
|
||||
#- OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
|
||||
# OAuth2 Token Endpoint.
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/oauth/token
|
||||
# OAUTH2 ID Token Whitelist Fields.
|
||||
#- OAUTH2_ID_TOKEN_WHITELIST_FIELDS=""
|
||||
# OAUTH2 Request Permissions.
|
||||
#- OAUTH2_REQUEST_PERMISSIONS=openid profile email
|
||||
# OAuth2 ID Mapping
|
||||
#- OAUTH2_ID_MAP=
|
||||
# OAuth2 Username Mapping
|
||||
#- OAUTH2_USERNAME_MAP=
|
||||
# OAuth2 Fullname Mapping
|
||||
#- OAUTH2_FULLNAME_MAP=
|
||||
# OAuth2 Email Mapping
|
||||
#- OAUTH2_EMAIL_MAP=
|
||||
#-----------------------------------------------------------------
|
||||
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
|
||||
# https://github.com/wekan/wekan/blob/main/docs/Login/LDAP.md
|
||||
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Snap/Supported-settings-keys.md
|
||||
# Most settings work both on Snap and Docker below.
|
||||
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
|
||||
#
|
||||
# The default authentication method used if a user does not exist to create and authenticate. Can be set as ldap.
|
||||
# (this is set properly in the Admin Panel, changing this item does not remove Password login option)
|
||||
#- DEFAULT_AUTHENTICATION_METHOD=ldap
|
||||
#
|
||||
# Enable or not the connection by the LDAP
|
||||
#- LDAP_ENABLE=true
|
||||
#
|
||||
# The port of the LDAP server
|
||||
#- LDAP_PORT=389
|
||||
#
|
||||
# The host server for the LDAP server
|
||||
#- LDAP_HOST=localhost
|
||||
#
|
||||
#-----------------------------------------------------------------
|
||||
# ==== LDAP AD Simple Auth ====
|
||||
#
|
||||
# Set to true, if you want to connect with Active Directory by Simple Authentication.
|
||||
# When using AD Simple Auth, LDAP_BASEDN is not needed.
|
||||
#
|
||||
# Example:
|
||||
#- LDAP_AD_SIMPLE_AUTH=true
|
||||
#
|
||||
# === LDAP User Authentication ===
|
||||
#
|
||||
# a) Option to login to the LDAP server with the user's own username and password, instead of
|
||||
# an administrator key. Default: false (use administrator key).
|
||||
#
|
||||
# b) When using AD Simple Auth, set to true, when login user is used for binding,
|
||||
# and LDAP_BASEDN is not needed.
|
||||
#
|
||||
# Example:
|
||||
#- LDAP_USER_AUTHENTICATION=true
|
||||
#
|
||||
# Which field is used to find the user for the user authentication. Default: uid.
|
||||
#- LDAP_USER_AUTHENTICATION_FIELD=uid
|
||||
#
|
||||
# === LDAP Default Domain ===
|
||||
#
|
||||
# a) In case AD SimpleAuth is configured, the default domain is appended to the given
|
||||
# loginname for creating the correct username for the bind request to AD.
|
||||
#
|
||||
# b) The default domain of the ldap it is used to create email if the field is not map
|
||||
# correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
|
||||
#
|
||||
# Example :
|
||||
#- LDAP_DEFAULT_DOMAIN=mydomain.com
|
||||
#
|
||||
#-----------------------------------------------------------------
|
||||
# ==== LDAP BASEDN Auth ====
|
||||
#
|
||||
# The base DN for the LDAP Tree
|
||||
#- LDAP_BASEDN=ou=user,dc=example,dc=org
|
||||
#
|
||||
#-----------------------------------------------------------------
|
||||
# Fallback on the default authentication method
|
||||
#- LDAP_LOGIN_FALLBACK=false
|
||||
#
|
||||
# Reconnect to the server if the connection is lost
|
||||
#- LDAP_RECONNECT=true
|
||||
#
|
||||
# Overall timeout, in milliseconds
|
||||
#- LDAP_TIMEOUT=10000
|
||||
#
|
||||
# Specifies the timeout for idle LDAP connections in milliseconds
|
||||
#- LDAP_IDLE_TIMEOUT=10000
|
||||
#
|
||||
# Connection timeout, in milliseconds
|
||||
#- LDAP_CONNECT_TIMEOUT=10000
|
||||
#
|
||||
# If the LDAP needs a user account to search
|
||||
#- LDAP_AUTHENTIFICATION=true
|
||||
#
|
||||
# The search user DN - You need quotes when you have spaces in parameters
|
||||
# 2 examples:
|
||||
#- LDAP_AUTHENTIFICATION_USERDN=CN=ldap admin,CN=users,DC=domainmatter,DC=lan
|
||||
#- LDAP_AUTHENTIFICATION_USERDN=CN=wekan_adm,OU=serviceaccounts,OU=admin,OU=prod,DC=mydomain,DC=com
|
||||
#
|
||||
# The password for the search user
|
||||
#- LDAP_AUTHENTIFICATION_PASSWORD=pwd
|
||||
#- LDAP_AUTHENTIFICATION_PASSWORD_FILE=/run/secrets/ldap_auth_password
|
||||
#
|
||||
# Enable logs for the module
|
||||
#- LDAP_LOG_ENABLED=true
|
||||
#
|
||||
# If the sync of the users should be done in the background
|
||||
#- LDAP_BACKGROUND_SYNC=false
|
||||
#
|
||||
# LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds
|
||||
# The format must be as specified in:
|
||||
# https://bunkat.github.io/later/parsers.html#text
|
||||
#- LDAP_BACKGROUND_SYNC_INTERVAL=every 1 hours
|
||||
# At which interval does the background task sync in milliseconds.
|
||||
# Leave this unset, so it uses default, and does not crash.
|
||||
# https://github.com/wekan/wekan/issues/2354#issuecomment-515305722
|
||||
- LDAP_BACKGROUND_SYNC_INTERVAL=''
|
||||
#
|
||||
#- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
|
||||
#
|
||||
#- LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false
|
||||
#
|
||||
# LDAP_BACKGROUND_SYNC_DISABLE_NONEXISTANT_USERS : When true, the LDAP
|
||||
# background sync disables users no longer present in LDAP (and re-enables
|
||||
# them when they reappear).
|
||||
#- LDAP_BACKGROUND_SYNC_DISABLE_NONEXISTANT_USERS=false
|
||||
#
|
||||
# If using LDAPS: LDAP_ENCRYPTION=ssl
|
||||
#- LDAP_ENCRYPTION=false
|
||||
#
|
||||
# The certification for the LDAPS server. Certificate needs to be included in this docker compose.yml file.
|
||||
#- LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+G2FIdAgIC...-----END CERTIFICATE-----
|
||||
#
|
||||
# Reject Unauthorized Certificate
|
||||
#- LDAP_REJECT_UNAUTHORIZED=false
|
||||
#
|
||||
# Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
#- LDAP_USER_SEARCH_FILTER=
|
||||
#
|
||||
# base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
|
||||
#- LDAP_USER_SEARCH_SCOPE=one
|
||||
#
|
||||
# Which field is used to find the user, like uid / sAMAccountName
|
||||
#- LDAP_USER_SEARCH_FIELD=sAMAccountName
|
||||
#
|
||||
# Used for pagination (0=unlimited)
|
||||
#- LDAP_SEARCH_PAGE_SIZE=0
|
||||
#
|
||||
# The limit number of entries (0=unlimited)
|
||||
#- LDAP_SEARCH_SIZE_LIMIT=0
|
||||
#
|
||||
# Enable the login restriction group filter. Note the authenticated ldap user must be able to query all relevant group data with own login data from ldap.
|
||||
# When true, only members of LDAP_GROUP_FILTER_GROUP_NAME are allowed to log in.
|
||||
# NOTE: This flag ONLY controls the login restriction. Admin status sync
|
||||
# (LDAP_SYNC_ADMIN_STATUS / LDAP_SYNC_ADMIN_GROUPS) and group->role sync
|
||||
# (LDAP_SYNC_GROUP_ROLES) query LDAP groups independently and do NOT require
|
||||
# this flag to be true. The group filter metadata below
|
||||
# (LDAP_GROUP_FILTER_OBJECTCLASS, LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE,
|
||||
# LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT, LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE)
|
||||
# must still be configured for any group search to work.
|
||||
#- LDAP_GROUP_FILTER_ENABLE=false
|
||||
#
|
||||
# The object class for filtering. Example: group
|
||||
#- LDAP_GROUP_FILTER_OBJECTCLASS=
|
||||
#
|
||||
# The attribute of a group identifying it. Example: cn
|
||||
#- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
|
||||
#
|
||||
# The attribute inside a group object listing its members. Example: member
|
||||
#- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
|
||||
#
|
||||
# The format of the value of LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE. Example: 'dn' if the users dn is saved as value into the attribute.
|
||||
#- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
|
||||
#
|
||||
# The group name (id) that matches all users.
|
||||
#- LDAP_GROUP_FILTER_GROUP_NAME=
|
||||
#
|
||||
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier). Example: guid
|
||||
#- LDAP_UNIQUE_IDENTIFIER_FIELD=
|
||||
#
|
||||
# LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8
|
||||
#- LDAP_UTF8_NAMES_SLUGIFY=true
|
||||
#
|
||||
# LDAP_USERNAME_FIELD : Which field contains the ldap username. username / sAMAccountName
|
||||
#- LDAP_USERNAME_FIELD=sAMAccountName
|
||||
#
|
||||
# LDAP_FULLNAME_FIELD : Which field contains the ldap fullname. fullname / sAMAccountName
|
||||
#- LDAP_FULLNAME_FIELD=fullname
|
||||
#
|
||||
#- LDAP_MERGE_EXISTING_USERS=false
|
||||
#
|
||||
# Allow existing account matching by e-mail address when username does not match
|
||||
#- LDAP_EMAIL_MATCH_ENABLE=true
|
||||
#
|
||||
# LDAP_EMAIL_MATCH_REQUIRE : require existing account matching by e-mail address when username does match
|
||||
#- LDAP_EMAIL_MATCH_REQUIRE=true
|
||||
#
|
||||
# LDAP_EMAIL_MATCH_VERIFIED : require existing account email address to be verified for matching
|
||||
#- LDAP_EMAIL_MATCH_VERIFIED=true
|
||||
#
|
||||
# LDAP_EMAIL_FIELD : which field contains the LDAP e-mail address
|
||||
#- LDAP_EMAIL_FIELD=mail
|
||||
#-----------------------------------------------------------------
|
||||
#- LDAP_SYNC_USER_DATA=false
|
||||
#
|
||||
#- LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
|
||||
#
|
||||
#- LDAP_SYNC_GROUP_ROLES=
|
||||
#
|
||||
# The default domain of the ldap it is used to create email if the field is not map correctly
|
||||
# with the LDAP_SYNC_USER_DATA_FIELDMAP is defined in setting LDAP_DEFAULT_DOMAIN above.
|
||||
#
|
||||
# Enable/Disable syncing of admin status based on ldap groups:
|
||||
# NOTE: Admin status sync and group->role sync (LDAP_SYNC_GROUP_ROLES) query
|
||||
# LDAP groups on their own. They no longer require LDAP_GROUP_FILTER_ENABLE=true,
|
||||
# which only controls the login restriction filter. The group filter metadata
|
||||
# (LDAP_GROUP_FILTER_OBJECTCLASS, LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE,
|
||||
# LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT, LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE)
|
||||
# must still be configured for the group search to work.
|
||||
#- LDAP_SYNC_ADMIN_STATUS=true
|
||||
#
|
||||
# Comma separated list of admin group names to sync.
|
||||
#- LDAP_SYNC_ADMIN_GROUPS=group1,group2
|
||||
#
|
||||
# When true, sync a user's LDAP groups as Wekan Organizations.
|
||||
#- LDAP_SYNC_ORGANIZATIONS=false
|
||||
#
|
||||
# Comma separated allowlist of LDAP group names to sync as Organizations. Empty = all of the user's groups.
|
||||
#- LDAP_SYNC_ORGANIZATIONS_GROUPS=
|
||||
#
|
||||
# When true, sync a user's LDAP groups as Wekan Teams.
|
||||
#- LDAP_SYNC_TEAMS=false
|
||||
#
|
||||
# Comma separated allowlist of LDAP group names to sync as Teams. Empty = all of the user's groups.
|
||||
#- LDAP_SYNC_TEAMS_GROUPS=
|
||||
#---------------------------------------------------------------------
|
||||
# Login to LDAP automatically with HTTP header.
|
||||
# In below example for siteminder, at right side of = is header name.
|
||||
#- HEADER_LOGIN_ID=HEADERUID
|
||||
#- HEADER_LOGIN_FIRSTNAME=HEADERFIRSTNAME
|
||||
#- HEADER_LOGIN_LASTNAME=HEADERLASTNAME
|
||||
#- HEADER_LOGIN_EMAIL=HEADEREMAILADDRESS
|
||||
# SECURITY (GHSA-jggc-qvfc-jr6x): comma-separated allowlist of source IPs
|
||||
# permitted to use header login. The source IP is the real TCP peer of the
|
||||
# connection (your reverse proxy), NOT the spoofable X-Forwarded-For header.
|
||||
# REQUIRED when header login is enabled: if empty/unset, header login fails
|
||||
# CLOSED and authenticates no one.
|
||||
#- HEADER_LOGIN_TRUSTED_IPS=127.0.0.1,10.0.0.2
|
||||
# Optional: if WeKan sits behind MULTIPLE proxy hops, list the intermediate
|
||||
# proxy IPs here. X-Forwarded-For is then honored ONLY when the immediate
|
||||
# TCP peer is one of these trusted proxies, and the right-most hop that is
|
||||
# not itself a trusted proxy (the real client) is matched against
|
||||
# HEADER_LOGIN_TRUSTED_IPS above.
|
||||
#- HEADER_LOGIN_TRUSTED_PROXIES=10.0.0.1,10.0.0.2
|
||||
#---------------------------------------------------------------------
|
||||
# ==== LOGOUT TIMER, probably does not work yet ====
|
||||
# LOGOUT_WITH_TIMER : Enables or not the option logout with timer
|
||||
# example : LOGOUT_WITH_TIMER=true
|
||||
#- LOGOUT_WITH_TIMER=
|
||||
#
|
||||
# LOGOUT_IN : The number of days
|
||||
# example : LOGOUT_IN=1
|
||||
#- LOGOUT_IN=
|
||||
#
|
||||
# LOGOUT_ON_HOURS : The number of hours
|
||||
# example : LOGOUT_ON_HOURS=9
|
||||
#- LOGOUT_ON_HOURS=
|
||||
#
|
||||
# LOGOUT_ON_MINUTES : The number of minutes
|
||||
# example : LOGOUT_ON_MINUTES=55
|
||||
#- LOGOUT_ON_MINUTES=
|
||||
#-------------------------------------------------------------------
|
||||
# Hide password login form
|
||||
# - PASSWORD_LOGIN_ENABLED=true
|
||||
#-------------------------------------------------------------------
|
||||
#- CAS_ENABLED=true
|
||||
#- CAS_BASE_URL=https://cas.example.com/cas
|
||||
#- CAS_LOGIN_URL=https://cas.example.com/login
|
||||
#- CAS_VALIDATE_URL=https://cas.example.com/cas/p3/serviceValidate
|
||||
#---------------------------------------------------------------------
|
||||
#- SAML_ENABLED=true
|
||||
#- SAML_PROVIDER=
|
||||
#- SAML_ENTRYPOINT=
|
||||
#- SAML_ISSUER=
|
||||
#- SAML_CERT=
|
||||
#- SAML_IDPSLO_REDIRECTURL=
|
||||
#- SAML_PRIVATE_KEYFILE=
|
||||
#- SAML_PUBLIC_CERTFILE=
|
||||
#- SAML_IDENTIFIER_FORMAT=
|
||||
#- SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE=
|
||||
#- SAML_ATTRIBUTES=
|
||||
#---------------------------------------------------------------------
|
||||
# Wait spinner to use
|
||||
# - WAIT_SPINNER=Bounce
|
||||
#---------------------------------------------------------------------
|
||||
# Wait for MongoDB to be healthy (replica-set primary elected) before
|
||||
# starting WeKan, so index creation does not crash with "Topology is closed".
|
||||
# If you use FerretDB instead, comment out the wekandb block below and
|
||||
# uncomment the wekan-ferretdb block.
|
||||
depends_on:
|
||||
ferretdb:
|
||||
condition: service_started
|
||||
# wekan-migrate:
|
||||
# condition: service_completed_successfully
|
||||
# wekan-ferretdb:
|
||||
# condition: service_healthy
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- wekan-files:/data:rw
|
||||
|
||||
volumes:
|
||||
wekan-files:
|
||||
driver: local
|
||||
postgres-data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
wekan-tier:
|
||||
driver: bridge
|
||||
@@ -1,82 +0,0 @@
|
||||
# WeKan with FerretDB 2 + PostgreSQL (DocumentDB extension)
|
||||
#
|
||||
# Use this compose file instead of docker-compose.yml on platforms where
|
||||
# MongoDB Community Edition is not available, or when you prefer PostgreSQL
|
||||
# as the backing store.
|
||||
#
|
||||
# Supported on all platforms where Node.js 22 runs:
|
||||
# linux/amd64, linux/arm64, linux/arm/v7, linux/ppc64le, linux/s390x
|
||||
#
|
||||
# Based on: docs/Databases/FerretDB2-PostgreSQL.md
|
||||
#
|
||||
# Setup:
|
||||
# docker compose -f docker-compose-ferretdb.yml up -d
|
||||
#
|
||||
# To stop:
|
||||
# docker compose -f docker-compose-ferretdb.yml stop
|
||||
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
|
||||
# PostgreSQL 17 with DocumentDB extension (FerretDB 2 backing store)
|
||||
# ghcr.io/ferretdb/postgres-documentdb is the official PostgreSQL image
|
||||
# pre-installed with the pg_documentdb extension required by FerretDB 2.
|
||||
postgres-documentdb:
|
||||
image: ghcr.io/ferretdb/postgres-documentdb:17
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: ferretdb
|
||||
POSTGRES_PASSWORD: ${FERRETDB_POSTGRES_PASSWORD:-ferretdb_secret}
|
||||
POSTGRES_DB: ferretdb
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- wekan-ferretdb
|
||||
|
||||
# FerretDB 2 — MongoDB-compatible protocol layer over PostgreSQL
|
||||
ferretdb:
|
||||
image: ghcr.io/ferretdb/ferretdb:2
|
||||
restart: always
|
||||
environment:
|
||||
FERRETDB_POSTGRESQL_URL: postgres://ferretdb:${FERRETDB_POSTGRES_PASSWORD:-ferretdb_secret}@postgres-documentdb/ferretdb
|
||||
ports:
|
||||
# Expose the MongoDB-compatible port only on localhost for security.
|
||||
# Remove "127.0.0.1:" prefix if WeKan runs on a different host.
|
||||
- 127.0.0.1:27017:27017
|
||||
depends_on:
|
||||
- postgres-documentdb
|
||||
networks:
|
||||
- wekan-ferretdb
|
||||
|
||||
# WeKan application
|
||||
wekan-app:
|
||||
image: ghcr.io/wekan/wekan:latest
|
||||
restart: always
|
||||
networks:
|
||||
- wekan-ferretdb
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
# FerretDB speaks the MongoDB wire protocol on port 27017.
|
||||
# The database name "wekan" is created automatically on first run.
|
||||
MONGO_URL: mongodb://ferretdb:${FERRETDB_POSTGRES_PASSWORD:-ferretdb_secret}@ferretdb:27017/wekan?authMechanism=PLAIN
|
||||
ROOT_URL: ${ROOT_URL:-http://localhost:8080}
|
||||
PORT: 8080
|
||||
WITH_API: true
|
||||
WRITABLE_PATH: /data
|
||||
# Mail settings (optional)
|
||||
MAIL_URL: ${MAIL_URL:-}
|
||||
MAIL_FROM: ${MAIL_FROM:-}
|
||||
volumes:
|
||||
- wekan-data:/data
|
||||
depends_on:
|
||||
- ferretdb
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
wekan-data:
|
||||
|
||||
networks:
|
||||
wekan-ferretdb:
|
||||
driver: bridge
|
||||
@@ -454,6 +454,14 @@ services:
|
||||
# https://github.com/wekan/wekan/pull/2560
|
||||
- RICHER_CARD_COMMENT_EDITOR=false
|
||||
#---------------------------------------------------------------
|
||||
# ==== CARD LOADING: all (default) or lazy ====
|
||||
# 'all' = load every card of a board into the browser (default).
|
||||
# 'lazy' = each list loads only the cards currently visible
|
||||
# (infinite scroll) plus a live count, so boards with
|
||||
# thousands of cards use less memory/bandwidth.
|
||||
# Also changeable at runtime in Admin Panel / Features.
|
||||
- CARDS_LOADING=all
|
||||
#---------------------------------------------------------------
|
||||
# ==== CARD OPENED, SEND WEBHOOK MESSAGE ====
|
||||
# https://github.com/wekan/wekan/issues/2518
|
||||
- CARD_OPENED_WEBHOOK_ENABLED=false
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
# Alternative Architectures, JavaScript Engines and Rewrite Options
|
||||
|
||||
This page documents which CPU architectures WeKan can run on, **why** some
|
||||
architectures are not supported, and what the realistic options are for
|
||||
supporting more of them — alternative JavaScript engines and full server
|
||||
rewrites (Go, Tcl/Tk, QuickJS, …).
|
||||
|
||||
It exists because the question keeps coming up: *"WeKan won't run on my 32-bit
|
||||
ARM board / old x86 / ReactOS — can we fix that?"* The short answer is that the
|
||||
limit is **Node.js**, not the database, and getting past it means either
|
||||
compiling Node from source (where still possible) or rewriting the server in a
|
||||
portable language.
|
||||
|
||||
## TL;DR
|
||||
|
||||
- WeKan's **server** is Meteor 3, which runs **only on Node.js 24**. The browser
|
||||
client is architecture-independent. So the supported-architecture list is
|
||||
exactly *"where is there a Node.js 24 build?"*.
|
||||
- The **database is not the bottleneck**: [FerretDB v1](https://github.com/wekan/FerretDB)
|
||||
(pure Go, SQLite backend, MongoDB wire protocol) cross-compiles to far more
|
||||
architectures than Node does.
|
||||
- **Node.js 24 dropped 32-bit**: no `armv7l`/`armhf` binaries (downgraded to
|
||||
*Experimental*, source-build only), no 32-bit x86 Linux (`i386`) at all.
|
||||
- **Other JS runtimes do not help**: Deno and Bun cover *fewer* architectures
|
||||
than Node (x64/arm64 only). Portable engines (QuickJS, JavaScriptCore,
|
||||
SpiderMonkey, JVM-based Rhino/GraalJS) *do* run on 32-bit, but **none of them
|
||||
run Meteor/WeKan** — Meteor is locked to Node.
|
||||
- If universal architecture coverage is the goal, a **Go server** (matching
|
||||
FerretDB) is the best target; **Tcl/Tk and QuickJS are worse fits** for a web
|
||||
app. All of these are large rewrites, not drop-in swaps.
|
||||
|
||||
## The two layers
|
||||
|
||||
Keep these separate — they have very different portability:
|
||||
|
||||
| Layer | Technology | Portability |
|
||||
| --- | --- | --- |
|
||||
| Client | HTML/CSS/JS in the browser | Architecture-independent (runs wherever a browser runs) |
|
||||
| **Server** | **Meteor 3 → Node.js 24** | **Only where a Node.js 24 build exists** |
|
||||
| Database | MongoDB *or* FerretDB v1 (Go) | MongoDB: amd64/arm64 only. FerretDB: almost everything |
|
||||
|
||||
Only the **server binary** (the Node.js runtime) constrains the architecture.
|
||||
|
||||
## Architecture support matrix
|
||||
|
||||
"Node 24" is what limits WeKan; "FerretDB v1" shows the database is available
|
||||
well beyond that. Sources:
|
||||
[nodejs.org/dist/latest-v24.x](https://nodejs.org/dist/latest-v24.x/) (official),
|
||||
[unofficial-builds.nodejs.org](https://unofficial-builds.nodejs.org/download/release/)
|
||||
(extra arches), and [wekan/FerretDB releases](https://github.com/wekan/FerretDB/releases).
|
||||
|
||||
| Arch | Node.js 24 | MongoDB | FerretDB v1 | WeKan runs? |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| amd64 (x64) | ✅ official | ✅ | ✅ | ✅ |
|
||||
| arm64 (aarch64) | ✅ official | ✅ | ✅ | ✅ |
|
||||
| ppc64le | ✅ official | ❌ | ✅ | ✅ (FerretDB) |
|
||||
| s390x | ✅ official | ❌ | ✅ | ✅ (FerretDB) |
|
||||
| riscv64 | ✅ unofficial (glibc) | ❌ | ✅ | ✅ (FerretDB) |
|
||||
| **loong64** | ✅ unofficial (glibc) | ❌ | ✅ | ⚠️ binaries exist, but **not buildable in CI** — see below |
|
||||
| **armhf / armv7l** | ❌ (source-build only, Experimental) | ❌ | ✅ | ❌ (no Node binary) |
|
||||
| **armel (armv5)** | ❌ | ❌ | ✅ | ❌ (no Node binary) |
|
||||
| **i386 (32-bit x86 Linux)** | ❌ (port removed) | ❌ | ✅ | ❌ (no Node binary) |
|
||||
|
||||
### Why `armhf` / `armv7l` is not supported
|
||||
|
||||
`armhf` = `armv7l` = 32-bit ARMv7 hard-float (the same thing; the `l` is
|
||||
little-endian; Debian calls it `armhf`, Docker calls it `linux/arm/v7`).
|
||||
|
||||
Node.js **downgraded ARMv7 to "Experimental" in v24** ("Downgraded as of Node.js
|
||||
24" in Node's `BUILDING.md`). That means the source still compiles for ARMv7, but
|
||||
**no binaries are published or tested**. Neither nodejs.org nor
|
||||
unofficial-builds ship an `armv7l` tarball for v24, and NodeSource does not ship
|
||||
`armhf` for the 24.x line. Distributions (Debian, Alpine) build Node from source
|
||||
for `armhf`, but their versions lag behind 24.x and are their own (often musl)
|
||||
builds, not the portable glibc tarball WeKan's self-contained bundle embeds.
|
||||
|
||||
→ The only way to get an `armhf`/`armv7l` WeKan bundle is to **compile Node 24
|
||||
from source** in CI (slow under emulation, Experimental tier, unsupported).
|
||||
|
||||
### Why `i386` (32-bit x86 Linux) is not supported
|
||||
|
||||
Node.js **removed the 32-bit x86 Linux port** years ago. There is no binary and
|
||||
no practical source build. So 32-bit x86 Linux cannot run current WeKan at all,
|
||||
regardless of effort. (FerretDB has an `i386` binary, but there is nothing to run
|
||||
the WeKan server with.)
|
||||
|
||||
### Why `loong64` is not built in CI (even though binaries exist)
|
||||
|
||||
LoongArch64 has both a Node 24 unofficial-builds binary **and** a FerretDB v1
|
||||
binary, so in principle WeKan could run on it. But the release workflow cannot
|
||||
**build** the bundle/Docker image for it:
|
||||
|
||||
1. **QEMU cannot emulate LoongArch.** `docker/setup-qemu-action`
|
||||
([tonistiigi/binfmt](https://github.com/tonistiigi/binfmt)) supports
|
||||
`amd64, arm64, riscv64, ppc64le, s390x, 386, arm/v7, arm/v6` — **no
|
||||
loong64**. The extra-arch bundles rebuild native modules (e.g. bcrypt) by
|
||||
running an emulated container of the target arch; with no emulator, that step
|
||||
is impossible.
|
||||
2. **No official base image.** Ubuntu/Debian do not publish an official
|
||||
`linux/loong64` container image (LoongArch is a debian-ports / community
|
||||
architecture), so there is nothing to build inside.
|
||||
|
||||
FerretDB's own image builds for `linux/loong64` only because Go **cross-compiles**
|
||||
from the amd64 builder (it never runs loong64 code at build time). WeKan's native
|
||||
`.node` addons would instead need a real LoongArch build environment — a
|
||||
`loongarch64-linux-gnu` cross-toolchain plus a (non-mainstream) loong64 runtime
|
||||
image. That is significantly more work than the riscv64 path and is why loong64
|
||||
is currently left out.
|
||||
|
||||
## Alternative JavaScript engines
|
||||
|
||||
The engine itself (V8) *can* target 32-bit — the problem is that the **Node.js
|
||||
project stopped shipping those binaries** and **Meteor is bound to Node**.
|
||||
Swapping the engine does not run Meteor.
|
||||
|
||||
| Engine / runtime | armhf / armv7 | i386 | Runs Meteor/WeKan? |
|
||||
| --- | --- | --- | --- |
|
||||
| **Node.js 24** (V8) | source-build only (exp.) | ❌ | — (current) |
|
||||
| **Deno** (V8, Rust) | ❌ x64/arm64 only | ❌ | ❌ |
|
||||
| **Bun** (JavaScriptCore, Zig) | ❌ x64/arm64 only | ❌ | ❌ |
|
||||
| **QuickJS** (Bellard, pure C) | ✅ any arch | ✅ | ❌ (no Node API / npm) |
|
||||
| **Duktape / MuJS / JerryScript** | ✅ | ✅ | ❌ (ES5, embeddable) |
|
||||
| **JavaScriptCore / SpiderMonkey** | ✅ (WebKit/Firefox build there) | ✅ | ❌ |
|
||||
| **Rhino / GraalJS** (on a JVM) | ✅ (OpenJDK armhf/i386) | ✅ | ❌ |
|
||||
|
||||
Two takeaways:
|
||||
|
||||
- The modern Node alternatives, **Deno and Bun, are *worse*** for architecture
|
||||
coverage (x64/arm64 only) — they do not help with 32-bit.
|
||||
- Several engines (**QuickJS, JavaScriptCore, SpiderMonkey, JVM-based**) *do* run
|
||||
on `armhf`/`i386`, but **none run Meteor/WeKan**. Meteor requires Node.js
|
||||
specifically (V8 + libuv + Node's native APIs + Meteor's Node-based build), so
|
||||
using another engine means rewriting the server for that runtime — as large a
|
||||
job as a language rewrite.
|
||||
|
||||
## Full server rewrite options
|
||||
|
||||
If the goal is to run on architectures Node cannot reach, the server has to be
|
||||
rewritten in a portable language. The browser client stays as-is; the new server
|
||||
just serves the SPA, the API, reactive sync, accounts and methods, talking to
|
||||
FerretDB/MongoDB.
|
||||
|
||||
### Go (recommended for portability)
|
||||
|
||||
- **Cross-compiles everywhere** — `i386`, `armv7`, `armhf`, `armel`, `loong64`,
|
||||
`mips`, … exactly as [FerretDB](https://github.com/wekan/FerretDB) already does,
|
||||
as a single static binary with no Node-ABI or native-module problems.
|
||||
- Speaks the **MongoDB wire protocol natively** to FerretDB/MongoDB.
|
||||
- Mature HTTP/WebSocket ecosystem for the API and reactive sync.
|
||||
- **Cost:** this is effectively a **different product** — Meteor's DDP, reactive
|
||||
pub/sub, accounts, methods and client sync all have to be rebuilt. Very large
|
||||
effort.
|
||||
|
||||
### QuickJS (the "keep JavaScript" option)
|
||||
|
||||
- Pure C, compiles on virtually **any architecture**, tiny footprint.
|
||||
- **Cost:** no npm ecosystem and no Node API, so the server is written against a
|
||||
minimal runtime — about as much work as the Go rewrite, with a smaller
|
||||
ecosystem and weaker performance. Niche.
|
||||
|
||||
### Tcl/Tk (not recommended for this)
|
||||
|
||||
- **Tcl** is extremely portable (pure C, builds on any arch: `i386`, `armv7`,
|
||||
`armhf`, `armv5`, `mips`, …).
|
||||
- **But Tk is a desktop GUI toolkit**, not a web-server stack. WeKan is a
|
||||
**web application** (browser SPA + server + database); Tk would produce a
|
||||
native desktop window — a completely different product and UX.
|
||||
- Tcl *can* run a web server (NaviServer/AOLserver, tclhttpd, Wapp), but it is a
|
||||
niche stack with no reactive-SPA story. For a web app this is a **step
|
||||
backwards** compared to Go.
|
||||
|
||||
### Comparison
|
||||
|
||||
| Option | Arch coverage | Effort | Fit for WeKan (web app) |
|
||||
| --- | --- | --- | --- |
|
||||
| Compile Node 24 from source (armv7 only) | + armv7 (not i386) | Medium, fragile, Experimental | Keeps Meteor; no rewrite |
|
||||
| **Go server** | Universal (like FerretDB) | Very large (full rewrite) | Best; static binary, native FerretDB |
|
||||
| QuickJS server | Universal | Very large | OK; keeps JS, small ecosystem |
|
||||
| Tcl/Tk | Universal (Tcl) | Very large | Poor; GUI/scripting, not a web stack |
|
||||
|
||||
## Hardware / OS notes
|
||||
|
||||
These are about the **FerretDB (Go) binary** on specific targets; remember the
|
||||
WeKan *server* still needs a Node.js 24 build, which is the actual blocker above.
|
||||
|
||||
- **ODroid-U3 (ARMv7-A, Linux)** — FerretDB `armhf` (GOARM=7) runs fine. The
|
||||
board has enough RAM for FerretDB v1; only the missing Node 24 `armhf` binary
|
||||
stops WeKan itself.
|
||||
- **Raspberry Pi OS 32-bit** — FerretDB `armhf` (v7) runs on Pi 2/3/4 (ARMv7);
|
||||
Pi 1/Zero (ARMv6) need the `armel` (GOARM=5) binary. Again, the WeKan server is
|
||||
blocked only by Node.
|
||||
- **ReactOS 32-bit** — the matching binary would be FerretDB `win32`
|
||||
(windows/386), but it is **doubtful**: Go 1.21+ targets Windows 10 / Server
|
||||
2016-level APIs, while ReactOS is around XP/2003, so Go's runtime may use APIs
|
||||
ReactOS does not implement. (On 32-bit *Linux* the `linux/386` FerretDB binary
|
||||
works.)
|
||||
|
||||
## Conclusion
|
||||
|
||||
- The architecture limit is **Node.js**, not FerretDB. FerretDB (Go) already runs
|
||||
on 32-bit ARM, 32-bit x86 and more.
|
||||
- With current tooling, the practical WeKan set is **amd64, arm64, ppc64le,
|
||||
s390x, riscv64**. `loong64` is blocked by tooling (no QEMU emulation, no base
|
||||
image), not by Node/FerretDB. `armhf`/`armel`/`i386` are blocked by Node.
|
||||
- Alternative JS engines do **not** unblock this — Deno/Bun cover fewer arches,
|
||||
and portable engines cannot run Meteor.
|
||||
- The only way to universal architecture coverage is a **portable-language server
|
||||
rewrite**, for which **Go is the best fit** (matching FerretDB), QuickJS the
|
||||
"keep JS" niche option, and Tcl/Tk a poor fit for a web application.
|
||||
@@ -10,7 +10,7 @@ This is without container (without Docker or Snap).
|
||||
|
||||
Right click and download files 1-4:
|
||||
|
||||
1. [wekan-9.78-win64.zip](https://github.com/wekan/wekan/releases/download/v9.78/wekan-9.78-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
|
||||
1. [wekan-9.84-win64.zip](https://github.com/wekan/wekan/releases/download/v9.84/wekan-9.84-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
|
||||
|
||||
2. [node.exe](https://nodejs.org/dist/latest-v24.x/win-x64/node.exe)
|
||||
|
||||
@@ -22,7 +22,7 @@ Right click and download files 1-4:
|
||||
|
||||
6. Double click `mongodb-windows-x86_64-7.0.34-signed.msi` . In installer, uncheck downloading MongoDB compass.
|
||||
|
||||
7. Unzip `wekan-9.78-win64.zip` , inside it is directory `bundle`, to it copy other files:
|
||||
7. Unzip `wekan-9.84-win64.zip` , inside it is directory `bundle`, to it copy other files:
|
||||
|
||||
```
|
||||
bundle (directory)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Menerima",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Cipta ruangan",
|
||||
"createCustomFieldPopup-title": "Cipta ruangan",
|
||||
"current": "sekarang",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Tidak boleh undur semula. Ianya akan menghapuskan ruangan khas daripada semua kad dan padamkan sejarahnya.",
|
||||
"custom-field-checkbox": "kotak semak",
|
||||
"custom-field-currency": "Matawang",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Nama Lengkap",
|
||||
"header-logo-title": "Kembali ke laman panel anda",
|
||||
"show-activities": "Tunjukkan Aktiviti",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Buat Panel",
|
||||
"home": "Beranda",
|
||||
"import": "Impor",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Tidak Diketahui)",
|
||||
"Node_version": "Versi Node",
|
||||
"Meteor_version": "Versi Meteor",
|
||||
"MongoDB_version": "Versi MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Mesin penyimpanan MongoDb",
|
||||
"MongoDB_Oplog_enabled": "Oplog MongoDB dihidupkan",
|
||||
"OS_Arch": "Arch OS",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Picu",
|
||||
"r-action": "Tindakan",
|
||||
"r-when-a-card": "Ketika kartu",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Pulihkan",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Aanvaar",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Skep veld",
|
||||
"createCustomFieldPopup-title": "Skep veld",
|
||||
"current": "huidige",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Daar is geen ongedaan maak nie. Dit sal hierdie pasgemaakte veld van alle kaarte verwyder en sy geskiedenis vernietig.",
|
||||
"custom-field-checkbox": "Merkblokkie",
|
||||
"custom-field-currency": "Geldeenheid",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Volle Naam",
|
||||
"header-logo-title": "Gaan terug na jou bordbladsy.",
|
||||
"show-activities": "Wys aktiwiteite",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Skep bord",
|
||||
"home": "Tuis",
|
||||
"import": "Invoer",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Onbekend)",
|
||||
"Node_version": "Node weergawe",
|
||||
"Meteor_version": "Meteoor weergawe",
|
||||
"MongoDB_version": "MongoDB weergawe",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB stoor enjin",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog geaktiveer",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Sneller",
|
||||
"r-action": "Aksie",
|
||||
"r-when-a-card": "Wanneer 'n kaart",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Herstel",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "SVE-kerns",
|
||||
"cpu-usage": "SVE Gebruik",
|
||||
"current-action": "Huidige Aksie",
|
||||
"database-migration": "Databasismigrasie",
|
||||
"database-migration-description": "Opdatering van databasisstruktuur vir verbeterde funksionaliteit en werkverrigting. Hierdie proses kan 'n paar minute neem.",
|
||||
"database-migrations": "Databasismigrasies",
|
||||
"days-old": "Dae oud",
|
||||
"duration": "Duur",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Aanvaar",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Skep veld",
|
||||
"createCustomFieldPopup-title": "Skep veld",
|
||||
"current": "huidige",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Daar is geen ongedaan maak nie. Dit sal hierdie pasgemaakte veld van alle kaarte verwyder en sy geskiedenis vernietig.",
|
||||
"custom-field-checkbox": "Merkblokkie",
|
||||
"custom-field-currency": "Geldeenheid",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Volle Naam",
|
||||
"header-logo-title": "Gaan terug na jou bordbladsy.",
|
||||
"show-activities": "Wys aktiwiteite",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Skep bord",
|
||||
"home": "Tuis",
|
||||
"import": "Invoer",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Onbekend)",
|
||||
"Node_version": "Node weergawe",
|
||||
"Meteor_version": "Meteoor weergawe",
|
||||
"MongoDB_version": "MongoDB weergawe",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB stoor enjin",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog geaktiveer",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Sneller",
|
||||
"r-action": "Aksie",
|
||||
"r-when-a-card": "Wanneer 'n kaart",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Herstel",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "SVE-kerns",
|
||||
"cpu-usage": "SVE Gebruik",
|
||||
"current-action": "Huidige Aksie",
|
||||
"database-migration": "Databasismigrasie",
|
||||
"database-migration-description": "Opdatering van databasisstruktuur vir verbeterde funksionaliteit en werkverrigting. Hierdie proses kan 'n paar minute neem.",
|
||||
"database-migrations": "Databasismigrasies",
|
||||
"days-old": "Dae oud",
|
||||
"duration": "Duur",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "قبول",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "انشاء حقل",
|
||||
"createCustomFieldPopup-title": "انشاء حقل",
|
||||
"current": "الحالي",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "این اقدام فیلد سفارشی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد",
|
||||
"custom-field-checkbox": "جعبه انتخابی",
|
||||
"custom-field-currency": "عملة",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "الإسم الكامل",
|
||||
"header-logo-title": "الرجوع إلى صفحة اللوحات",
|
||||
"show-activities": "عرض الأنشطة",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "إنشاء لوحة",
|
||||
"home": "الرئيسية",
|
||||
"import": "وارد کردن",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(غير معروف)",
|
||||
"Node_version": "إصدار النود",
|
||||
"Meteor_version": "نسخه متئور",
|
||||
"MongoDB_version": "ورژن MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "موتور ذخیره سازی MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog فعال است",
|
||||
"OS_Arch": "معمارية نظام التشغيل",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "تریگر",
|
||||
"r-action": "عملیات",
|
||||
"r-when-a-card": "زمانی که کارت",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "استعادة",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "Cœurs du CPU ",
|
||||
"cpu-usage": "Utilisation du CPU",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Migration base de données",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Migrations base de données",
|
||||
"days-old": "Jours d'ancienneté",
|
||||
"duration": "Durée",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "قبول",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "انشاء حقل",
|
||||
"createCustomFieldPopup-title": "انشاء حقل",
|
||||
"current": "الحالي",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "این اقدام فیلد سفارشی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد",
|
||||
"custom-field-checkbox": "جعبه انتخابی",
|
||||
"custom-field-currency": "عملة",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "الإسم الكامل",
|
||||
"header-logo-title": "الرجوع إلى صفحة اللوحات",
|
||||
"show-activities": "عرض الأنشطة",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "إنشاء لوحة",
|
||||
"home": "الرئيسية",
|
||||
"import": "وارد کردن",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(غير معروف)",
|
||||
"Node_version": "إصدار النود",
|
||||
"Meteor_version": "نسخه متئور",
|
||||
"MongoDB_version": "ورژن MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "موتور ذخیره سازی MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog فعال است",
|
||||
"OS_Arch": "معمارية نظام التشغيل",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "تریگر",
|
||||
"r-action": "عملیات",
|
||||
"r-when-a-card": "زمانی که کارت",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "استعادة",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "قبول",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "انشاء حقل",
|
||||
"createCustomFieldPopup-title": "انشاء حقل",
|
||||
"current": "الحالي",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "این اقدام فیلد سفارشی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد",
|
||||
"custom-field-checkbox": "جعبه انتخابی",
|
||||
"custom-field-currency": "عملة",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "الإسم الكامل",
|
||||
"header-logo-title": "الرجوع إلى صفحة اللوحات",
|
||||
"show-activities": "عرض الأنشطة",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "إنشاء لوحة",
|
||||
"home": "الرئيسية",
|
||||
"import": "وارد کردن",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(غير معروف)",
|
||||
"Node_version": "إصدار النود",
|
||||
"Meteor_version": "نسخه متئور",
|
||||
"MongoDB_version": "ورژن MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "موتور ذخیره سازی MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog فعال است",
|
||||
"OS_Arch": "معمارية نظام التشغيل",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "تریگر",
|
||||
"r-action": "عملیات",
|
||||
"r-when-a-card": "زمانی که کارت",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "استعادة",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "قبول",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "انشاء حقل",
|
||||
"createCustomFieldPopup-title": "انشاء حقل",
|
||||
"current": "الحالي",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "این اقدام فیلد سفارشی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد",
|
||||
"custom-field-checkbox": "جعبه انتخابی",
|
||||
"custom-field-currency": "عملة",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "الإسم الكامل",
|
||||
"header-logo-title": "الرجوع إلى صفحة اللوحات",
|
||||
"show-activities": "عرض الأنشطة",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "إنشاء لوحة",
|
||||
"home": "الرئيسية",
|
||||
"import": "وارد کردن",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(غير معروف)",
|
||||
"Node_version": "إصدار النود",
|
||||
"Meteor_version": "نسخه متئور",
|
||||
"MongoDB_version": "ورژن MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "موتور ذخیره سازی MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog فعال است",
|
||||
"OS_Arch": "معمارية نظام التشغيل",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "تریگر",
|
||||
"r-action": "عملیات",
|
||||
"r-when-a-card": "زمانی که کارت",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "استعادة",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Aceptar",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Crear un campo",
|
||||
"createCustomFieldPopup-title": "Crear un campo",
|
||||
"current": "actual",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Se eliminará este campo personalizado de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.",
|
||||
"custom-field-checkbox": "Casilla de verificación",
|
||||
"custom-field-currency": "Moneda",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Nombre completo",
|
||||
"header-logo-title": "Volver a tu página de tableros",
|
||||
"show-activities": "Mostrar actividades",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Crear tablero",
|
||||
"home": "Inicio",
|
||||
"import": "Importar",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Desconocido)",
|
||||
"Node_version": "Versión de Node",
|
||||
"Meteor_version": "Versión de Meteor",
|
||||
"MongoDB_version": "Versión de MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Motor de almacenamiento de MongoDB",
|
||||
"MongoDB_Oplog_enabled": "Oplog de MongoDB habilitado",
|
||||
"OS_Arch": "Arquitectura del sistema",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "disparador",
|
||||
"r-action": "acción",
|
||||
"r-when-a-card": "Cuando una tarjeta",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restaurar",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duración",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Qəbul edin",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Sahə yaradın",
|
||||
"createCustomFieldPopup-title": "Sahə yaradın",
|
||||
"current": "cari",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Geri qaytarma yoxdur. Bu, bu fərdi sahəni bütün kartlardan siləcək və onun tarixçəsini məhv edəcək.",
|
||||
"custom-field-checkbox": "Yoxlama qutusu",
|
||||
"custom-field-currency": "Valyuta",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Tam adı",
|
||||
"header-logo-title": "Lövhələr səhifəsinə qayıdın.",
|
||||
"show-activities": "Fəaliyyətləri göstərin",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Şura yaradın",
|
||||
"home": "Ev",
|
||||
"import": "İdxal",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Naməlum)",
|
||||
"Node_version": "Node versiyası",
|
||||
"Meteor_version": "Meteor versiyası",
|
||||
"MongoDB_version": "MongoDB versiyası",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB saxlama mühərriki",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oploq aktivləşdirildi",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Tətik",
|
||||
"r-action": "Fəaliyyət",
|
||||
"r-when-a-card": "Bir kart olanda",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Bərpa et",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU nüvələri",
|
||||
"cpu-usage": "CPU İstifadəsi",
|
||||
"current-action": "Cari Fəaliyyət",
|
||||
"database-migration": "Verilənlər bazasının miqrasiyası",
|
||||
"database-migration-description": "Təkmil funksionallıq və performans üçün verilənlər bazası strukturunun yenilənməsi. Bu proses bir neçə dəqiqə çəkə bilər.",
|
||||
"database-migrations": "Verilənlər Bazasının Miqrasiyaları",
|
||||
"days-old": "Günlər köhnə",
|
||||
"duration": "Müddət",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Qəbul edin",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Sahə yaradın",
|
||||
"createCustomFieldPopup-title": "Sahə yaradın",
|
||||
"current": "cari",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Geri qaytarma yoxdur. Bu, bu fərdi sahəni bütün kartlardan siləcək və onun tarixçəsini məhv edəcək.",
|
||||
"custom-field-checkbox": "Yoxlama qutusu",
|
||||
"custom-field-currency": "Valyuta",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Tam adı",
|
||||
"header-logo-title": "Lövhələr səhifəsinə qayıdın.",
|
||||
"show-activities": "Fəaliyyətləri göstərin",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Şura yaradın",
|
||||
"home": "Ev",
|
||||
"import": "İdxal",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Naməlum)",
|
||||
"Node_version": "Node versiyası",
|
||||
"Meteor_version": "Meteor versiyası",
|
||||
"MongoDB_version": "MongoDB versiyası",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB saxlama mühərriki",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oploq aktivləşdirildi",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Tətik",
|
||||
"r-action": "Fəaliyyət",
|
||||
"r-when-a-card": "Bir kart olanda",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Bərpa et",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU nüvələri",
|
||||
"cpu-usage": "CPU İstifadəsi",
|
||||
"current-action": "Cari Fəaliyyət",
|
||||
"database-migration": "Verilənlər bazasının miqrasiyası",
|
||||
"database-migration-description": "Təkmil funksionallıq və performans üçün verilənlər bazası strukturunun yenilənməsi. Bu proses bir neçə dəqiqə çəkə bilər.",
|
||||
"database-migrations": "Verilənlər Bazasının Miqrasiyaları",
|
||||
"days-old": "Günlər köhnə",
|
||||
"duration": "Müddət",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Qəbul edin",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Sahə yaradın",
|
||||
"createCustomFieldPopup-title": "Sahə yaradın",
|
||||
"current": "cari",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Geri qaytarma yoxdur. Bu, bu fərdi sahəni bütün kartlardan siləcək və onun tarixçəsini məhv edəcək.",
|
||||
"custom-field-checkbox": "Yoxlama qutusu",
|
||||
"custom-field-currency": "Valyuta",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Tam adı",
|
||||
"header-logo-title": "Lövhələr səhifəsinə qayıdın.",
|
||||
"show-activities": "Fəaliyyətləri göstərin",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Şura yaradın",
|
||||
"home": "Ev",
|
||||
"import": "İdxal",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Naməlum)",
|
||||
"Node_version": "Node versiyası",
|
||||
"Meteor_version": "Meteor versiyası",
|
||||
"MongoDB_version": "MongoDB versiyası",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB saxlama mühərriki",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oploq aktivləşdirildi",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Tətik",
|
||||
"r-action": "Fəaliyyət",
|
||||
"r-when-a-card": "Bir kart olanda",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Bərpa et",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU nüvələri",
|
||||
"cpu-usage": "CPU İstifadəsi",
|
||||
"current-action": "Cari Fəaliyyət",
|
||||
"database-migration": "Verilənlər bazasının miqrasiyası",
|
||||
"database-migration-description": "Təkmil funksionallıq və performans üçün verilənlər bazası strukturunun yenilənməsi. Bu proses bir neçə dəqiqə çəkə bilər.",
|
||||
"database-migrations": "Verilənlər Bazasının Miqrasiyaları",
|
||||
"days-old": "Günlər köhnə",
|
||||
"duration": "Müddət",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Accept",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Create Field",
|
||||
"createCustomFieldPopup-title": "Create Field",
|
||||
"current": "current",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
|
||||
"custom-field-checkbox": "Checkbox",
|
||||
"custom-field-currency": "Currency",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Full Name",
|
||||
"header-logo-title": "Go back to your boards page.",
|
||||
"show-activities": "Show Activities",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Create Board",
|
||||
"home": "Home",
|
||||
"import": "Import",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Unknown)",
|
||||
"Node_version": "Node version",
|
||||
"Meteor_version": "Meteor version",
|
||||
"MongoDB_version": "MongoDB version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB storage engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Trigger",
|
||||
"r-action": "Action",
|
||||
"r-when-a-card": "When a card",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restore",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Приемам",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Създай Поле",
|
||||
"createCustomFieldPopup-title": "Създай Поле",
|
||||
"current": "сегашен",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Опозив ове радње неће бити могућ. Радњом се уклања ова придружена рубрика из свих предмета и уништива њена претходна историја употребе.",
|
||||
"custom-field-checkbox": "Чекбокс",
|
||||
"custom-field-currency": "Валута",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Име",
|
||||
"header-logo-title": "Назад към страницата с Вашите табла.",
|
||||
"show-activities": "Писани записник",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Създай Табло",
|
||||
"home": "Начало",
|
||||
"import": "Импорт",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Непознат)",
|
||||
"Node_version": "Версия на Node",
|
||||
"Meteor_version": "Версия на Meteor",
|
||||
"MongoDB_version": "Версия на MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB енджин за данни",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog включен",
|
||||
"OS_Arch": "Архитектура на ОС",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Добави спусък",
|
||||
"r-action": "Действие",
|
||||
"r-when-a-card": "Когато карта",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Възстанови",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "Процесорска језгра",
|
||||
"cpu-usage": "Заузеће процесора",
|
||||
"current-action": "Текућа радња",
|
||||
"database-migration": "Обнова базе података",
|
||||
"database-migration-description": "Преслажем структуру базе података да би била боља и бржа. Процес може потрајати неколико минута.",
|
||||
"database-migrations": "Преслагање базе података",
|
||||
"days-old": "дана стар",
|
||||
"duration": "Трајање",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Asantiñ",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Créer un champ personnalisé",
|
||||
"createCustomFieldPopup-title": "Créer un champ personnalisé",
|
||||
"current": "actuel",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Cette action est irréversible. Elle supprimera ce champ personnalisé de toutes les cartes et détruira son historique.",
|
||||
"custom-field-checkbox": "Case à cocher",
|
||||
"custom-field-currency": "Devise",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Nom complet",
|
||||
"header-logo-title": "Retourner à la page des tableaux",
|
||||
"show-activities": "Afficher les activités",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Créer un tableau",
|
||||
"home": "Accueil",
|
||||
"import": "Importer",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Inconnu)",
|
||||
"Node_version": "Version de Node",
|
||||
"Meteor_version": "Version de Meteor",
|
||||
"MongoDB_version": "Version de MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Moteur de stockage MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog activé",
|
||||
"OS_Arch": "OS Architecture",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Déclencheur",
|
||||
"r-action": "Action",
|
||||
"r-when-a-card": "Quand une carte",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restaurer",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "Cœurs du CPU ",
|
||||
"cpu-usage": "Utilisation du CPU",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Migration base de données",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Migrations base de données",
|
||||
"days-old": "Jours d'ancienneté",
|
||||
"duration": "Durée",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Accepta",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Crear campament",
|
||||
"createCustomFieldPopup-title": "Crear campament",
|
||||
"current": "Real",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "No hi ha cap desfer. Això eliminarà aquest camp personalitzat de totes les cartes i destruirà el seu historial.",
|
||||
"custom-field-checkbox": "casella de selecció",
|
||||
"custom-field-currency": "Moneda",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Nom complet",
|
||||
"header-logo-title": "Torna a la teva pàgina de taulers",
|
||||
"show-activities": "Show Activities",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Crea tauler",
|
||||
"home": "Inici",
|
||||
"import": "importa",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "Importa tauler des de Wekan",
|
||||
"Node_version": "Versió Node",
|
||||
"Meteor_version": "Versió Meteor",
|
||||
"MongoDB_version": "Versió MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Motor d'emmagatzematge MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog habilitat",
|
||||
"OS_Arch": "Arquitectura SO",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Disparador",
|
||||
"r-action": "Acció",
|
||||
"r-when-a-card": "Quan una fitxa",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restaura",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Acceptar",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Crear campament",
|
||||
"createCustomFieldPopup-title": "Crear campament",
|
||||
"current": "Real",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "No hi ha cap desfer. Això eliminarà aquest camp personalitzat de totes les cartes i destruirà el seu historial.",
|
||||
"custom-field-checkbox": "casella de selecció",
|
||||
"custom-field-currency": "Moneda",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Nom complet",
|
||||
"header-logo-title": "Torna a la teva pàgina de taulers",
|
||||
"show-activities": "Mostrar actividades",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Crea tauler",
|
||||
"home": "Inici",
|
||||
"import": "importa",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "Importa tauler des de Wekan",
|
||||
"Node_version": "Versió Node",
|
||||
"Meteor_version": "Versió Meteor",
|
||||
"MongoDB_version": "Versió MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Motor d'emmagatzematge MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog habilitat",
|
||||
"OS_Arch": "Arquitectura SO",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Disparador",
|
||||
"r-action": "Acció",
|
||||
"r-when-a-card": "Quan una fitxa",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restaura",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duración",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Accepta",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Crear campament",
|
||||
"createCustomFieldPopup-title": "Crear campament",
|
||||
"current": "Real",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "No hi ha cap desfer. Això eliminarà aquest camp personalitzat de totes les cartes i destruirà el seu historial.",
|
||||
"custom-field-checkbox": "casella de selecció",
|
||||
"custom-field-currency": "Moneda",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Nom complet",
|
||||
"header-logo-title": "Torna a la teva pàgina de taulers",
|
||||
"show-activities": "Show Activities",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Crea tauler",
|
||||
"home": "Inici",
|
||||
"import": "importa",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "Importa tauler des de Wekan",
|
||||
"Node_version": "Versió Node",
|
||||
"Meteor_version": "Versió Meteor",
|
||||
"MongoDB_version": "Versió MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Motor d'emmagatzematge MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog habilitat",
|
||||
"OS_Arch": "Arquitectura SO",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Disparador",
|
||||
"r-action": "Acció",
|
||||
"r-when-a-card": "Quan una fitxa",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restaura",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "接受",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "创建字段",
|
||||
"createCustomFieldPopup-title": "创建字段",
|
||||
"current": "当前的",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "无法撤消。这将从所有卡中删除此自定义字段并销毁其历史记录。",
|
||||
"custom-field-checkbox": "复选框",
|
||||
"custom-field-currency": "货币",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "姓名",
|
||||
"header-logo-title": "返回您的图板页面。",
|
||||
"show-activities": "展会活动",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "创建板",
|
||||
"home": "家",
|
||||
"import": "进口",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(未知)",
|
||||
"Node_version": "节点版本",
|
||||
"Meteor_version": "流星版",
|
||||
"MongoDB_version": "MongoDB版本",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB 存储引擎",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog 已启用",
|
||||
"OS_Arch": "操作系统架构",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "扳机",
|
||||
"r-action": "行动",
|
||||
"r-when-a-card": "当一张卡",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "恢复",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU核心",
|
||||
"cpu-usage": "中央处理器使用率",
|
||||
"current-action": "当前行动",
|
||||
"database-migration": "数据库迁移",
|
||||
"database-migration-description": "更新数据库结构以改进功能和性能。此过程可能需要几分钟。",
|
||||
"database-migrations": "数据库迁移",
|
||||
"days-old": "岁月如梭",
|
||||
"duration": "期间",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Přijmout",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Vytvořit pole",
|
||||
"createCustomFieldPopup-title": "Vytvořit pole",
|
||||
"current": "Aktuální",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Nelze vrátit zpět. Toto odebere toto vlastní pole ze všech karet a zničí jeho historii.",
|
||||
"custom-field-checkbox": "Checkbox",
|
||||
"custom-field-currency": "Měna",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Celé jméno",
|
||||
"header-logo-title": "Jit zpět na stránku s tably.",
|
||||
"show-activities": "Show Activities",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Vytvořit tablo",
|
||||
"home": "Domů",
|
||||
"import": "Import",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Neznámé)",
|
||||
"Node_version": "Node verze",
|
||||
"Meteor_version": "Verze Meteor",
|
||||
"MongoDB_version": "MongoDB verze",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB storage engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog povolen",
|
||||
"OS_Arch": "OS Architektura",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Spouštěč",
|
||||
"r-action": "Akce",
|
||||
"r-when-a-card": "Pokud karta",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Obnovit",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Přijmout",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Vytvořit pole",
|
||||
"createCustomFieldPopup-title": "Vytvořit pole",
|
||||
"current": "Aktuální",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Nelze vrátit zpět. Toto odebere toto vlastní pole ze všech karet a zničí jeho historii.",
|
||||
"custom-field-checkbox": "Checkbox",
|
||||
"custom-field-currency": "Měna",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Celé jméno",
|
||||
"header-logo-title": "Jit zpět na stránku s tably.",
|
||||
"show-activities": "Show Activities",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Vytvořit tablo",
|
||||
"home": "Domů",
|
||||
"import": "Import",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Neznámé)",
|
||||
"Node_version": "Node verze",
|
||||
"Meteor_version": "Verze Meteor",
|
||||
"MongoDB_version": "MongoDB verze",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB storage engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog povolen",
|
||||
"OS_Arch": "OS Architektura",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Spouštěč",
|
||||
"r-action": "Akce",
|
||||
"r-when-a-card": "Pokud karta",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Obnovit",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Derbyn",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Creu Maes",
|
||||
"createCustomFieldPopup-title": "Creu Maes",
|
||||
"current": "presennol",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Does dim dadwneud. Bydd hyn yn dileu'r maes arfer hwn o bob cerdyn ac yn dinistrio ei hanes.",
|
||||
"custom-field-checkbox": "Blwch ticio",
|
||||
"custom-field-currency": "Arian cyfred",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Enw Llawn",
|
||||
"header-logo-title": "Ewch yn ôl i dudalen eich byrddau.",
|
||||
"show-activities": "Gweithgareddau Sioe",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Creu Bwrdd",
|
||||
"home": "Cartref",
|
||||
"import": "Mewnforio",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Anhysbys)",
|
||||
"Node_version": "Fersiwn nod",
|
||||
"Meteor_version": "Fersiwn Meteor",
|
||||
"MongoDB_version": "Fersiwn MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Peiriant storio MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog wedi'i alluogi",
|
||||
"OS_Arch": "AO Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Sbardun",
|
||||
"r-action": "Gweithred",
|
||||
"r-when-a-card": "Pan fydd cerdyn",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Adfer",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "Craidd CPU",
|
||||
"cpu-usage": "Defnydd CPU",
|
||||
"current-action": "Gweithredu Presennol",
|
||||
"database-migration": "Mudo Cronfa Ddata",
|
||||
"database-migration-description": "Diweddaru strwythur cronfa ddata i wella ymarferoldeb a pherfformiad. Gall y broses hon gymryd sawl munud.",
|
||||
"database-migrations": "Mudo Cronfa Ddata",
|
||||
"days-old": "Dyddiau Hen",
|
||||
"duration": "Hyd",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Derbyn",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Creu Maes",
|
||||
"createCustomFieldPopup-title": "Creu Maes",
|
||||
"current": "presennol",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Does dim dadwneud. Bydd hyn yn dileu'r maes arfer hwn o bob cerdyn ac yn dinistrio ei hanes.",
|
||||
"custom-field-checkbox": "Blwch ticio",
|
||||
"custom-field-currency": "Arian cyfred",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Enw Llawn",
|
||||
"header-logo-title": "Ewch yn ôl i dudalen eich byrddau.",
|
||||
"show-activities": "Gweithgareddau Sioe",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Creu Bwrdd",
|
||||
"home": "Cartref",
|
||||
"import": "Mewnforio",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Anhysbys)",
|
||||
"Node_version": "Fersiwn nod",
|
||||
"Meteor_version": "Fersiwn Meteor",
|
||||
"MongoDB_version": "Fersiwn MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Peiriant storio MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog wedi'i alluogi",
|
||||
"OS_Arch": "AO Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Sbardun",
|
||||
"r-action": "Gweithred",
|
||||
"r-when-a-card": "Pan fydd cerdyn",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Adfer",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "Craidd CPU",
|
||||
"cpu-usage": "Defnydd CPU",
|
||||
"current-action": "Gweithredu Presennol",
|
||||
"database-migration": "Mudo Cronfa Ddata",
|
||||
"database-migration-description": "Diweddaru strwythur cronfa ddata i wella ymarferoldeb a pherfformiad. Gall y broses hon gymryd sawl munud.",
|
||||
"database-migrations": "Mudo Cronfa Ddata",
|
||||
"days-old": "Dyddiau Hen",
|
||||
"duration": "Hyd",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Accepter",
|
||||
"activity-changedTitle": "ændrede titel til %s af %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "slettede kortet __card__ fra listen __list__ i svømmebanen __swimlane__ på tavlen __board__",
|
||||
"act-removeBoard": "slettede tavlen __board__",
|
||||
"act-removeList": "slettede listen __list__ fra tavlen __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Opret felt",
|
||||
"createCustomFieldPopup-title": "Opret felt",
|
||||
"current": "nuværende",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Du kan ikke fortryde handlingen. Dette vil fjerne dette brugerdefinerede felt fra alle kort og tilintetgøre dens historik.",
|
||||
"custom-field-checkbox": "Afkrydsningsfelt",
|
||||
"custom-field-currency": "Valuta",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Fuldt navn",
|
||||
"header-logo-title": "Gå tilbage til siden med dine tavler",
|
||||
"show-activities": "Visa aktiviteter",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Opret tavle",
|
||||
"home": "Hjem",
|
||||
"import": "Importér",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Ukendt)",
|
||||
"Node_version": "Node-version",
|
||||
"Meteor_version": "Meteor-version",
|
||||
"MongoDB_version": "MongoDB-version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Lagringsmotor for MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog er slået til",
|
||||
"OS_Arch": "OS - Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Træk en trigger og en handling ind i byggeren, og tilføj derefter reglen. Træk en handling over på en eksisterende regel for at ændre, hvad den gør.",
|
||||
"r-drop-trigger": "træk en udløser hertil",
|
||||
"r-drop-action": "træk en handling hertil",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Eksporter regler til JSON",
|
||||
"r-export-csv": "Eksporter regler til CSV",
|
||||
"r-import-json": "Importer regler fra JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Markér kort som ikke fuldført",
|
||||
"r-move-all-cards": "Flyt alle kort i en liste",
|
||||
"r-set-date-relative": "Angiv dato i forhold til dags dato",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Udløser",
|
||||
"r-action": "Handling",
|
||||
"r-when-a-card": "Når et kort",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Blobcontainer",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Genskab",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Projekt ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Sti til servicekontoens nøglefil",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU-kärnor",
|
||||
"cpu-usage": "CPU-användning",
|
||||
"current-action": "Nuvarande åtgärd",
|
||||
"database-migration": "Databasmigrering",
|
||||
"database-migration-description": "Uppdaterar databasstrukturen för förbättrad funktionalitet och prestanda. Denna process kan ta flera minuter.",
|
||||
"database-migrations": "Databasmigreringar",
|
||||
"days-old": "Dagar gammal",
|
||||
"duration": "Varaktighet",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Akzeptieren",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Feld erstellen",
|
||||
"createCustomFieldPopup-title": "Feld erstellen",
|
||||
"current": "aktuell",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.",
|
||||
"custom-field-checkbox": "Kontrollkästchen",
|
||||
"custom-field-currency": "Währung",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Vollständiger Name",
|
||||
"header-logo-title": "Zurück zur Board Seite.",
|
||||
"show-activities": "Aktivitäten anzeigen",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Board erstellen",
|
||||
"home": "Home",
|
||||
"import": "Importieren",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Unbekannt)",
|
||||
"Node_version": "Node-Version",
|
||||
"Meteor_version": "Meteor-Version",
|
||||
"MongoDB_version": "MongoDB-Version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB-Speicher-Engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB-Oplog aktiviert",
|
||||
"OS_Arch": "Betriebssystem-Architektur",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Auslöser",
|
||||
"r-action": "Aktion",
|
||||
"r-when-a-card": "Wenn Karte",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Wiederherstellen",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Laufender Vorgang",
|
||||
"database-migration": "Datenbankmigration",
|
||||
"database-migration-description": "Update der Datenbankstruktur für eine Verbesserung der Funktionalität und der Performanz. Das kann ein paar Minuten dauern.",
|
||||
"database-migrations": "Datenbankmigration",
|
||||
"days-old": "Tage alt",
|
||||
"duration": "Dauer",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Akzeptieren",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Feld erstellen",
|
||||
"createCustomFieldPopup-title": "Feld erstellen",
|
||||
"current": "aktuell",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.",
|
||||
"custom-field-checkbox": "Kontrollkästchen",
|
||||
"custom-field-currency": "Währung",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Vollständiger Name",
|
||||
"header-logo-title": "Zurück zur Board Seite.",
|
||||
"show-activities": "Aktivitäten anzeigen",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Board erstellen",
|
||||
"home": "Home",
|
||||
"import": "Importieren",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Unbekannt)",
|
||||
"Node_version": "Node-Version",
|
||||
"Meteor_version": "Meteor-Version",
|
||||
"MongoDB_version": "MongoDB-Version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB-Speicher-Engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB-Oplog aktiviert",
|
||||
"OS_Arch": "Betriebssystem-Architektur",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Auslöser",
|
||||
"r-action": "Aktion",
|
||||
"r-when-a-card": "Wenn Karte",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Wiederherstellen",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Laufender Vorgang",
|
||||
"database-migration": "Datenbankmigration",
|
||||
"database-migration-description": "Update der Datenbankstruktur für eine Verbesserung der Funktionalität und der Performanz. Das kann ein paar Minuten dauern.",
|
||||
"database-migrations": "Datenbankmigration",
|
||||
"days-old": "Tage alt",
|
||||
"duration": "Dauer",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Akzeptieren",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Feld erstellen",
|
||||
"createCustomFieldPopup-title": "Feld erstellen",
|
||||
"current": "aktuell",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.",
|
||||
"custom-field-checkbox": "Kontrollkästchen",
|
||||
"custom-field-currency": "Währung",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Vollständiger Name",
|
||||
"header-logo-title": "Zurück zur Board Seite.",
|
||||
"show-activities": "Aktivitäten anzeigen",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Board erstellen",
|
||||
"home": "Home",
|
||||
"import": "Importieren",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Unbekannt)",
|
||||
"Node_version": "Node-Version",
|
||||
"Meteor_version": "Meteor-Version",
|
||||
"MongoDB_version": "MongoDB-Version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB-Speicher-Engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB-Oplog aktiviert",
|
||||
"OS_Arch": "Betriebssystem-Architektur",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Auslöser",
|
||||
"r-action": "Aktion",
|
||||
"r-when-a-card": "Wenn Karte",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Wiederherstellen",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Laufender Vorgang",
|
||||
"database-migration": "Datenbankmigration",
|
||||
"database-migration-description": "Update der Datenbankstruktur für eine Verbesserung der Funktionalität und der Performanz. Das kann ein paar Minuten dauern.",
|
||||
"database-migrations": "Datenbankmigration",
|
||||
"days-old": "Tage alt",
|
||||
"duration": "Dauer",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Akzeptieren",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Feld erstellen",
|
||||
"createCustomFieldPopup-title": "Feld erstellen",
|
||||
"current": "aktuell",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.",
|
||||
"custom-field-checkbox": "Kontrollkästchen",
|
||||
"custom-field-currency": "Währung",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Vollständiger Name",
|
||||
"header-logo-title": "Zurück zur Board Seite.",
|
||||
"show-activities": "Aktivitäten anzeigen",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Board erstellen",
|
||||
"home": "Home",
|
||||
"import": "Importieren",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Unbekannt)",
|
||||
"Node_version": "Node-Version",
|
||||
"Meteor_version": "Meteor-Version",
|
||||
"MongoDB_version": "MongoDB-Version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB-Speicher-Engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB-Oplog aktiviert",
|
||||
"OS_Arch": "Betriebssystem-Architektur",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Auslöser",
|
||||
"r-action": "Aktion",
|
||||
"r-when-a-card": "Wenn Karte",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Wiederherstellen",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Laufender Vorgang",
|
||||
"database-migration": "Datenbankmigration",
|
||||
"database-migration-description": "Update der Datenbankstruktur für eine Verbesserung der Funktionalität und der Performanz. Das kann ein paar Minuten dauern.",
|
||||
"database-migrations": "Datenbankmigration",
|
||||
"days-old": "Tage alt",
|
||||
"duration": "Dauer",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Αποδοχή",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Δημιουργία Πεδίου",
|
||||
"createCustomFieldPopup-title": "Δημιουργία Πεδίου",
|
||||
"current": "τρέχων",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Non potrai tornare indietro. Questa azione rimuoverà questo campo personalizzato da tutte le schede ed eliminerà ogni sua traccia.",
|
||||
"custom-field-checkbox": "Casella di scelta",
|
||||
"custom-field-currency": "Νόμισμα",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Πλήρες Όνομα",
|
||||
"header-logo-title": "Επιστροφή στη σελίδα των πινάκων σας.",
|
||||
"show-activities": "Mostra Attività",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Δημιουργία Πίνακα",
|
||||
"home": "Αρχική",
|
||||
"import": "Εισαγωγή",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Άγνωστο)",
|
||||
"Node_version": "Έκδοση Node",
|
||||
"Meteor_version": "Έκδοση Meteor",
|
||||
"MongoDB_version": "Έκδοση MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Storage engine di MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog ενεργοποιημένο",
|
||||
"OS_Arch": "Architettura SO",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Déclencheur",
|
||||
"r-action": "Ενέργεια",
|
||||
"r-when-a-card": "Όταν μία κάρτα",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Επαναφορά",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "Cœurs du CPU ",
|
||||
"cpu-usage": "Utilisation du CPU",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Migration base de données",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Migrations base de données",
|
||||
"days-old": "Jours d'ancienneté",
|
||||
"duration": "Durée",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Αποδοχή",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Δημιουργία Πεδίου",
|
||||
"createCustomFieldPopup-title": "Δημιουργία Πεδίου",
|
||||
"current": "τρέχων",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "Non potrai tornare indietro. Questa azione rimuoverà questo campo personalizzato da tutte le schede ed eliminerà ogni sua traccia.",
|
||||
"custom-field-checkbox": "Casella di scelta",
|
||||
"custom-field-currency": "Νόμισμα",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Πλήρες Όνομα",
|
||||
"header-logo-title": "Επιστροφή στη σελίδα των πινάκων σας.",
|
||||
"show-activities": "Mostra Attività",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Δημιουργία Πίνακα",
|
||||
"home": "Αρχική",
|
||||
"import": "Εισαγωγή",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Άγνωστο)",
|
||||
"Node_version": "Έκδοση Node",
|
||||
"Meteor_version": "Έκδοση Meteor",
|
||||
"MongoDB_version": "Έκδοση MongoDB",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "Storage engine di MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog ενεργοποιημένο",
|
||||
"OS_Arch": "Architettura SO",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Déclencheur",
|
||||
"r-action": "Ενέργεια",
|
||||
"r-when-a-card": "Όταν μία κάρτα",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Επαναφορά",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "Cœurs du CPU ",
|
||||
"cpu-usage": "Utilisation du CPU",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Migration base de données",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Migrations base de données",
|
||||
"days-old": "Jours d'ancienneté",
|
||||
"duration": "Durée",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Accept",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Create Field",
|
||||
"createCustomFieldPopup-title": "Create Field",
|
||||
"current": "current",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
|
||||
"custom-field-checkbox": "Checkbox",
|
||||
"custom-field-currency": "Currency",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Full Name",
|
||||
"header-logo-title": "Go back to your boards page.",
|
||||
"show-activities": "Show Activities",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Create Board",
|
||||
"home": "Home",
|
||||
"import": "Import",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Unknown)",
|
||||
"Node_version": "Node version",
|
||||
"Meteor_version": "Meteor version",
|
||||
"MongoDB_version": "MongoDB version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB storage engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Trigger",
|
||||
"r-action": "Action",
|
||||
"r-when-a-card": "When a card",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restore",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Accept",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Create Field",
|
||||
"createCustomFieldPopup-title": "Create Field",
|
||||
"current": "current",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
|
||||
"custom-field-checkbox": "Checkbox",
|
||||
"custom-field-currency": "Currency",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Full Name",
|
||||
"header-logo-title": "Go back to your boards page.",
|
||||
"show-activities": "Show Activities",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Create Board",
|
||||
"home": "Home",
|
||||
"import": "Import",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Unknown)",
|
||||
"Node_version": "Node version",
|
||||
"Meteor_version": "Meteor version",
|
||||
"MongoDB_version": "MongoDB version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB storage engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Trigger",
|
||||
"r-action": "Action",
|
||||
"r-when-a-card": "When a card",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restore",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Accept",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Create Field",
|
||||
"createCustomFieldPopup-title": "Create Field",
|
||||
"current": "current",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
|
||||
"custom-field-checkbox": "Checkbox",
|
||||
"custom-field-currency": "Currency",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Full Name",
|
||||
"header-logo-title": "Go back to your boards page.",
|
||||
"show-activities": "Show Activities",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Create Board",
|
||||
"home": "Home",
|
||||
"import": "Import",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Unknown)",
|
||||
"Node_version": "Node version",
|
||||
"Meteor_version": "Meteor version",
|
||||
"MongoDB_version": "MongoDB version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB storage engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Trigger",
|
||||
"r-action": "Action",
|
||||
"r-when-a-card": "When a card",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restore",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Accept",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Create Field",
|
||||
"createCustomFieldPopup-title": "Create Field",
|
||||
"current": "current",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
|
||||
"custom-field-checkbox": "Checkbox",
|
||||
"custom-field-currency": "Currency",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Full Name",
|
||||
"header-logo-title": "Go back to your boards page.",
|
||||
"show-activities": "Show Activities",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Create Board",
|
||||
"home": "Home",
|
||||
"import": "Import",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Unknown)",
|
||||
"Node_version": "Node version",
|
||||
"Meteor_version": "Meteor version",
|
||||
"MongoDB_version": "MongoDB version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB storage engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Trigger",
|
||||
"r-action": "Action",
|
||||
"r-when-a-card": "When a card",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restore",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accept": "Accept",
|
||||
"activity-changedTitle": "changed title to %s of %s",
|
||||
"activity-changedDescription": "changed description of %s",
|
||||
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoard": "deleted board __board__",
|
||||
"act-removeList": "deleted list __list__ at board __board__",
|
||||
@@ -441,6 +442,7 @@
|
||||
"createCustomField": "Create Field",
|
||||
"createCustomFieldPopup-title": "Create Field",
|
||||
"current": "current",
|
||||
"custom-color": "Custom color",
|
||||
"custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
|
||||
"custom-field-checkbox": "Checkbox",
|
||||
"custom-field-currency": "Currency",
|
||||
@@ -582,6 +584,7 @@
|
||||
"fullname": "Full Name",
|
||||
"header-logo-title": "Go back to your boards page.",
|
||||
"show-activities": "Show Activities",
|
||||
"hide-activities": "Hide Activities",
|
||||
"headerBarCreateBoardPopup-title": "Create Board",
|
||||
"home": "Home",
|
||||
"import": "Import",
|
||||
@@ -895,7 +898,12 @@
|
||||
"no-name": "(Unknown)",
|
||||
"Node_version": "Node version",
|
||||
"Meteor_version": "Meteor version",
|
||||
"MongoDB_version": "MongoDB version",
|
||||
"Database_type": "Database type",
|
||||
"Database_commit": "Database commit",
|
||||
"FerretDB_version": "FerretDB version",
|
||||
"FerretDB_commit": "FerretDB commit",
|
||||
"Reactivity_mode": "Reactivity mode (changeStreams / oplog / polling)",
|
||||
"MongoDB_version": "MongoDB compatible version",
|
||||
"MongoDB_storage_engine": "MongoDB storage engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
|
||||
"OS_Arch": "OS Arch",
|
||||
@@ -1016,6 +1024,17 @@
|
||||
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
|
||||
"r-drop-trigger": "drop a trigger here",
|
||||
"r-drop-action": "drop an action here",
|
||||
"r-w-card-created": "Card is created",
|
||||
"r-w-card-archived": "Card is archived",
|
||||
"r-w-card-unarchived": "Card is unarchived",
|
||||
"r-w-label-added": "Any label is added",
|
||||
"r-w-label-removed": "Any label is removed",
|
||||
"r-w-member-added": "A member is added",
|
||||
"r-w-member-removed": "A member is removed",
|
||||
"r-w-checklist-added": "A checklist is added",
|
||||
"r-w-attachment-added": "An attachment is added",
|
||||
"r-w-every-day-at": "Every day at __time__",
|
||||
"r-w-set-received-now": "Set received date to now",
|
||||
"r-export-json": "Export rules to JSON",
|
||||
"r-export-csv": "Export rules to CSV",
|
||||
"r-import-json": "Import rules from JSON",
|
||||
@@ -1069,6 +1088,12 @@
|
||||
"r-mark-incomplete": "Mark card incomplete",
|
||||
"r-move-all-cards": "Move all cards in list",
|
||||
"r-set-date-relative": "Set date relative to now",
|
||||
"r-later": "later",
|
||||
"r-unit-minutes": "minute(s)",
|
||||
"r-unit-hours": "hour(s)",
|
||||
"r-unit-days": "day(s)",
|
||||
"r-unit-weeks": "week(s)",
|
||||
"r-unit-months": "month(s)",
|
||||
"r-trigger": "Trigger",
|
||||
"r-action": "Action",
|
||||
"r-when-a-card": "When a card",
|
||||
@@ -1810,6 +1835,45 @@
|
||||
"azure-container": "Container",
|
||||
"gcs-storage": "Google Cloud Storage",
|
||||
"gcs-storage-description": "Google Cloud Storage.",
|
||||
"database-migration": "Database migration",
|
||||
"database-migration-description": "Migrate text-based data (everything except attachments and avatars, which stay on the filesystem) between MongoDB and FerretDB v1 (SQLite). The other database must be running and reachable (default ports: FerretDB mongodb://127.0.0.1:27018, MongoDB mongodb://127.0.0.1:27019; override with WEKAN_FERRETDB_URL / WEKAN_MONGODB_URL). After it finishes, point MONGO_URL at the other database and restart WeKan (on Snap: snap set wekan database=ferretdb or =mongodb).",
|
||||
"database-migrate-to-ferretdb": "Migrate text data to FerretDB v1 (SQLite)",
|
||||
"database-migrate-to-mongodb": "Migrate text data to MongoDB",
|
||||
"database-migration-confirm": "Copy all text-based collections to __db__? Attachments and avatars on the filesystem are not affected. The target database must be running.",
|
||||
"database-migration-phase": "Phase",
|
||||
"database-migration-done": "Text-data migration completed.",
|
||||
"collections": "collections",
|
||||
"features": "Features",
|
||||
"features-performance": "Performance",
|
||||
"cards-loading": "Card loading",
|
||||
"cards-loading-all": "All cards (default)",
|
||||
"cards-loading-lazy": "Lazy (only visible cards)",
|
||||
"cards-loading-description": "How each board loads its cards. All cards: every card is loaded into the browser (simplest, all features work, but a board with thousands of cards sends a lot of data to each viewer). Lazy: each list loads only the cards currently visible (infinite scroll) plus a live count, so very large boards use less memory and bandwidth. Also set by the CARDS_LOADING environment variable.",
|
||||
"cards-loading-lazy-note": "Lazy card loading is experimental: card counters and WIP limits are accurate, but the Calendar/Table/Gantt views and multi-select reflect only the cards loaded so far. Reload open boards after changing this.",
|
||||
"backup": "Backup",
|
||||
"backup-description": "Back up Attachments, Avatars and/or Data (all text-based collections that are not attachments or avatars). The .zip is streamed directly to the selected storage — no temporary file — as backup/YYYY/MM/DD/HH_MM_SS/backup.zip, containing YYYY_MM_DD-HH_MM_SS/attachments, /avatars and /data. Cloud storages (S3/MinIO, Azure, GCS) use the credentials configured in their storage tabs.",
|
||||
"backup-data": "Data (text)",
|
||||
"backup-storage": "Save to storage",
|
||||
"backup-now": "Backup now",
|
||||
"backup-done": "Backup completed",
|
||||
"backup-schedule": "Scheduled backups",
|
||||
"backup-frequency": "Frequency",
|
||||
"backup-frequency-off": "Off",
|
||||
"backup-frequency-daily": "Daily",
|
||||
"backup-frequency-weekly": "Weekly",
|
||||
"backup-frequency-monthly": "Monthly",
|
||||
"backup-time": "Time (HH:MM)",
|
||||
"backup-day-of-week": "Day of week (weekly)",
|
||||
"backup-day-of-month": "Day of month (monthly, 1-28)",
|
||||
"backup-restore": "Restore",
|
||||
"backup-list": "List backups",
|
||||
"backup-datetime": "Date and time",
|
||||
"backup-path": "File path",
|
||||
"backup-restore-mode": "Restore mode",
|
||||
"backup-restore-add-missing": "Add missing data only",
|
||||
"backup-restore-replace-all": "Replace all data",
|
||||
"backup-restore-select-first": "Select a backup from the list first.",
|
||||
"backup-restore-confirm": "Restore the selected backup? Attachments/avatars are written to the filesystem and data to the database according to the chosen mode.",
|
||||
"gcs-project-id": "Project ID",
|
||||
"gcs-bucket": "Bucket",
|
||||
"gcs-key-filename": "Service account key file path",
|
||||
@@ -1987,8 +2051,6 @@
|
||||
"cpu-cores": "CPU Cores",
|
||||
"cpu-usage": "CPU Usage",
|
||||
"current-action": "Current Action",
|
||||
"database-migration": "Database Migration",
|
||||
"database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.",
|
||||
"database-migrations": "Database Migrations",
|
||||
"days-old": "Days Old",
|
||||
"duration": "Duration",
|
||||
|
||||
Loaded 100 of 339 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user