Browse Source

Moved migrations from opening board to right sidebar / Migrations.

Thanks to xet7 !
Lauri Ojansivu 2 days ago
parent
commit
1b25d1d572

+ 3 - 18
client/components/boards/boardBody.js

@@ -99,24 +99,9 @@ BlazeComponent.extendComponent({
         return;
       }
 
-      // Check if board needs comprehensive migration
-      const needsMigration = await this.checkComprehensiveMigration(boardId);
-      
-      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);
-      }
+      // Automatic migration disabled - migrations must be run manually from sidebar
+      // Board admins can run migrations from the sidebar Migrations menu
+      this.isBoardReady.set(true);
 
     } catch (error) {
       console.error('Error during board conversion check:', error);

+ 4 - 0
client/components/sidebar/sidebar.jade

@@ -587,6 +587,10 @@ template(name="boardMenuPopup")
         | 📦
         | {{_ 'archived-items'}}
     if currentUser.isBoardAdmin
+      li
+        a.js-open-migrations
+          | 🔧
+          | {{_ 'migrations'}}
       li
         a.js-change-board-color
           | 🎨

+ 5 - 0
client/components/sidebar/sidebar.js

@@ -13,6 +13,7 @@ const viewTitles = {
   multiselection: 'multi-selection',
   customFields: 'custom-fields',
   archives: 'archives',
+  migrations: 'migrations',
 };
 
 BlazeComponent.extendComponent({
@@ -271,6 +272,10 @@ Template.boardMenuPopup.events({
     Sidebar.setView('archives');
     Popup.back();
   },
+  'click .js-open-migrations'() {
+    Sidebar.setView('migrations');
+    Popup.back();
+  },
   'click .js-change-board-color': Popup.open('boardChangeColor'),
   'click .js-change-background-image': Popup.open('boardChangeBackgroundImage'),
   'click .js-board-info-on-my-boards': Popup.open('boardInfoOnMyBoards'),

+ 69 - 0
client/components/sidebar/sidebarMigrations.jade

@@ -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 - 0
client/components/sidebar/sidebarMigrations.js

@@ -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');

+ 24 - 0
imports/i18n/data/en.i18n.json

@@ -1404,7 +1404,31 @@
   "back-to-settings": "Back to Settings",
   "board-id": "Board ID",
   "board-migration": "Board Migration",
+  "board-migrations": "Board Migrations",
   "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-old-jobs": "Cleanup Old Jobs",
   "completed": "Completed",

+ 29 - 44
server/migrations/fixAllFileUrls.js

@@ -30,15 +30,11 @@ class FixAllFileUrlsMigration {
       }
     }
 
-    // Check for problematic attachment URLs in cards
-    const cards = ReactiveCache.getCards({});
-    for (const card of cards) {
-      if (card.attachments) {
-        for (const attachment of card.attachments) {
-          if (attachment.url && this.hasProblematicUrl(attachment.url)) {
-            return true;
-          }
-        }
+    // Check for problematic attachment URLs
+    const attachments = ReactiveCache.getAttachments({});
+    for (const attachment of attachments) {
+      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() {
-    const cards = ReactiveCache.getCards({});
-    let cardsFixed = 0;
+    const attachments = ReactiveCache.getAttachments({});
+    let attachmentsFixed = 0;
 
-    for (const card of cards) {
-      if (card.attachments) {
-        let needsUpdate = false;
-        const updatedAttachments = card.attachments.map(attachment => {
-          if (attachment.url && this.hasProblematicUrl(attachment.url)) {
-            try {
-              const fileId = attachment._id || extractFileIdFromUrl(attachment.url, 'attachment');
-              const cleanUrl = fileId ? generateUniversalAttachmentUrl(fileId) : cleanFileUrl(attachment.url, 'attachment');
-              
-              if (cleanUrl && cleanUrl !== attachment.url) {
-                needsUpdate = true;
-                return { ...attachment, url: cleanUrl };
+    for (const attachment of attachments) {
+      if (attachment.url && this.hasProblematicUrl(attachment.url)) {
+        try {
+          const fileId = attachment._id || extractFileIdFromUrl(attachment.url, 'attachment');
+          const cleanUrl = fileId ? generateUniversalAttachmentUrl(fileId) : cleanFileUrl(attachment.url, 'attachment');
+          
+          if (cleanUrl && cleanUrl !== attachment.url) {
+            // Update attachment with fixed URL
+            Attachments.update(attachment._id, {
+              $set: {
+                url: cleanUrl,
+                modifiedAt: new Date()
               }
-            } catch (error) {
-              console.error(`Error fixing card attachment URL:`, error);
-            }
-          }
-          return attachment;
-        });
-
-        if (needsUpdate) {
-          // Update card with fixed attachment URLs
-          Cards.update(card._id, {
-            $set: {
-              attachments: updatedAttachments,
-              modifiedAt: new Date()
+            });
+            
+            attachmentsFixed++;
+            
+            if (process.env.DEBUG === 'true') {
+              console.log(`Fixed attachment URL ${attachment._id}`);
             }
-          });
-          
-          cardsFixed++;
-          
-          if (process.env.DEBUG === 'true') {
-            console.log(`Fixed attachment URLs in card ${card._id}`);
           }
+        } catch (error) {
+          console.error(`Error fixing attachment URL:`, error);
         }
       }
     }
 
-    return cardsFixed;
+    return attachmentsFixed;
   }
 }