Compare commits

..
20 Commits
Author SHA1 Message Date
Lauri Ojansivu ef7771febb v8.09 2025-10-21 13:54:37 +03:00
Lauri Ojansivu 12cba0e148 Updated ChangeLog. 2025-10-21 13:36:43 +03:00
Lauri Ojansivu c3a4052227 Fix upgrade to 8.08 duplicates lists.
Thanks to xet7 !

Fixes #5962,
fixes #5952
2025-10-21 13:34:39 +03:00
Lauri Ojansivu 82f048ccef Updated ChangeLog. 2025-10-21 13:26:41 +03:00
Lauri Ojansivu 7a585a3dfb Fix Admin Panel / People editing and layout.
Thanks to xet7 !

Fixes #5961
2025-10-21 13:22:58 +03:00
Lauri Ojansivu 8d3b53f51d v8.08 2025-10-21 11:01:40 +03:00
Lauri Ojansivu d73e006935 Updated ChangeLog. 2025-10-21 10:48:45 +03:00
Lauri Ojansivu 9536e60bd1 Fix opening board migration of Shared Lists to Per-Swimlane lists to use ReactiveCache correctly without errors.
Thanks to xet7 !

Fixes #5960
2025-10-21 10:46:37 +03:00
Lauri Ojansivu 678ca978a3 Merge branch 'main' of github.com:wekan/wekan 2025-10-20 19:13:10 +03:00
Lauri Ojansivu 39420877fd Updated ChangeLog. 2025-10-20 19:12:07 +03:00
Lauri Ojansivu 6ea03cfba3 Revert moving mongodb raw database files.
Thanks to xet7 !
2025-10-20 18:08:52 +03:00
Lauri Ojansivu 9214b56aea v8.07 2025-10-20 17:43:18 +03:00
Lauri Ojansivu 699b4c464f Updated translations. 2025-10-20 17:30:42 +03:00
Lauri Ojansivu 9fa54a3148 Updated ChangeLog. 2025-10-20 17:29:29 +03:00
Lauri Ojansivu f2019b1059 If Snap Candidate MongoDB raw database files were at SNAP_COMMON/wekan, migrate them back to SNAP_COMMON.
Thanks to xet7 !
2025-10-20 17:26:53 +03:00
Lauri Ojansivu 714bbd0fb0 Updated ChangeLog. 2025-10-20 17:10:30 +03:00
Lauri Ojansivu 80777b4663 When opening board, add missing lists.
Thanks to xet7 !

Fixes #5926
2025-10-20 17:06:42 +03:00
Lauri Ojansivu 9473c1fe41 Updated ChangeLog. 2025-10-20 16:50:35 +03:00
Lauri Ojansivu 98f141d62f Fix Snap Candidate WeKan 8.00-8.06 commit ae01ea5 database directory from /var/snap/wekan/common/wekan back to 8.07 /var/snap/wekan/common.
Thanks to xet7 !
2025-10-20 16:42:28 +03:00
Lauri Ojansivu 85dd213b14 Updated translations. 2025-10-20 06:21:02 +03:00
24 changed files with 497 additions and 139 deletions

No files matched your search

