mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
Moved migrations from opening board to right sidebar / Migrations.
Thanks to xet7 !
This commit is contained in:
parent
e93e72234c
commit
1b25d1d572
7 changed files with 277 additions and 62 deletions
|
|
@ -99,24 +99,9 @@ BlazeComponent.extendComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if board needs comprehensive migration
|
// Automatic migration disabled - migrations must be run manually from sidebar
|
||||||
const needsMigration = await this.checkComprehensiveMigration(boardId);
|
// Board admins can run migrations from the sidebar Migrations menu
|
||||||
|
this.isBoardReady.set(true);
|
||||||
if (needsMigration) {
|
|
||||||
// Start comprehensive migration
|
|
||||||
this.isMigrating.set(true);
|
|
||||||
const success = await this.executeComprehensiveMigration(boardId);
|
|
||||||
this.isMigrating.set(false);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.isBoardReady.set(true);
|
|
||||||
} else {
|
|
||||||
console.error('Comprehensive migration failed, setting ready to true anyway');
|
|
||||||
this.isBoardReady.set(true); // Still show board even if migration failed
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.isBoardReady.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error during board conversion check:', error);
|
console.error('Error during board conversion check:', error);
|
||||||
|
|
|
||||||
|
|
@ -587,6 +587,10 @@ template(name="boardMenuPopup")
|
||||||
| 📦
|
| 📦
|
||||||
| {{_ 'archived-items'}}
|
| {{_ 'archived-items'}}
|
||||||
if currentUser.isBoardAdmin
|
if currentUser.isBoardAdmin
|
||||||
|
li
|
||||||
|
a.js-open-migrations
|
||||||
|
| 🔧
|
||||||
|
| {{_ 'migrations'}}
|
||||||
li
|
li
|
||||||
a.js-change-board-color
|
a.js-change-board-color
|
||||||
| 🎨
|
| 🎨
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const viewTitles = {
|
||||||
multiselection: 'multi-selection',
|
multiselection: 'multi-selection',
|
||||||
customFields: 'custom-fields',
|
customFields: 'custom-fields',
|
||||||
archives: 'archives',
|
archives: 'archives',
|
||||||
|
migrations: 'migrations',
|
||||||
};
|
};
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
BlazeComponent.extendComponent({
|
||||||
|
|
@ -271,6 +272,10 @@ Template.boardMenuPopup.events({
|
||||||
Sidebar.setView('archives');
|
Sidebar.setView('archives');
|
||||||
Popup.back();
|
Popup.back();
|
||||||
},
|
},
|
||||||
|
'click .js-open-migrations'() {
|
||||||
|
Sidebar.setView('migrations');
|
||||||
|
Popup.back();
|
||||||
|
},
|
||||||
'click .js-change-board-color': Popup.open('boardChangeColor'),
|
'click .js-change-board-color': Popup.open('boardChangeColor'),
|
||||||
'click .js-change-background-image': Popup.open('boardChangeBackgroundImage'),
|
'click .js-change-background-image': Popup.open('boardChangeBackgroundImage'),
|
||||||
'click .js-board-info-on-my-boards': Popup.open('boardInfoOnMyBoards'),
|
'click .js-board-info-on-my-boards': Popup.open('boardInfoOnMyBoards'),
|
||||||
|
|
|
||||||
69
client/components/sidebar/sidebarMigrations.jade
Normal file
69
client/components/sidebar/sidebarMigrations.jade
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
template(name='migrationsSidebar')
|
||||||
|
if currentUser.isBoardAdmin
|
||||||
|
.sidebar-migrations
|
||||||
|
h3
|
||||||
|
| 🔧
|
||||||
|
| {{_ 'migrations'}}
|
||||||
|
p.quiet {{_ 'migrations-description'}}
|
||||||
|
|
||||||
|
.migrations-list
|
||||||
|
h4 {{_ 'board-migrations'}}
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="comprehensive")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'comprehensive-board-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if comprehensiveMigrationNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="fixMissingLists")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'fix-missing-lists-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if fixMissingListsNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
|
||||||
|
hr
|
||||||
|
h4 {{_ 'global-migrations'}}
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="fixAvatarUrls")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'fix-avatar-urls-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if fixAvatarUrlsNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="fixAllFileUrls")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'fix-all-file-urls-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if fixAllFileUrlsNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
else
|
||||||
|
p.quiet {{_ 'migrations-admin-only'}}
|
||||||
|
|
||||||
|
template(name='runComprehensiveMigrationPopup')
|
||||||
|
p {{_ 'run-comprehensive-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
|
|
||||||
|
template(name='runFixMissingListsMigrationPopup')
|
||||||
|
p {{_ 'run-fix-missing-lists-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
|
|
||||||
|
template(name='runFixAvatarUrlsMigrationPopup')
|
||||||
|
p {{_ 'run-fix-avatar-urls-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
|
|
||||||
|
template(name='runFixAllFileUrlsMigrationPopup')
|
||||||
|
p {{_ 'run-fix-all-file-urls-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
143
client/components/sidebar/sidebarMigrations.js
Normal file
143
client/components/sidebar/sidebarMigrations.js
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
import { ReactiveCache } from '/imports/reactiveCache';
|
||||||
|
import { TAPi18n } from '/imports/i18n';
|
||||||
|
|
||||||
|
BlazeComponent.extendComponent({
|
||||||
|
onCreated() {
|
||||||
|
this.migrationStatuses = new ReactiveVar({});
|
||||||
|
this.loadMigrationStatuses();
|
||||||
|
},
|
||||||
|
|
||||||
|
loadMigrationStatuses() {
|
||||||
|
const boardId = Session.get('currentBoard');
|
||||||
|
if (!boardId) return;
|
||||||
|
|
||||||
|
// Check comprehensive migration
|
||||||
|
Meteor.call('comprehensiveBoardMigration.needsMigration', boardId, (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.comprehensive = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check fix missing lists migration
|
||||||
|
Meteor.call('fixMissingListsMigration.needsMigration', boardId, (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.fixMissingLists = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check fix avatar URLs migration (global)
|
||||||
|
Meteor.call('fixAvatarUrls.needsMigration', (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.fixAvatarUrls = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check fix all file URLs migration (global)
|
||||||
|
Meteor.call('fixAllFileUrls.needsMigration', (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.fixAllFileUrls = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
comprehensiveMigrationNeeded() {
|
||||||
|
return this.migrationStatuses.get().comprehensive === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
fixMissingListsNeeded() {
|
||||||
|
return this.migrationStatuses.get().fixMissingLists === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
fixAvatarUrlsNeeded() {
|
||||||
|
return this.migrationStatuses.get().fixAvatarUrls === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
fixAllFileUrlsNeeded() {
|
||||||
|
return this.migrationStatuses.get().fixAllFileUrls === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
runMigration(migrationType) {
|
||||||
|
const boardId = Session.get('currentBoard');
|
||||||
|
|
||||||
|
let methodName;
|
||||||
|
let methodArgs = [];
|
||||||
|
|
||||||
|
switch (migrationType) {
|
||||||
|
case 'comprehensive':
|
||||||
|
methodName = 'comprehensiveBoardMigration.execute';
|
||||||
|
methodArgs = [boardId];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fixMissingLists':
|
||||||
|
methodName = 'fixMissingListsMigration.execute';
|
||||||
|
methodArgs = [boardId];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fixAvatarUrls':
|
||||||
|
methodName = 'fixAvatarUrls.execute';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fixAllFileUrls':
|
||||||
|
methodName = 'fixAllFileUrls.execute';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodName) {
|
||||||
|
Meteor.call(methodName, ...methodArgs, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Migration failed:', err);
|
||||||
|
// Show error notification
|
||||||
|
Alert.error(TAPi18n.__('migration-failed') + ': ' + (err.message || err.reason));
|
||||||
|
} else {
|
||||||
|
console.log('Migration completed:', result);
|
||||||
|
// Show success notification
|
||||||
|
Alert.success(TAPi18n.__('migration-successful'));
|
||||||
|
|
||||||
|
// Reload migration statuses
|
||||||
|
Meteor.setTimeout(() => {
|
||||||
|
this.loadMigrationStatuses();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
events() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'click .js-run-migration[data-migration="comprehensive"]': Popup.afterConfirm('runComprehensiveMigration', function() {
|
||||||
|
const component = BlazeComponent.getComponentForElement(this);
|
||||||
|
if (component) {
|
||||||
|
component.runMigration('comprehensive');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'click .js-run-migration[data-migration="fixMissingLists"]': Popup.afterConfirm('runFixMissingListsMigration', function() {
|
||||||
|
const component = BlazeComponent.getComponentForElement(this);
|
||||||
|
if (component) {
|
||||||
|
component.runMigration('fixMissingLists');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'click .js-run-migration[data-migration="fixAvatarUrls"]': Popup.afterConfirm('runFixAvatarUrlsMigration', function() {
|
||||||
|
const component = BlazeComponent.getComponentForElement(this);
|
||||||
|
if (component) {
|
||||||
|
component.runMigration('fixAvatarUrls');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'click .js-run-migration[data-migration="fixAllFileUrls"]': Popup.afterConfirm('runFixAllFileUrlsMigration', function() {
|
||||||
|
const component = BlazeComponent.getComponentForElement(this);
|
||||||
|
if (component) {
|
||||||
|
component.runMigration('fixAllFileUrls');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
}).register('migrationsSidebar');
|
||||||
|
|
@ -1404,7 +1404,31 @@
|
||||||
"back-to-settings": "Back to Settings",
|
"back-to-settings": "Back to Settings",
|
||||||
"board-id": "Board ID",
|
"board-id": "Board ID",
|
||||||
"board-migration": "Board Migration",
|
"board-migration": "Board Migration",
|
||||||
|
"board-migrations": "Board Migrations",
|
||||||
"card-show-lists-on-minicard": "Show Lists on Minicard",
|
"card-show-lists-on-minicard": "Show Lists on Minicard",
|
||||||
|
"comprehensive-board-migration": "Comprehensive Board Migration",
|
||||||
|
"comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.",
|
||||||
|
"fix-missing-lists-migration": "Fix Missing Lists",
|
||||||
|
"fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.",
|
||||||
|
"fix-avatar-urls-migration": "Fix Avatar URLs",
|
||||||
|
"fix-avatar-urls-migration-description": "Updates avatar URLs to use the correct storage backend and fixes broken avatar references.",
|
||||||
|
"fix-all-file-urls-migration": "Fix All File URLs",
|
||||||
|
"fix-all-file-urls-migration-description": "Updates all file attachment URLs to use the correct storage backend and fixes broken file references.",
|
||||||
|
"global-migrations": "Global Migrations",
|
||||||
|
"migration-needed": "Migration Needed",
|
||||||
|
"migration-complete": "Complete",
|
||||||
|
"migration-running": "Running...",
|
||||||
|
"migration-successful": "Migration completed successfully",
|
||||||
|
"migration-failed": "Migration failed",
|
||||||
|
"migrations": "Migrations",
|
||||||
|
"migrations-admin-only": "Only board administrators can run migrations",
|
||||||
|
"migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.",
|
||||||
|
"no-issues-found": "No issues found",
|
||||||
|
"run-migration": "Run Migration",
|
||||||
|
"run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?",
|
||||||
|
"run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?",
|
||||||
|
"run-fix-avatar-urls-migration-confirm": "This will update avatar URLs across all boards to use the correct storage backend. This is a global operation. Continue?",
|
||||||
|
"run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs across all boards to use the correct storage backend. This is a global operation. Continue?",
|
||||||
"cleanup": "Cleanup",
|
"cleanup": "Cleanup",
|
||||||
"cleanup-old-jobs": "Cleanup Old Jobs",
|
"cleanup-old-jobs": "Cleanup Old Jobs",
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,11 @@ class FixAllFileUrlsMigration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for problematic attachment URLs in cards
|
// Check for problematic attachment URLs
|
||||||
const cards = ReactiveCache.getCards({});
|
const attachments = ReactiveCache.getAttachments({});
|
||||||
for (const card of cards) {
|
for (const attachment of attachments) {
|
||||||
if (card.attachments) {
|
if (attachment.url && this.hasProblematicUrl(attachment.url)) {
|
||||||
for (const attachment of card.attachments) {
|
return true;
|
||||||
if (attachment.url && this.hasProblematicUrl(attachment.url)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,51 +202,40 @@ class FixAllFileUrlsMigration {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fix attachment URLs in card references
|
* Fix attachment URLs in the Attachments collection
|
||||||
*/
|
*/
|
||||||
async fixCardAttachmentUrls() {
|
async fixCardAttachmentUrls() {
|
||||||
const cards = ReactiveCache.getCards({});
|
const attachments = ReactiveCache.getAttachments({});
|
||||||
let cardsFixed = 0;
|
let attachmentsFixed = 0;
|
||||||
|
|
||||||
for (const card of cards) {
|
for (const attachment of attachments) {
|
||||||
if (card.attachments) {
|
if (attachment.url && this.hasProblematicUrl(attachment.url)) {
|
||||||
let needsUpdate = false;
|
try {
|
||||||
const updatedAttachments = card.attachments.map(attachment => {
|
const fileId = attachment._id || extractFileIdFromUrl(attachment.url, 'attachment');
|
||||||
if (attachment.url && this.hasProblematicUrl(attachment.url)) {
|
const cleanUrl = fileId ? generateUniversalAttachmentUrl(fileId) : cleanFileUrl(attachment.url, 'attachment');
|
||||||
try {
|
|
||||||
const fileId = attachment._id || extractFileIdFromUrl(attachment.url, 'attachment');
|
if (cleanUrl && cleanUrl !== attachment.url) {
|
||||||
const cleanUrl = fileId ? generateUniversalAttachmentUrl(fileId) : cleanFileUrl(attachment.url, 'attachment');
|
// Update attachment with fixed URL
|
||||||
|
Attachments.update(attachment._id, {
|
||||||
if (cleanUrl && cleanUrl !== attachment.url) {
|
$set: {
|
||||||
needsUpdate = true;
|
url: cleanUrl,
|
||||||
return { ...attachment, url: cleanUrl };
|
modifiedAt: new Date()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
});
|
||||||
console.error(`Error fixing card attachment URL:`, error);
|
|
||||||
|
attachmentsFixed++;
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Fixed attachment URL ${attachment._id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return attachment;
|
} catch (error) {
|
||||||
});
|
console.error(`Error fixing attachment URL:`, error);
|
||||||
|
|
||||||
if (needsUpdate) {
|
|
||||||
// Update card with fixed attachment URLs
|
|
||||||
Cards.update(card._id, {
|
|
||||||
$set: {
|
|
||||||
attachments: updatedAttachments,
|
|
||||||
modifiedAt: new Date()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cardsFixed++;
|
|
||||||
|
|
||||||
if (process.env.DEBUG === 'true') {
|
|
||||||
console.log(`Fixed attachment URLs in card ${card._id}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cardsFixed;
|
return attachmentsFixed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue