123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982 |
- /**
- * Cron Migration Manager
- * Manages database migrations as cron jobs using percolate:synced-cron
- */
- import { Meteor } from 'meteor/meteor';
- import { SyncedCron } from 'meteor/percolate:synced-cron';
- import { ReactiveVar } from 'meteor/reactive-var';
- // Server-side reactive variables for cron migration progress
- export const cronMigrationProgress = new ReactiveVar(0);
- export const cronMigrationStatus = new ReactiveVar('');
- export const cronMigrationCurrentStep = new ReactiveVar('');
- export const cronMigrationSteps = new ReactiveVar([]);
- export const cronIsMigrating = new ReactiveVar(false);
- export const cronJobs = new ReactiveVar([]);
- // Board-specific operation tracking
- export const boardOperations = new ReactiveVar(new Map());
- export const boardOperationProgress = new ReactiveVar(new Map());
- class CronMigrationManager {
- constructor() {
- this.migrationSteps = this.initializeMigrationSteps();
- this.currentStepIndex = 0;
- this.startTime = null;
- this.isRunning = false;
- }
- /**
- * Initialize migration steps as cron jobs
- */
- initializeMigrationSteps() {
- return [
- {
- id: 'board-background-color',
- name: 'Board Background Colors',
- description: 'Setting up board background colors',
- weight: 1,
- completed: false,
- progress: 0,
- cronName: 'migration_board_background_color',
- schedule: 'every 1 minute', // Will be changed to 'once' when triggered
- status: 'stopped'
- },
- {
- id: 'add-cardcounterlist-allowed',
- name: 'Card Counter List Settings',
- description: 'Adding card counter list permissions',
- weight: 1,
- completed: false,
- progress: 0,
- cronName: 'migration_card_counter_list',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'add-boardmemberlist-allowed',
- name: 'Board Member List Settings',
- description: 'Adding board member list permissions',
- weight: 1,
- completed: false,
- progress: 0,
- cronName: 'migration_board_member_list',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'lowercase-board-permission',
- name: 'Board Permission Standardization',
- description: 'Converting board permissions to lowercase',
- weight: 1,
- completed: false,
- progress: 0,
- cronName: 'migration_lowercase_permission',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'change-attachments-type-for-non-images',
- name: 'Attachment Type Standardization',
- description: 'Updating attachment types for non-images',
- weight: 2,
- completed: false,
- progress: 0,
- cronName: 'migration_attachment_types',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'card-covers',
- name: 'Card Covers System',
- description: 'Setting up card cover functionality',
- weight: 2,
- completed: false,
- progress: 0,
- cronName: 'migration_card_covers',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'use-css-class-for-boards-colors',
- name: 'Board Color CSS Classes',
- description: 'Converting board colors to CSS classes',
- weight: 2,
- completed: false,
- progress: 0,
- cronName: 'migration_board_color_css',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'denormalize-star-number-per-board',
- name: 'Board Star Counts',
- description: 'Calculating star counts per board',
- weight: 3,
- completed: false,
- progress: 0,
- cronName: 'migration_star_numbers',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'add-member-isactive-field',
- name: 'Member Activity Status',
- description: 'Adding member activity tracking',
- weight: 2,
- completed: false,
- progress: 0,
- cronName: 'migration_member_activity',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'add-sort-checklists',
- name: 'Checklist Sorting',
- description: 'Adding sort order to checklists',
- weight: 2,
- completed: false,
- progress: 0,
- cronName: 'migration_sort_checklists',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'add-swimlanes',
- name: 'Swimlanes System',
- description: 'Setting up swimlanes functionality',
- weight: 4,
- completed: false,
- progress: 0,
- cronName: 'migration_swimlanes',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'add-views',
- name: 'Board Views',
- description: 'Adding board view options',
- weight: 2,
- completed: false,
- progress: 0,
- cronName: 'migration_views',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'add-checklist-items',
- name: 'Checklist Items',
- description: 'Setting up checklist items system',
- weight: 3,
- completed: false,
- progress: 0,
- cronName: 'migration_checklist_items',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'add-card-types',
- name: 'Card Types',
- description: 'Adding card type functionality',
- weight: 2,
- completed: false,
- progress: 0,
- cronName: 'migration_card_types',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'add-custom-fields-to-cards',
- name: 'Custom Fields',
- description: 'Adding custom fields to cards',
- weight: 3,
- completed: false,
- progress: 0,
- cronName: 'migration_custom_fields',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'migrate-attachments-collectionFS-to-ostrioFiles',
- name: 'Migrate Attachments to Meteor-Files',
- description: 'Migrating attachments from CollectionFS to Meteor-Files',
- weight: 8,
- completed: false,
- progress: 0,
- cronName: 'migration_attachments_collectionfs',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'migrate-avatars-collectionFS-to-ostrioFiles',
- name: 'Migrate Avatars to Meteor-Files',
- description: 'Migrating avatars from CollectionFS to Meteor-Files',
- weight: 6,
- completed: false,
- progress: 0,
- cronName: 'migration_avatars_collectionfs',
- schedule: 'every 1 minute',
- status: 'stopped'
- },
- {
- id: 'migrate-lists-to-per-swimlane',
- name: 'Migrate Lists to Per-Swimlane',
- description: 'Migrating lists to per-swimlane structure',
- weight: 5,
- completed: false,
- progress: 0,
- cronName: 'migration_lists_per_swimlane',
- schedule: 'every 1 minute',
- status: 'stopped'
- }
- ];
- }
- /**
- * Initialize all migration cron jobs
- */
- initializeCronJobs() {
- this.migrationSteps.forEach(step => {
- this.createCronJob(step);
- });
-
- // Update cron jobs list
- this.updateCronJobsList();
- }
- /**
- * Create a cron job for a migration step
- */
- createCronJob(step) {
- SyncedCron.add({
- name: step.cronName,
- schedule: (parser) => parser.text(step.schedule),
- job: () => {
- this.runMigrationStep(step);
- },
- });
- }
- /**
- * Run a migration step
- */
- async runMigrationStep(step) {
- try {
- console.log(`Starting migration: ${step.name}`);
-
- cronMigrationCurrentStep.set(step.name);
- cronMigrationStatus.set(`Running: ${step.description}`);
- cronIsMigrating.set(true);
- // Simulate migration progress
- const progressSteps = 10;
- for (let i = 0; i <= progressSteps; i++) {
- step.progress = (i / progressSteps) * 100;
- this.updateProgress();
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 100));
- }
- // Mark as completed
- step.completed = true;
- step.progress = 100;
- step.status = 'completed';
- console.log(`Completed migration: ${step.name}`);
-
- // Update progress
- this.updateProgress();
- } catch (error) {
- console.error(`Migration ${step.name} failed:`, error);
- step.status = 'error';
- cronMigrationStatus.set(`Migration failed: ${error.message}`);
- }
- }
- /**
- * Start all migrations in sequence
- */
- async startAllMigrations() {
- if (this.isRunning) {
- return;
- }
- this.isRunning = true;
- cronIsMigrating.set(true);
- cronMigrationStatus.set('Starting all migrations...');
- this.startTime = Date.now();
- try {
- for (let i = 0; i < this.migrationSteps.length; i++) {
- const step = this.migrationSteps[i];
- this.currentStepIndex = i;
- if (step.completed) {
- continue; // Skip already completed steps
- }
- // Start the cron job for this step
- await this.startCronJob(step.cronName);
-
- // Wait for completion
- await this.waitForCronJobCompletion(step);
- }
- // All migrations completed
- cronMigrationStatus.set('All migrations completed successfully!');
- cronMigrationProgress.set(100);
- cronMigrationCurrentStep.set('');
- // Clear status after delay
- setTimeout(() => {
- cronIsMigrating.set(false);
- cronMigrationStatus.set('');
- cronMigrationProgress.set(0);
- }, 3000);
- } catch (error) {
- console.error('Migration process failed:', error);
- cronMigrationStatus.set(`Migration process failed: ${error.message}`);
- cronIsMigrating.set(false);
- } finally {
- this.isRunning = false;
- }
- }
- /**
- * Start a specific cron job
- */
- async startCronJob(cronName) {
- // Change schedule to run once
- const job = SyncedCron.jobs.find(j => j.name === cronName);
- if (job) {
- job.schedule = 'once';
- SyncedCron.start();
- }
- }
- /**
- * Wait for a cron job to complete
- */
- async waitForCronJobCompletion(step) {
- return new Promise((resolve) => {
- const checkInterval = setInterval(() => {
- if (step.completed || step.status === 'error') {
- clearInterval(checkInterval);
- resolve();
- }
- }, 1000);
- });
- }
- /**
- * Stop a specific cron job
- */
- stopCronJob(cronName) {
- SyncedCron.remove(cronName);
- const step = this.migrationSteps.find(s => s.cronName === cronName);
- if (step) {
- step.status = 'stopped';
- }
- this.updateCronJobsList();
- }
- /**
- * Pause a specific cron job
- */
- pauseCronJob(cronName) {
- SyncedCron.pause(cronName);
- const step = this.migrationSteps.find(s => s.cronName === cronName);
- if (step) {
- step.status = 'paused';
- }
- this.updateCronJobsList();
- }
- /**
- * Resume a specific cron job
- */
- resumeCronJob(cronName) {
- SyncedCron.resume(cronName);
- const step = this.migrationSteps.find(s => s.cronName === cronName);
- if (step) {
- step.status = 'running';
- }
- this.updateCronJobsList();
- }
- /**
- * Remove a cron job
- */
- removeCronJob(cronName) {
- SyncedCron.remove(cronName);
- this.migrationSteps = this.migrationSteps.filter(s => s.cronName !== cronName);
- this.updateCronJobsList();
- }
- /**
- * Add a new cron job
- */
- addCronJob(jobData) {
- const step = {
- id: jobData.id || `custom_${Date.now()}`,
- name: jobData.name,
- description: jobData.description,
- weight: jobData.weight || 1,
- completed: false,
- progress: 0,
- cronName: jobData.cronName || `custom_${Date.now()}`,
- schedule: jobData.schedule || 'every 1 minute',
- status: 'stopped'
- };
- this.migrationSteps.push(step);
- this.createCronJob(step);
- this.updateCronJobsList();
- }
- /**
- * Update progress variables
- */
- updateProgress() {
- const totalWeight = this.migrationSteps.reduce((total, step) => total + step.weight, 0);
- const completedWeight = this.migrationSteps.reduce((total, step) => {
- return total + (step.completed ? step.weight : step.progress * step.weight / 100);
- }, 0);
- const progress = Math.round((completedWeight / totalWeight) * 100);
-
- cronMigrationProgress.set(progress);
- cronMigrationSteps.set([...this.migrationSteps]);
- }
- /**
- * Update cron jobs list
- */
- updateCronJobsList() {
- const jobs = SyncedCron.jobs.map(job => {
- const step = this.migrationSteps.find(s => s.cronName === job.name);
- return {
- name: job.name,
- schedule: job.schedule,
- status: step ? step.status : 'unknown',
- lastRun: job.lastRun,
- nextRun: job.nextRun,
- running: job.running
- };
- });
- cronJobs.set(jobs);
- }
- /**
- * Get all cron jobs
- */
- getAllCronJobs() {
- return cronJobs.get();
- }
- /**
- * Get migration steps
- */
- getMigrationSteps() {
- return this.migrationSteps;
- }
- /**
- * Start a long-running operation for a specific board
- */
- startBoardOperation(boardId, operationType, operationData) {
- const operationId = `${boardId}_${operationType}_${Date.now()}`;
- const operation = {
- id: operationId,
- boardId: boardId,
- type: operationType,
- data: operationData,
- status: 'running',
- progress: 0,
- startTime: new Date(),
- endTime: null,
- error: null
- };
- // Update board operations map
- const operations = boardOperations.get();
- operations.set(operationId, operation);
- boardOperations.set(operations);
- // Create cron job for this operation
- const cronName = `board_operation_${operationId}`;
- SyncedCron.add({
- name: cronName,
- schedule: (parser) => parser.text('once'),
- job: () => {
- this.executeBoardOperation(operationId, operationType, operationData);
- },
- });
- // Start the cron job
- SyncedCron.start();
- return operationId;
- }
- /**
- * Execute a board operation
- */
- async executeBoardOperation(operationId, operationType, operationData) {
- const operations = boardOperations.get();
- const operation = operations.get(operationId);
-
- if (!operation) {
- console.error(`Operation ${operationId} not found`);
- return;
- }
- try {
- console.log(`Starting board operation: ${operationType} for board ${operation.boardId}`);
-
- // Update operation status
- operation.status = 'running';
- operation.progress = 0;
- this.updateBoardOperation(operationId, operation);
- // Execute the specific operation
- switch (operationType) {
- case 'copy_board':
- await this.copyBoard(operationId, operationData);
- break;
- case 'move_board':
- await this.moveBoard(operationId, operationData);
- break;
- case 'copy_swimlane':
- await this.copySwimlane(operationId, operationData);
- break;
- case 'move_swimlane':
- await this.moveSwimlane(operationId, operationData);
- break;
- case 'copy_list':
- await this.copyList(operationId, operationData);
- break;
- case 'move_list':
- await this.moveList(operationId, operationData);
- break;
- case 'copy_card':
- await this.copyCard(operationId, operationData);
- break;
- case 'move_card':
- await this.moveCard(operationId, operationData);
- break;
- case 'copy_checklist':
- await this.copyChecklist(operationId, operationData);
- break;
- case 'move_checklist':
- await this.moveChecklist(operationId, operationData);
- break;
- default:
- throw new Error(`Unknown operation type: ${operationType}`);
- }
- // Mark as completed
- operation.status = 'completed';
- operation.progress = 100;
- operation.endTime = new Date();
- this.updateBoardOperation(operationId, operation);
- console.log(`Completed board operation: ${operationType} for board ${operation.boardId}`);
- } catch (error) {
- console.error(`Board operation ${operationType} failed:`, error);
- operation.status = 'error';
- operation.error = error.message;
- operation.endTime = new Date();
- this.updateBoardOperation(operationId, operation);
- }
- }
- /**
- * Update board operation progress
- */
- updateBoardOperation(operationId, operation) {
- const operations = boardOperations.get();
- operations.set(operationId, operation);
- boardOperations.set(operations);
- // Update progress map
- const progressMap = boardOperationProgress.get();
- progressMap.set(operationId, {
- progress: operation.progress,
- status: operation.status,
- error: operation.error
- });
- boardOperationProgress.set(progressMap);
- }
- /**
- * Copy board operation
- */
- async copyBoard(operationId, data) {
- const { sourceBoardId, targetBoardId, copyOptions } = data;
- const operation = boardOperations.get().get(operationId);
-
- // Simulate copy progress
- const steps = ['copying_swimlanes', 'copying_lists', 'copying_cards', 'copying_attachments', 'finalizing'];
- for (let i = 0; i < steps.length; i++) {
- operation.progress = Math.round(((i + 1) / steps.length) * 100);
- this.updateBoardOperation(operationId, operation);
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 1000));
- }
- }
- /**
- * Move board operation
- */
- async moveBoard(operationId, data) {
- const { sourceBoardId, targetBoardId, moveOptions } = data;
- const operation = boardOperations.get().get(operationId);
-
- // Simulate move progress
- const steps = ['preparing_move', 'moving_swimlanes', 'moving_lists', 'moving_cards', 'updating_references', 'finalizing'];
- for (let i = 0; i < steps.length; i++) {
- operation.progress = Math.round(((i + 1) / steps.length) * 100);
- this.updateBoardOperation(operationId, operation);
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 800));
- }
- }
- /**
- * Copy swimlane operation
- */
- async copySwimlane(operationId, data) {
- const { sourceSwimlaneId, targetBoardId, copyOptions } = data;
- const operation = boardOperations.get().get(operationId);
-
- // Simulate copy progress
- const steps = ['copying_swimlane', 'copying_lists', 'copying_cards', 'finalizing'];
- for (let i = 0; i < steps.length; i++) {
- operation.progress = Math.round(((i + 1) / steps.length) * 100);
- this.updateBoardOperation(operationId, operation);
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 500));
- }
- }
- /**
- * Move swimlane operation
- */
- async moveSwimlane(operationId, data) {
- const { sourceSwimlaneId, targetBoardId, moveOptions } = data;
- const operation = boardOperations.get().get(operationId);
-
- // Simulate move progress
- const steps = ['preparing_move', 'moving_swimlane', 'updating_references', 'finalizing'];
- for (let i = 0; i < steps.length; i++) {
- operation.progress = Math.round(((i + 1) / steps.length) * 100);
- this.updateBoardOperation(operationId, operation);
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 400));
- }
- }
- /**
- * Copy list operation
- */
- async copyList(operationId, data) {
- const { sourceListId, targetBoardId, copyOptions } = data;
- const operation = boardOperations.get().get(operationId);
-
- // Simulate copy progress
- const steps = ['copying_list', 'copying_cards', 'copying_attachments', 'finalizing'];
- for (let i = 0; i < steps.length; i++) {
- operation.progress = Math.round(((i + 1) / steps.length) * 100);
- this.updateBoardOperation(operationId, operation);
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 300));
- }
- }
- /**
- * Move list operation
- */
- async moveList(operationId, data) {
- const { sourceListId, targetBoardId, moveOptions } = data;
- const operation = boardOperations.get().get(operationId);
-
- // Simulate move progress
- const steps = ['preparing_move', 'moving_list', 'updating_references', 'finalizing'];
- for (let i = 0; i < steps.length; i++) {
- operation.progress = Math.round(((i + 1) / steps.length) * 100);
- this.updateBoardOperation(operationId, operation);
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 200));
- }
- }
- /**
- * Copy card operation
- */
- async copyCard(operationId, data) {
- const { sourceCardId, targetListId, copyOptions } = data;
- const operation = boardOperations.get().get(operationId);
-
- // Simulate copy progress
- const steps = ['copying_card', 'copying_attachments', 'copying_checklists', 'finalizing'];
- for (let i = 0; i < steps.length; i++) {
- operation.progress = Math.round(((i + 1) / steps.length) * 100);
- this.updateBoardOperation(operationId, operation);
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 150));
- }
- }
- /**
- * Move card operation
- */
- async moveCard(operationId, data) {
- const { sourceCardId, targetListId, moveOptions } = data;
- const operation = boardOperations.get().get(operationId);
-
- // Simulate move progress
- const steps = ['preparing_move', 'moving_card', 'updating_references', 'finalizing'];
- for (let i = 0; i < steps.length; i++) {
- operation.progress = Math.round(((i + 1) / steps.length) * 100);
- this.updateBoardOperation(operationId, operation);
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 100));
- }
- }
- /**
- * Copy checklist operation
- */
- async copyChecklist(operationId, data) {
- const { sourceChecklistId, targetCardId, copyOptions } = data;
- const operation = boardOperations.get().get(operationId);
-
- // Simulate copy progress
- const steps = ['copying_checklist', 'copying_items', 'finalizing'];
- for (let i = 0; i < steps.length; i++) {
- operation.progress = Math.round(((i + 1) / steps.length) * 100);
- this.updateBoardOperation(operationId, operation);
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 100));
- }
- }
- /**
- * Move checklist operation
- */
- async moveChecklist(operationId, data) {
- const { sourceChecklistId, targetCardId, moveOptions } = data;
- const operation = boardOperations.get().get(operationId);
-
- // Simulate move progress
- const steps = ['preparing_move', 'moving_checklist', 'finalizing'];
- for (let i = 0; i < steps.length; i++) {
- operation.progress = Math.round(((i + 1) / steps.length) * 100);
- this.updateBoardOperation(operationId, operation);
-
- // Simulate work
- await new Promise(resolve => setTimeout(resolve, 50));
- }
- }
- /**
- * Get board operations for a specific board
- */
- getBoardOperations(boardId) {
- const operations = boardOperations.get();
- const boardOps = [];
-
- for (const [operationId, operation] of operations) {
- if (operation.boardId === boardId) {
- boardOps.push(operation);
- }
- }
-
- return boardOps.sort((a, b) => b.startTime - a.startTime);
- }
- /**
- * Get all board operations with pagination
- */
- getAllBoardOperations(page = 1, limit = 20, searchTerm = '') {
- const operations = boardOperations.get();
- const allOps = Array.from(operations.values());
-
- // Filter by search term if provided
- let filteredOps = allOps;
- if (searchTerm) {
- filteredOps = allOps.filter(op =>
- op.boardId.toLowerCase().includes(searchTerm.toLowerCase()) ||
- op.type.toLowerCase().includes(searchTerm.toLowerCase())
- );
- }
-
- // Sort by start time (newest first)
- filteredOps.sort((a, b) => b.startTime - a.startTime);
-
- // Paginate
- const startIndex = (page - 1) * limit;
- const endIndex = startIndex + limit;
- const paginatedOps = filteredOps.slice(startIndex, endIndex);
-
- return {
- operations: paginatedOps,
- total: filteredOps.length,
- page: page,
- limit: limit,
- totalPages: Math.ceil(filteredOps.length / limit)
- };
- }
- /**
- * Get board operation statistics
- */
- getBoardOperationStats() {
- const operations = boardOperations.get();
- const stats = {
- total: operations.size,
- running: 0,
- completed: 0,
- error: 0,
- byType: {}
- };
-
- for (const [operationId, operation] of operations) {
- stats[operation.status]++;
-
- if (!stats.byType[operation.type]) {
- stats.byType[operation.type] = 0;
- }
- stats.byType[operation.type]++;
- }
-
- return stats;
- }
- }
- // Export singleton instance
- export const cronMigrationManager = new CronMigrationManager();
- // Initialize cron jobs on server start
- Meteor.startup(() => {
- cronMigrationManager.initializeCronJobs();
- });
- // Meteor methods for client-server communication
- Meteor.methods({
- 'cron.startAllMigrations'() {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.startAllMigrations();
- },
-
- 'cron.startJob'(cronName) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.startCronJob(cronName);
- },
-
- 'cron.stopJob'(cronName) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.stopCronJob(cronName);
- },
-
- 'cron.pauseJob'(cronName) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.pauseCronJob(cronName);
- },
-
- 'cron.resumeJob'(cronName) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.resumeCronJob(cronName);
- },
-
- 'cron.removeJob'(cronName) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.removeCronJob(cronName);
- },
-
- 'cron.addJob'(jobData) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.addCronJob(jobData);
- },
-
- 'cron.getJobs'() {
- return cronMigrationManager.getAllCronJobs();
- },
-
- 'cron.getMigrationProgress'() {
- return {
- progress: cronMigrationProgress.get(),
- status: cronMigrationStatus.get(),
- currentStep: cronMigrationCurrentStep.get(),
- steps: cronMigrationSteps.get(),
- isMigrating: cronIsMigrating.get()
- };
- },
- 'cron.startBoardOperation'(boardId, operationType, operationData) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.startBoardOperation(boardId, operationType, operationData);
- },
- 'cron.getBoardOperations'(boardId) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.getBoardOperations(boardId);
- },
- 'cron.getAllBoardOperations'(page, limit, searchTerm) {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.getAllBoardOperations(page, limit, searchTerm);
- },
- 'cron.getBoardOperationStats'() {
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return cronMigrationManager.getBoardOperationStats();
- }
- });
|