+36
View File
@@ -19,6 +19,42 @@ Fixing other platforms In Progress.
[Upgrade WeKan](https://wekan.fi/upgrade/)
WeKan 8.00-8.06 had wrong raw database directory setting /var/snap/wekan/common/wekan and some cards were not visible.
Those are fixed at WeKan 8.07 where database directory is back to /var/snap/wekan/common and all cards are visible.
# v8.09 2025-10-21 WeKan ® release
This release fixes the following bugs:
- [Fix Admin Panel / People editing and layout](https://github.com/wekan/wekan/commit/7a585a3dfb080af51f88669ea5928f715779cee4).
Thanks to xet7.
- [Fix upgrade to 8.08 duplicates lists](https://github.com/wekan/wekan/commit/c3a405222782a4a91eb8725faaa8309f0926dcc4).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.08 2025-10-21 WeKan ® release
This release fixes the following bugs:
- [Fix opening board migration of Shared Lists to Per-Swimlane lists to use ReactiveCache correctly without errors](https://github.com/wekan/wekan/commit/9536e60bd1c77c8a22e89d2eb2968e11da3a28cd).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.07 2025-10-20 WeKan ® release
This release fixes the following bugs:
- [Fix Snap Candidate WeKan 8.00-8.06 commit ae01ea5 database directory from /var/snap/wekan/common/wekan back to 8.07 /var/snap/wekan/common](https://github.com/wekan/wekan/commit/98f141d62f3b6d4371d024c72eae6688d0f4e516).
Thanks to xet7.
- [When opening board, add missing lists](https://github.com/wekan/wekan/commit/80777b46638ed15b8194105751499ada4b066d19).
Thanks to xet7.
- [If Snap Candidate MongoDB raw database files were at SNAP_COMMON/wekan, migrate them back to SNAP_COMMON](https://github.com/wekan/wekan/commit/f2019b1059c8d6f4cd9a46c3db7e004c4928cebb).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.06 2025-10-20 WeKan ® release
This release adds the following new features:
+3 -3
View File
@@ -249,9 +249,9 @@ cd /home/wekan/app
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
#rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy
#mv /home/wekan/app_build/bundle /build
wget "https://github.com/wekan/wekan/releases/download/v8.06/wekan-8.06-amd64.zip"
unzip wekan-8.06-amd64.zip
rm wekan-8.06-amd64.zip
wget "https://github.com/wekan/wekan/releases/download/v8.09/wekan-8.09-amd64.zip"
unzip wekan-8.09-amd64.zip
rm wekan-8.09-amd64.zip
mv /home/wekan/app/bundle /build
# Put back the original tar
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v8.06.0"
appVersion: "v8.09.0"
files:
userUploads:
- README.md
+74 -8
View File
@@ -115,6 +115,9 @@ BlazeComponent.extendComponent({
// Convert shared lists to per-swimlane lists if needed
await this.convertSharedListsToPerSwimlane(boardId);
// Fix missing lists migration (for cards with wrong listId references)
await this.fixMissingLists(boardId);
// Start attachment migration in background if needed
this.startAttachmentMigrationIfNeeded(boardId);
} catch (error) {
@@ -200,7 +203,7 @@ BlazeComponent.extendComponent({
boardId: boardId,
swimlaneId: swimlane._id,
sort: sharedList.sort || 0,
archived: sharedList.archived || false,
archived: sharedList.archived || false, // Preserve archived state from original list
createdAt: new Date(),
modifiedAt: new Date()
};
@@ -212,16 +215,24 @@ BlazeComponent.extendComponent({
if (sharedList.wipLimitSoft) newListData.wipLimitSoft = sharedList.wipLimitSoft;
Lists.insert(newListData);
if (process.env.DEBUG === 'true') {
const archivedStatus = sharedList.archived ? ' (archived)' : ' (active)';
console.log(`Created list "${sharedList.title}"${archivedStatus} for swimlane ${swimlane.title || swimlane._id}`);
}
} else {
if (process.env.DEBUG === 'true') {
console.log(`List "${sharedList.title}" already exists in swimlane ${swimlane.title || swimlane._id}, skipping`);
}
}
}
// Archive or remove the original shared list
Lists.update(sharedList._id, {
$set: {
archived: true,
modifiedAt: new Date()
}
});
// Remove the original shared list completely
Lists.remove(sharedList._id);
if (process.env.DEBUG === 'true') {
console.log(`Removed shared list "${sharedList.title}"`);
}
}
// Mark board as processed
@@ -236,6 +247,61 @@ BlazeComponent.extendComponent({
}
},
async fixMissingLists(boardId) {
try {
const board = ReactiveCache.getBoard(boardId);
if (!board) return;
// Check if board has already been processed for missing lists fix
if (board.fixMissingListsCompleted) {
if (process.env.DEBUG === 'true') {
console.log(`Board ${boardId} has already been processed for missing lists fix`);
}
return;
}
// Check if migration is needed
const needsMigration = await new Promise((resolve, reject) => {
Meteor.call('fixMissingListsMigration.needsMigration', boardId, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
if (!needsMigration) {
if (process.env.DEBUG === 'true') {
console.log(`Board ${boardId} does not need missing lists fix`);
}
return;
}
if (process.env.DEBUG === 'true') {
console.log(`Starting fix missing lists migration for board ${boardId}`);
}
// Execute the migration
const result = await new Promise((resolve, reject) => {
Meteor.call('fixMissingListsMigration.execute', boardId, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
if (result && result.success) {
console.log(`Successfully fixed missing lists for board ${boardId}: created ${result.createdLists} lists, updated ${result.updatedCards} cards`);
}
} catch (error) {
console.error('Error fixing missing lists:', error);
}
},
async startAttachmentMigrationIfNeeded(boardId) {
try {
// Check if board has already been migrated
+23
View File
@@ -93,6 +93,29 @@
max-height: inherit;
}
/* Admin edit popups: use full height */
.pop-over[data-popup="editUser"],
.pop-over[data-popup="editOrg"],
.pop-over[data-popup="editTeam"] {
height: calc(100vh - 20px) !important;
max-height: calc(100vh - 20px) !important;
}
.pop-over[data-popup="editUser"] .content-wrapper,
.pop-over[data-popup="editOrg"] .content-wrapper,
.pop-over[data-popup="editTeam"] .content-wrapper {
max-height: calc(100vh - 80px) !important; /* Subtract header height */
height: calc(100vh - 80px) !important;
overflow-y: auto !important;
}
.pop-over[data-popup="editUser"] .content-container,
.pop-over[data-popup="editOrg"] .content-container,
.pop-over[data-popup="editTeam"] .content-container {
max-height: calc(100vh - 80px) !important; /* Subtract header height */
height: calc(100vh - 80px) !important;
}
/* Ensure language popup list can scroll properly */
.pop-over .pop-over-list {
max-height: none;
+9 -2
View File
@@ -71,6 +71,13 @@ table tr:nth-child(even) {
left: 0 !important;
display: block !important;
}
/* Make checkbox column fit content */
table th:first-child,
table td:first-child {
width: auto;
min-width: auto;
}
#divAddOrRemoveTeam {
background: #008000;
display: none;
@@ -141,7 +148,7 @@ table tr:nth-child(even) {
}
.account-active-status {
width: 20px;
width: auto;
text-align: center;
}
@@ -170,7 +177,7 @@ table tr:nth-child(even) {
}
.account-status {
width: 20px;
width: auto;
text-align: center;
}
+15 -63
View File
@@ -46,6 +46,7 @@ template(name="people")
option(value="locked") {{_ 'admin-people-filter-locked'}}
option(value="active") {{_ 'admin-people-filter-active'}}
option(value="inactive") {{_ 'admin-people-filter-inactive'}}
option(value="admin") Admin
button#unlockAllUsers.unlock-all-btn
| 🔓
| {{_ 'accounts-lockout-unlock-all'}}
@@ -57,7 +58,7 @@ template(name="people")
| {{_ 'add'}} / {{_ 'delete'}} {{_ 'teams'}}
else if lockedUsersSetting.get
span
| 🔒.text-red
span.text-red 🔒
unless isMiniScreen
| {{_ 'accounts-lockout-locked-users'}}
@@ -78,7 +79,7 @@ template(name="people")
| {{_ 'people'}}
li
a.js-locked-users-menu(data-id="locked-users-setting")
| 🔒.text-red
span.text-red 🔒
| {{_ 'accounts-lockout-locked-users'}}
.main-body
if loading.get
@@ -100,7 +101,6 @@ template(name="orgGeneral")
th {{_ 'displayName'}}
th {{_ 'description'}}
th {{_ 'shortName'}}
th {{_ 'autoAddUsersWithDomainName'}}
th {{_ 'website'}}
th {{_ 'createdAt'}}
th {{_ 'active'}}
@@ -140,16 +140,9 @@ template(name="peopleGeneral")
th {{_ 'admin-people-active-status'}}
th {{_ 'username'}}
th {{_ 'fullname'}}
th {{_ 'initials'}}
th {{_ 'admin'}}
th {{_ 'email'}}
th {{_ 'verified'}}
th {{_ 'createdAt'}}
th {{_ 'active'}}
th {{_ 'authentication-method'}}
th {{_ 'import-usernames'}}
th {{_ 'organizations'}}
th {{_ 'teams'}}
th
+newUserRow
tbody
@@ -190,10 +183,6 @@ template(name="orgRow")
td {{ orgData.orgShortName }}
else
td <s>{{ orgData.orgShortName }}</s>
if orgData.orgIsActive
td {{ orgData.orgAutoAddUsersWithDomainName }}
else
td <s>{{ orgData.orgAutoAddUsersWithDomainName }}</s>
if orgData.orgIsActive
td {{ orgData.orgWebsite }}
else
@@ -258,14 +247,14 @@ template(name="peopleRow")
input.selectUserChkBox(type="checkbox", id="{{userData._id}}")
td.account-status
if isUserLocked
| 🔒.text-red.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}")
span.text-red.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}") 🔒
else
| 🔓.text-green.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}")
span.text-green.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}") 🔓
td.account-active-status
if userData.loginDisabled
| 🚫.text-red.js-toggle-active-status(data-user-id=userData._id, data-is-active="false", title="{{_ 'admin-people-user-inactive'}}")
span.text-red.js-toggle-active-status(data-user-id=userData._id, data-is-active="false", title="{{_ 'admin-people-user-inactive'}}") 🚫
else
| ✅.text-green.js-toggle-active-status(data-user-id=userData._id, data-is-active="true", title="{{_ 'admin-people-user-active'}}")
span.text-green.js-toggle-active-status(data-user-id=userData._id, data-is-active="true", title="{{_ 'admin-people-user-active'}}") ✅
if userData.loginDisabled
td.username <s>{{ userData.username }}</s>
else if isUserLocked
@@ -276,10 +265,6 @@ template(name="peopleRow")
td <s>{{ userData.profile.fullname }}</s>
else
td {{ userData.profile.fullname }}
if userData.loginDisabled
td <s>{{ userData.profile.initials }}</s>
else
td {{ userData.profile.initials }}
if userData.loginDisabled
td
if userData.isAdmin
@@ -296,43 +281,10 @@ template(name="peopleRow")
td <s>{{ userData.emails.[0].address }}</s>
else
td {{ userData.emails.[0].address }}
if userData.loginDisabled
td
if userData.emails.[0].verified
| <s>{{_ 'yes'}}</s>
else
| <s>{{_ 'no'}}</s>
else
td
if userData.emails.[0].verified
| {{_ 'yes'}}
else
| {{_ 'no'}}
if userData.loginDisabled
td <s>{{ moment userData.createdAt 'LLL' }}</s>
else
td {{ moment userData.createdAt 'LLL' }}
td
if userData.loginDisabled
| {{_ 'no'}}
else
| {{_ 'yes'}}
if userData.loginDisabled
td <s>{{_ userData.authenticationMethod }}</s>
else
td {{_ userData.authenticationMethod }}
if userData.loginDisabled
td <s>{{ userData.importUsernamesString }}</s>
else
td {{ userData.importUsernamesString }}
if userData.loginDisabled
td <s>{{ userData.orgsUserBelongs }}</s>
else
td {{ userData.orgsUserBelongs }}
if userData.loginDisabled
td <s>{{ userData.teamsUserBelongs }}</s>
else
td {{ userData.teamsUserBelongs }}
td
a.edit-user
| ✏️
@@ -448,8 +400,8 @@ template(name="editUserPopup")
option(value="{{value}}") {{_ value}}
label
| {{_ 'organizations'}}
| ➕#addUserOrg
| ➖#removeUserOrg
span#addUserOrg ➕
span#removeUserOrg ➖
select.js-orgs#jsOrgs
option(value="-1") {{_ 'organizations'}} :
each value in orgsDatas
@@ -458,8 +410,8 @@ template(name="editUserPopup")
input#jsUserOrgIdsInPut.js-userOrgIds.hide(type="hidden" value=user.orgIdsUserBelongs)
label
| {{_ 'teams'}}
| ➕#addUserTeam
| ➖#removeUserTeam
span#addUserTeam ➕
span#removeUserTeam ➖
select.js-teams#jsTeams
option(value="-1") {{_ 'teams'}} :
each value in teamsDatas
@@ -591,8 +543,8 @@ template(name="newUserPopup")
option(value="{{value}}") {{_ value}}
label
| {{_ 'organizations'}}
| ➕#addUserOrgNewUser
| ➖#removeUserOrgNewUser
span#addUserOrgNewUser ➕
span#removeUserOrgNewUser ➖
select.js-orgsNewUser#jsOrgsNewUser
option(value="-1") {{_ 'organizations'}} :
each value in orgsDatas
@@ -601,8 +553,8 @@ template(name="newUserPopup")
input#jsUserOrgIdsInPutNewUser.js-userOrgIdsNewUser.hide(type="text" value=user.orgIdsUserBelongs)
label
| {{_ 'teams'}}
| ➕#addUserTeamNewUser
| ➖#removeUserTeamNewUser
span#addUserTeamNewUser ➕
span#removeUserTeamNewUser ➖
select.js-teamsNewUser#jsTeamsNewUser
option(value="-1") {{_ 'teams'}} :
each value in teamsDatas
+4
View File
@@ -172,6 +172,10 @@ BlazeComponent.extendComponent({
// Show only inactive users (loginDisabled is true)
query['loginDisabled'] = true;
break;
case 'admin':
// Show only admin users (isAdmin is true)
query['isAdmin'] = true;
break;
case 'all':
default:
// Show all users, no additional filter
+16
View File
@@ -218,6 +218,22 @@ window.Popup = new (class {
const viewportHeight = $(window).height();
const popupWidth = Math.min(380, viewportWidth * 0.55) + 15; // Add 15px for margin
// 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(10, centeredLeft), // Ensure popup doesn't go off screen
top: 10, // Start from top with small margin
maxHeight: viewportHeight - 20, // Use full height minus small margins
};
}
// Calculate available height for popup
const popupTop = offset.top + $element.outerHeight();
+2 -2
View File
@@ -10,7 +10,7 @@ This is without container (without Docker or Snap).
Right click and download files 1-4:
1. [wekan-8.06-amd64-windows.zip](https://github.com/wekan/wekan/releases/download/v8.06/wekan-8.06-amd64-windows.zip)
1. [wekan-8.09-amd64-windows.zip](https://github.com/wekan/wekan/releases/download/v8.09/wekan-8.09-amd64-windows.zip)
2. [node.exe](https://nodejs.org/dist/latest-v14.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.25-signed.msi` . In installer, uncheck downloading MongoDB compass.
7. Unzip `wekan-8.06-amd64-windows.zip` , inside it is directory `bundle`, to it copy other files:
7. Unzip `wekan-8.09-amd64-windows.zip` , inside it is directory `bundle`, to it copy other files:
```
bundle (directory)
+4 -4
View File
@@ -190,9 +190,9 @@
"board-view-collapse": "Contraer",
"board-view-gantt": "Gantt",
"board-view-lists": "Listas",
"bucket-example": "Like \"Bucket List\" for example",
"calendar-previous-month-label": "Previous Month",
"calendar-next-month-label": "Next Month",
"bucket-example": "Mes anterior",
"calendar-previous-month-label": "Mes anterior",
"calendar-next-month-label": "Mes siguiente",
"cancel": "Cancelar",
"card-archived": "Se archivó esta tarjeta",
"board-archived": "Se archivó este tablero",
@@ -356,7 +356,7 @@
"custom-field-text": "Texto",
"custom-fields": "Campos personalizados",
"date": "Fecha",
"date-format": "Date Format",
"date-format": "Formato de fecha",
"date-format-yyyy-mm-dd": "YYYY-MM-DD HH:MM",
"date-format-dd-mm-yyyy": "DD-MM-YYYY HH:MM",
"date-format-mm-dd-yyyy": "MM-DD-YYYY HH:MM",
+4 -4
View File
@@ -190,9 +190,9 @@
"board-view-collapse": "צמצום",
"board-view-gantt": "גאנט",
"board-view-lists": "רשימות",
"bucket-example": "Like \"Bucket List\" for example",
"calendar-previous-month-label": "Previous Month",
"calendar-next-month-label": "Next Month",
"bucket-example": "כמו „רשימת משאלות” למשל",
"calendar-previous-month-label": "החודש הקודם",
"calendar-next-month-label": "החודש הבא",
"cancel": "ביטול",
"card-archived": "כרטיס זה שמור בארכיון.",
"board-archived": "הלוח עבר לארכיון",
@@ -356,7 +356,7 @@
"custom-field-text": "טקסט",
"custom-fields": "שדות מותאמים אישית",
"date": "תאריך",
"date-format": "Date Format",
"date-format": "תבנית תאריך",
"date-format-yyyy-mm-dd": "YYYY-MM-DD HH:MM",
"date-format-dd-mm-yyyy": "DD-MM-YYYY HH:MM",
"date-format-mm-dd-yyyy": "MM-DD-YYYY HH:MM",
+7 -7
View File
@@ -190,9 +190,9 @@
"board-view-collapse": "Expandir",
"board-view-gantt": "Gantt",
"board-view-lists": "Listas",
"bucket-example": "Like \"Bucket List\" for example",
"calendar-previous-month-label": "Previous Month",
"calendar-next-month-label": "Next Month",
"bucket-example": "Como \"Lista de Desejos\", por exemplo",
"calendar-previous-month-label": "Mês Anterior",
"calendar-next-month-label": "Próximo Mês",
"cancel": "Cancelar",
"card-archived": "Este cartão está Arquivado.",
"board-archived": "Este quadro está Arquivado.",
@@ -356,10 +356,10 @@
"custom-field-text": "Texto",
"custom-fields": "Campos customizados",
"date": "Data",
"date-format": "Date Format",
"date-format-yyyy-mm-dd": "YYYY-MM-DD HH:MM",
"date-format-dd-mm-yyyy": "DD-MM-YYYY HH:MM",
"date-format-mm-dd-yyyy": "MM-DD-YYYY HH:MM",
"date-format": "Formato da Data",
"date-format-yyyy-mm-dd": "AAAA-MM-DD HH:MM",
"date-format-dd-mm-yyyy": "DD-MM-AAAA HH:MM",
"date-format-mm-dd-yyyy": "MM-DD-AAAA HH:MM",
"decline": "Rejeitar",
"default-avatar": "Avatar padrão",
"delete": "Excluir",
+4 -4
View File
@@ -190,9 +190,9 @@
"board-view-collapse": "損毀",
"board-view-gantt": "甘特圖",
"board-view-lists": "清單",
"bucket-example": "Like \"Bucket List\" for example",
"calendar-previous-month-label": "Previous Month",
"calendar-next-month-label": "Next Month",
"bucket-example": "例如「人生清單」",
"calendar-previous-month-label": "上個月",
"calendar-next-month-label": "下個月",
"cancel": "取消",
"card-archived": "封存這個卡片。",
"board-archived": "封存這個看板。",
@@ -356,7 +356,7 @@
"custom-field-text": "文字",
"custom-fields": "自訂欄位",
"date": "日期",
"date-format": "Date Format",
"date-format": "日期格式",
"date-format-yyyy-mm-dd": "YYYY-MM-DD HH:MM",
"date-format-dd-mm-yyyy": "DD-MM-YYYY HH:MM",
"date-format-mm-dd-yyyy": "MM-DD-YYYY HH:MM",
-14
View File
@@ -803,13 +803,6 @@ Boards.helpers({
{
boardId: this._id,
archived: false,
// Get lists for all swimlanes in this board, plus lists without swimlaneId for backward compatibility
$or: [
{ swimlaneId: { $in: this.swimlanes().map(s => s._id) } },
{ swimlaneId: { $exists: false } },
{ swimlaneId: '' },
{ swimlaneId: null }
],
},
{ sort: sortKey },
);
@@ -819,13 +812,6 @@ Boards.helpers({
return ReactiveCache.getLists(
{
boardId: this._id,
// Get lists for all swimlanes in this board, plus lists without swimlaneId for backward compatibility
$or: [
{ swimlaneId: { $in: this.swimlanes().map(s => s._id) } },
{ swimlaneId: { $exists: false } },
{ swimlaneId: '' },
{ swimlaneId: null }
]
},
{ sort: { sort: 1 } }
);
+4 -16
View File
@@ -211,33 +211,20 @@ Swimlanes.helpers({
return this.draggableLists();
},
newestLists() {
// sorted lists from newest to the oldest, by its creation date or its cards' last modification date
// Include lists without swimlaneId for backward compatibility (they belong to default swimlane)
// Revert to shared lists across swimlanes: filter by board only
return ReactiveCache.getLists(
{
boardId: this.boardId,
$or: [
{ swimlaneId: this._id },
{ swimlaneId: { $exists: false } },
{ swimlaneId: '' },
{ swimlaneId: null }
],
archived: false,
},
{ sort: { modifiedAt: -1 } },
);
},
draggableLists() {
// Include lists without swimlaneId for backward compatibility (they belong to default swimlane)
// Revert to shared lists across swimlanes: filter by board only
return ReactiveCache.getLists(
{
boardId: this.boardId,
$or: [
{ swimlaneId: this._id },
{ swimlaneId: { $exists: false } },
{ swimlaneId: '' },
{ swimlaneId: null }
],
//archived: false,
},
{ sort: ['sort'] },
@@ -245,7 +232,8 @@ Swimlanes.helpers({
},
myLists() {
return ReactiveCache.getLists({ swimlaneId: this._id });
// Revert to shared lists: provide lists by board for this swimlane's board
return ReactiveCache.getLists({ boardId: this.boardId });
},
allCards() {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v8.06.0",
"version": "v8.09.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v8.06.0",
"version": "v8.09.0",
"description": "Open-Source kanban",
"private": true,
"repository": {
+9 -1
View File
@@ -12,4 +12,12 @@ if [ $# -ne 2 ]
exit 1
fi
sed -i 's|$1|$2|g' package.json package-lock.json snapcraft.yaml docs/Platforms/Propietary/Windows/Offline.md Dockerfile Stackerfile.yml sandstorm-pkgdef.capnp
# Remove dots from version numbers for sandstorm-pkgdef.capnp
OLD_VERSION_NO_DOTS=$(echo "$1" | tr -d '.')
NEW_VERSION_NO_DOTS=$(echo "$2" | tr -d '.')
# Update all files except sandstorm-pkgdef.capnp with regular version format
sed -i "s|$1|$2|g" package.json package-lock.json snapcraft.yaml docs/Platforms/Propietary/Windows/Offline.md Dockerfile Stackerfile.yml
# Update sandstorm-pkgdef.capnp with version format without dots
sed -i "s|$OLD_VERSION_NO_DOTS|$NEW_VERSION_NO_DOTS|g" sandstorm-pkgdef.capnp
+2 -2
View File
@@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = (
appTitle = (defaultText = "Wekan"),
# The name of the app as it is displayed to the user.
appVersion = 806,
appVersion = 809,
# Increment this for every release.
appMarketingVersion = (defaultText = "8.06.0~2025-10-20"),
appMarketingVersion = (defaultText = "8.09.0~2025-10-21"),
# Human-readable presentation of the app version.
minUpgradableAppVersion = 0,
+3
View File
@@ -40,6 +40,9 @@ if (errors.length > 0) {
// Import cron job storage for persistent job tracking
import './cronJobStorage';
// Import migrations
import './migrations/fixMissingListsMigration';
// Note: Automatic migrations are disabled - migrations only run when opening boards
// import './boardMigrationDetector';
// import './cronMigrationManager';
@@ -0,0 +1,269 @@
/**
* Fix Missing Lists Migration
*
* This migration fixes the issue where cards have incorrect listId references
* due to the per-swimlane lists change. It detects cards with mismatched
* listId/swimlaneId and creates the missing lists.
*
* Issue: When upgrading from v7.94 to v8.02, cards that were in different
* swimlanes but shared the same list now have wrong listId references.
*
* Example:
* - Card1: listId: 'HB93dWNnY5bgYdtxc', swimlaneId: 'sK69SseWkh3tMbJvg'
* - Card2: listId: 'HB93dWNnY5bgYdtxc', swimlaneId: 'XeecF9nZxGph4zcT4'
*
* Card2 should have a different listId that corresponds to its swimlane.
*/
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { ReactiveCache } from '/imports/reactiveCache';
import Boards from '/models/boards';
import Lists from '/models/lists';
import Cards from '/models/cards';
class FixMissingListsMigration {
constructor() {
this.name = 'fix-missing-lists';
this.version = 1;
}
/**
* Check if migration is needed for a board
*/
needsMigration(boardId) {
try {
const board = ReactiveCache.getBoard(boardId);
if (!board) return false;
// Check if board has already been processed
if (board.fixMissingListsCompleted) {
return false;
}
// Check if there are cards with mismatched listId/swimlaneId
const cards = ReactiveCache.getCards({ boardId });
const lists = ReactiveCache.getLists({ boardId });
// Create a map of listId -> swimlaneId for existing lists
const listSwimlaneMap = new Map();
lists.forEach(list => {
listSwimlaneMap.set(list._id, list.swimlaneId || '');
});
// Check for cards with mismatched listId/swimlaneId
for (const card of cards) {
const expectedSwimlaneId = listSwimlaneMap.get(card.listId);
if (expectedSwimlaneId && expectedSwimlaneId !== card.swimlaneId) {
console.log(`Found mismatched card: ${card._id}, listId: ${card.listId}, card swimlaneId: ${card.swimlaneId}, list swimlaneId: ${expectedSwimlaneId}`);
return true;
}
}
return false;
} catch (error) {
console.error('Error checking if migration is needed:', error);
return false;
}
}
/**
* Execute the migration for a board
*/
async executeMigration(boardId) {
try {
console.log(`Starting fix missing lists migration for board ${boardId}`);
const board = ReactiveCache.getBoard(boardId);
if (!board) {
throw new Error(`Board ${boardId} not found`);
}
const cards = ReactiveCache.getCards({ boardId });
const lists = ReactiveCache.getLists({ boardId });
const swimlanes = ReactiveCache.getSwimlanes({ boardId });
// Create maps for efficient lookup
const listSwimlaneMap = new Map();
const swimlaneListsMap = new Map();
lists.forEach(list => {
listSwimlaneMap.set(list._id, list.swimlaneId || '');
if (!swimlaneListsMap.has(list.swimlaneId || '')) {
swimlaneListsMap.set(list.swimlaneId || '', []);
}
swimlaneListsMap.get(list.swimlaneId || '').push(list);
});
// Group cards by swimlaneId
const cardsBySwimlane = new Map();
cards.forEach(card => {
if (!cardsBySwimlane.has(card.swimlaneId)) {
cardsBySwimlane.set(card.swimlaneId, []);
}
cardsBySwimlane.get(card.swimlaneId).push(card);
});
let createdLists = 0;
let updatedCards = 0;
// Process each swimlane
for (const [swimlaneId, swimlaneCards] of cardsBySwimlane) {
if (!swimlaneId) continue;
// Get existing lists for this swimlane
const existingLists = swimlaneListsMap.get(swimlaneId) || [];
const existingListTitles = new Set(existingLists.map(list => list.title));
// Group cards by their current listId
const cardsByListId = new Map();
swimlaneCards.forEach(card => {
if (!cardsByListId.has(card.listId)) {
cardsByListId.set(card.listId, []);
}
cardsByListId.get(card.listId).push(card);
});
// For each listId used by cards in this swimlane
for (const [listId, cardsInList] of cardsByListId) {
const originalList = lists.find(l => l._id === listId);
if (!originalList) continue;
// Check if this list's swimlaneId matches the card's swimlaneId
const listSwimlaneId = listSwimlaneMap.get(listId);
if (listSwimlaneId === swimlaneId) {
// List is already correctly assigned to this swimlane
continue;
}
// Check if we already have a list with the same title in this swimlane
let targetList = existingLists.find(list => list.title === originalList.title);
if (!targetList) {
// Create a new list for this swimlane
const newListData = {
title: originalList.title,
boardId: boardId,
swimlaneId: swimlaneId,
sort: originalList.sort || 0,
archived: originalList.archived || false,
createdAt: new Date(),
modifiedAt: new Date(),
type: originalList.type || 'list'
};
// Copy other properties if they exist
if (originalList.color) newListData.color = originalList.color;
if (originalList.wipLimit) newListData.wipLimit = originalList.wipLimit;
if (originalList.wipLimitEnabled) newListData.wipLimitEnabled = originalList.wipLimitEnabled;
if (originalList.wipLimitSoft) newListData.wipLimitSoft = originalList.wipLimitSoft;
if (originalList.starred) newListData.starred = originalList.starred;
if (originalList.collapsed) newListData.collapsed = originalList.collapsed;
// Insert the new list
const newListId = Lists.insert(newListData);
targetList = { _id: newListId, ...newListData };
createdLists++;
console.log(`Created new list "${originalList.title}" for swimlane ${swimlaneId}`);
}
// Update all cards in this group to use the correct listId
for (const card of cardsInList) {
Cards.update(card._id, {
$set: {
listId: targetList._id,
modifiedAt: new Date()
}
});
updatedCards++;
}
}
}
// Mark board as processed
Boards.update(boardId, {
$set: {
fixMissingListsCompleted: true,
fixMissingListsCompletedAt: new Date()
}
});
console.log(`Fix missing lists migration completed for board ${boardId}: created ${createdLists} lists, updated ${updatedCards} cards`);
return {
success: true,
createdLists,
updatedCards
};
} catch (error) {
console.error(`Error executing fix missing lists migration for board ${boardId}:`, error);
throw error;
}
}
/**
* Get migration status for a board
*/
getMigrationStatus(boardId) {
try {
const board = ReactiveCache.getBoard(boardId);
if (!board) {
return { status: 'board_not_found' };
}
if (board.fixMissingListsCompleted) {
return {
status: 'completed',
completedAt: board.fixMissingListsCompletedAt
};
}
const needsMigration = this.needsMigration(boardId);
return {
status: needsMigration ? 'needed' : 'not_needed'
};
} catch (error) {
console.error('Error getting migration status:', error);
return { status: 'error', error: error.message };
}
}
}
// Export singleton instance
export const fixMissingListsMigration = new FixMissingListsMigration();
// Meteor methods
Meteor.methods({
'fixMissingListsMigration.check'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
return fixMissingListsMigration.getMigrationStatus(boardId);
},
'fixMissingListsMigration.execute'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
return fixMissingListsMigration.executeMigration(boardId);
},
'fixMissingListsMigration.needsMigration'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
return fixMissingListsMigration.needsMigration(boardId);
}
});
+2 -2
View File
@@ -43,7 +43,7 @@ fi
export MONGO_LOG_DESTINATION="snapcommon"
# Set MongoDB data directory
export MONGO_DATA_DIR="${SNAP_COMMON}/wekan"
export MONGO_DATA_DIR="${SNAP_COMMON}"
# Create MongoDB data directory if it doesn't exist
if [ ! -d "$MONGO_DATA_DIR" ]; then
@@ -130,4 +130,4 @@ echo $$ > "${SNAP_COMMON}/mongodb.pid"
exec /snap/${SNAP_NAME}/current/bin/mongod \
--dbpath="$MONGO_DATA_DIR" \
--logpath="$MONGO_LOG_FILE" \
--logappend $BIND_OPTIONS
--logappend $BIND_OPTIONS
+4 -4
View File
@@ -1,5 +1,5 @@
name: wekan
version: '8.06'
version: '8.09'
base: core24
summary: Open Source kanban
description: |
@@ -166,9 +166,9 @@ parts:
# Cleanup
mkdir .build
cd .build
wget https://github.com/wekan/wekan/releases/download/v8.06/wekan-8.06-amd64.zip
unzip wekan-8.06-amd64.zip
rm wekan-8.06-amd64.zip
wget https://github.com/wekan/wekan/releases/download/v8.09/wekan-8.09-amd64.zip
unzip wekan-8.09-amd64.zip
rm wekan-8.09-amd64.zip
cd ..
##cd .build/bundle
##find . -type d -name '*-garbage*' | xargs rm -rf