cronSettings.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. import { Template } from 'meteor/templating';
  2. import { ReactiveVar } from 'meteor/reactive-var';
  3. import { Meteor } from 'meteor/meteor';
  4. import { TAPi18n } from '/imports/i18n';
  5. // Reactive variables for cron settings
  6. const migrationProgress = new ReactiveVar(0);
  7. const migrationStatus = new ReactiveVar('');
  8. const migrationCurrentStep = new ReactiveVar('');
  9. const migrationSteps = new ReactiveVar([]);
  10. const isMigrating = new ReactiveVar(false);
  11. const cronJobs = new ReactiveVar([]);
  12. Template.cronSettings.onCreated(function() {
  13. this.loading = new ReactiveVar(true);
  14. this.showMigrations = new ReactiveVar(true);
  15. this.showBoardOperations = new ReactiveVar(false);
  16. this.showJobs = new ReactiveVar(false);
  17. this.showAddJob = new ReactiveVar(false);
  18. // Board operations pagination
  19. this.currentPage = new ReactiveVar(1);
  20. this.pageSize = new ReactiveVar(20);
  21. this.searchTerm = new ReactiveVar('');
  22. this.boardOperations = new ReactiveVar([]);
  23. this.operationStats = new ReactiveVar({});
  24. this.pagination = new ReactiveVar({});
  25. this.queueStats = new ReactiveVar({});
  26. this.systemResources = new ReactiveVar({});
  27. this.boardMigrationStats = new ReactiveVar({});
  28. // Load initial data
  29. this.loadCronData();
  30. });
  31. Template.cronSettings.helpers({
  32. loading() {
  33. return Template.instance().loading.get();
  34. },
  35. showMigrations() {
  36. return Template.instance().showMigrations.get();
  37. },
  38. showBoardOperations() {
  39. return Template.instance().showBoardOperations.get();
  40. },
  41. showJobs() {
  42. return Template.instance().showJobs.get();
  43. },
  44. showAddJob() {
  45. return Template.instance().showAddJob.get();
  46. },
  47. migrationProgress() {
  48. return migrationProgress.get();
  49. },
  50. migrationStatus() {
  51. return migrationStatus.get();
  52. },
  53. migrationCurrentStep() {
  54. return migrationCurrentStep.get();
  55. },
  56. migrationSteps() {
  57. const steps = migrationSteps.get();
  58. const currentStep = migrationCurrentStep.get();
  59. return steps.map(step => ({
  60. ...step,
  61. isCurrentStep: step.name === currentStep
  62. }));
  63. },
  64. cronJobs() {
  65. return cronJobs.get();
  66. },
  67. formatDate(date) {
  68. if (!date) return '-';
  69. return new Date(date).toLocaleString();
  70. },
  71. boardOperations() {
  72. return Template.instance().boardOperations.get();
  73. },
  74. operationStats() {
  75. return Template.instance().operationStats.get();
  76. },
  77. pagination() {
  78. return Template.instance().pagination.get();
  79. },
  80. queueStats() {
  81. return Template.instance().queueStats.get();
  82. },
  83. systemResources() {
  84. return Template.instance().systemResources.get();
  85. },
  86. boardMigrationStats() {
  87. return Template.instance().boardMigrationStats.get();
  88. },
  89. formatDateTime(date) {
  90. if (!date) return '-';
  91. return new Date(date).toLocaleString();
  92. },
  93. formatDuration(startTime, endTime) {
  94. if (!startTime) return '-';
  95. const start = new Date(startTime);
  96. const end = endTime ? new Date(endTime) : new Date();
  97. const diffMs = end - start;
  98. const diffMins = Math.floor(diffMs / 60000);
  99. const diffSecs = Math.floor((diffMs % 60000) / 1000);
  100. if (diffMins > 0) {
  101. return `${diffMins}m ${diffSecs}s`;
  102. } else {
  103. return `${diffSecs}s`;
  104. }
  105. }
  106. });
  107. Template.cronSettings.events({
  108. 'click .js-cron-migrations'(event) {
  109. event.preventDefault();
  110. const instance = Template.instance();
  111. instance.showMigrations.set(true);
  112. instance.showJobs.set(false);
  113. instance.showAddJob.set(false);
  114. },
  115. 'click .js-cron-board-operations'(event) {
  116. event.preventDefault();
  117. const instance = Template.instance();
  118. instance.showMigrations.set(false);
  119. instance.showBoardOperations.set(true);
  120. instance.showJobs.set(false);
  121. instance.showAddJob.set(false);
  122. instance.loadBoardOperations();
  123. },
  124. 'click .js-cron-jobs'(event) {
  125. event.preventDefault();
  126. const instance = Template.instance();
  127. instance.showMigrations.set(false);
  128. instance.showBoardOperations.set(false);
  129. instance.showJobs.set(true);
  130. instance.showAddJob.set(false);
  131. instance.loadCronJobs();
  132. },
  133. 'click .js-cron-add'(event) {
  134. event.preventDefault();
  135. const instance = Template.instance();
  136. instance.showMigrations.set(false);
  137. instance.showJobs.set(false);
  138. instance.showAddJob.set(true);
  139. },
  140. 'click .js-start-all-migrations'(event) {
  141. event.preventDefault();
  142. Meteor.call('cron.startAllMigrations', (error, result) => {
  143. if (error) {
  144. console.error('Failed to start migrations:', error);
  145. alert('Failed to start migrations: ' + error.message);
  146. } else {
  147. console.log('Migrations started successfully');
  148. Template.instance().pollMigrationProgress();
  149. }
  150. });
  151. },
  152. 'click .js-pause-all-migrations'(event) {
  153. event.preventDefault();
  154. // Pause all migration cron jobs
  155. const jobs = cronJobs.get();
  156. jobs.forEach(job => {
  157. if (job.name.startsWith('migration_')) {
  158. Meteor.call('cron.pauseJob', job.name);
  159. }
  160. });
  161. },
  162. 'click .js-stop-all-migrations'(event) {
  163. event.preventDefault();
  164. // Stop all migration cron jobs
  165. const jobs = cronJobs.get();
  166. jobs.forEach(job => {
  167. if (job.name.startsWith('migration_')) {
  168. Meteor.call('cron.stopJob', job.name);
  169. }
  170. });
  171. },
  172. 'click .js-refresh-jobs'(event) {
  173. event.preventDefault();
  174. Template.instance().loadCronJobs();
  175. },
  176. 'click .js-start-job'(event) {
  177. event.preventDefault();
  178. const jobName = $(event.currentTarget).data('job');
  179. Meteor.call('cron.startJob', jobName, (error, result) => {
  180. if (error) {
  181. console.error('Failed to start job:', error);
  182. alert('Failed to start job: ' + error.message);
  183. } else {
  184. console.log('Job started successfully');
  185. Template.instance().loadCronJobs();
  186. }
  187. });
  188. },
  189. 'click .js-pause-job'(event) {
  190. event.preventDefault();
  191. const jobName = $(event.currentTarget).data('job');
  192. Meteor.call('cron.pauseJob', jobName, (error, result) => {
  193. if (error) {
  194. console.error('Failed to pause job:', error);
  195. alert('Failed to pause job: ' + error.message);
  196. } else {
  197. console.log('Job paused successfully');
  198. Template.instance().loadCronJobs();
  199. }
  200. });
  201. },
  202. 'click .js-stop-job'(event) {
  203. event.preventDefault();
  204. const jobName = $(event.currentTarget).data('job');
  205. Meteor.call('cron.stopJob', jobName, (error, result) => {
  206. if (error) {
  207. console.error('Failed to stop job:', error);
  208. alert('Failed to stop job: ' + error.message);
  209. } else {
  210. console.log('Job stopped successfully');
  211. Template.instance().loadCronJobs();
  212. }
  213. });
  214. },
  215. 'click .js-remove-job'(event) {
  216. event.preventDefault();
  217. const jobName = $(event.currentTarget).data('job');
  218. if (confirm('Are you sure you want to remove this job?')) {
  219. Meteor.call('cron.removeJob', jobName, (error, result) => {
  220. if (error) {
  221. console.error('Failed to remove job:', error);
  222. alert('Failed to remove job: ' + error.message);
  223. } else {
  224. console.log('Job removed successfully');
  225. Template.instance().loadCronJobs();
  226. }
  227. });
  228. }
  229. },
  230. 'submit .js-add-cron-job-form'(event) {
  231. event.preventDefault();
  232. const form = event.currentTarget;
  233. const formData = new FormData(form);
  234. const jobData = {
  235. name: formData.get('name'),
  236. description: formData.get('description'),
  237. schedule: formData.get('schedule'),
  238. weight: parseInt(formData.get('weight'))
  239. };
  240. Meteor.call('cron.addJob', jobData, (error, result) => {
  241. if (error) {
  242. console.error('Failed to add job:', error);
  243. alert('Failed to add job: ' + error.message);
  244. } else {
  245. console.log('Job added successfully');
  246. form.reset();
  247. Template.instance().showJobs.set(true);
  248. Template.instance().showAddJob.set(false);
  249. Template.instance().loadCronJobs();
  250. }
  251. });
  252. },
  253. 'click .js-cancel-add-job'(event) {
  254. event.preventDefault();
  255. const instance = Template.instance();
  256. instance.showJobs.set(true);
  257. instance.showAddJob.set(false);
  258. },
  259. 'click .js-refresh-board-operations'(event) {
  260. event.preventDefault();
  261. Template.instance().loadBoardOperations();
  262. },
  263. 'click .js-start-test-operation'(event) {
  264. event.preventDefault();
  265. const testBoardId = 'test-board-' + Date.now();
  266. const operationData = {
  267. sourceBoardId: 'source-board',
  268. targetBoardId: 'target-board',
  269. copyOptions: { includeCards: true, includeAttachments: true }
  270. };
  271. Meteor.call('cron.startBoardOperation', testBoardId, 'copy_board', operationData, (error, result) => {
  272. if (error) {
  273. console.error('Failed to start test operation:', error);
  274. alert('Failed to start test operation: ' + error.message);
  275. } else {
  276. console.log('Test operation started:', result);
  277. Template.instance().loadBoardOperations();
  278. }
  279. });
  280. },
  281. 'input .js-search-board-operations'(event) {
  282. const searchTerm = $(event.currentTarget).val();
  283. const instance = Template.instance();
  284. instance.searchTerm.set(searchTerm);
  285. instance.currentPage.set(1);
  286. instance.loadBoardOperations();
  287. },
  288. 'click .js-prev-page'(event) {
  289. event.preventDefault();
  290. const instance = Template.instance();
  291. const currentPage = instance.currentPage.get();
  292. if (currentPage > 1) {
  293. instance.currentPage.set(currentPage - 1);
  294. instance.loadBoardOperations();
  295. }
  296. },
  297. 'click .js-next-page'(event) {
  298. event.preventDefault();
  299. const instance = Template.instance();
  300. const currentPage = instance.currentPage.get();
  301. const pagination = instance.pagination.get();
  302. if (currentPage < pagination.totalPages) {
  303. instance.currentPage.set(currentPage + 1);
  304. instance.loadBoardOperations();
  305. }
  306. },
  307. 'click .js-pause-operation'(event) {
  308. event.preventDefault();
  309. const operationId = $(event.currentTarget).data('operation');
  310. // Implementation for pausing operation
  311. console.log('Pause operation:', operationId);
  312. },
  313. 'click .js-resume-operation'(event) {
  314. event.preventDefault();
  315. const operationId = $(event.currentTarget).data('operation');
  316. // Implementation for resuming operation
  317. console.log('Resume operation:', operationId);
  318. },
  319. 'click .js-stop-operation'(event) {
  320. event.preventDefault();
  321. const operationId = $(event.currentTarget).data('operation');
  322. if (confirm('Are you sure you want to stop this operation?')) {
  323. // Implementation for stopping operation
  324. console.log('Stop operation:', operationId);
  325. }
  326. },
  327. 'click .js-view-details'(event) {
  328. event.preventDefault();
  329. const operationId = $(event.currentTarget).data('operation');
  330. // Implementation for viewing operation details
  331. console.log('View details for operation:', operationId);
  332. },
  333. 'click .js-force-board-scan'(event) {
  334. event.preventDefault();
  335. Meteor.call('cron.forceBoardMigrationScan', (error, result) => {
  336. if (error) {
  337. console.error('Failed to force board scan:', error);
  338. alert('Failed to force board scan: ' + error.message);
  339. } else {
  340. console.log('Board scan started successfully');
  341. // Refresh the data
  342. Template.instance().loadBoardOperations();
  343. }
  344. });
  345. }
  346. });
  347. Template.cronSettings.prototype.loadCronData = function() {
  348. this.loading.set(true);
  349. // Load migration progress
  350. Meteor.call('cron.getMigrationProgress', (error, result) => {
  351. if (result) {
  352. migrationProgress.set(result.progress);
  353. migrationStatus.set(result.status);
  354. migrationCurrentStep.set(result.currentStep);
  355. migrationSteps.set(result.steps);
  356. isMigrating.set(result.isMigrating);
  357. }
  358. });
  359. // Load cron jobs
  360. this.loadCronJobs();
  361. this.loading.set(false);
  362. };
  363. Template.cronSettings.prototype.loadCronJobs = function() {
  364. Meteor.call('cron.getJobs', (error, result) => {
  365. if (result) {
  366. cronJobs.set(result);
  367. }
  368. });
  369. };
  370. Template.cronSettings.prototype.loadBoardOperations = function() {
  371. const instance = this;
  372. const page = instance.currentPage.get();
  373. const limit = instance.pageSize.get();
  374. const searchTerm = instance.searchTerm.get();
  375. Meteor.call('cron.getAllBoardOperations', page, limit, searchTerm, (error, result) => {
  376. if (result) {
  377. instance.boardOperations.set(result.operations);
  378. instance.pagination.set({
  379. total: result.total,
  380. page: result.page,
  381. limit: result.limit,
  382. totalPages: result.totalPages,
  383. start: ((result.page - 1) * result.limit) + 1,
  384. end: Math.min(result.page * result.limit, result.total),
  385. hasPrev: result.page > 1,
  386. hasNext: result.page < result.totalPages
  387. });
  388. }
  389. });
  390. // Load operation stats
  391. Meteor.call('cron.getBoardOperationStats', (error, result) => {
  392. if (result) {
  393. instance.operationStats.set(result);
  394. }
  395. });
  396. // Load queue stats
  397. Meteor.call('cron.getQueueStats', (error, result) => {
  398. if (result) {
  399. instance.queueStats.set(result);
  400. }
  401. });
  402. // Load system resources
  403. Meteor.call('cron.getSystemResources', (error, result) => {
  404. if (result) {
  405. instance.systemResources.set(result);
  406. }
  407. });
  408. // Load board migration stats
  409. Meteor.call('cron.getBoardMigrationStats', (error, result) => {
  410. if (result) {
  411. instance.boardMigrationStats.set(result);
  412. }
  413. });
  414. };
  415. Template.cronSettings.prototype.pollMigrationProgress = function() {
  416. const pollInterval = setInterval(() => {
  417. Meteor.call('cron.getMigrationProgress', (error, result) => {
  418. if (result) {
  419. migrationProgress.set(result.progress);
  420. migrationStatus.set(result.status);
  421. migrationCurrentStep.set(result.currentStep);
  422. migrationSteps.set(result.steps);
  423. isMigrating.set(result.isMigrating);
  424. // Stop polling if migration is complete
  425. if (!result.isMigrating && result.progress === 100) {
  426. clearInterval(pollInterval);
  427. }
  428. }
  429. });
  430. }, 1000);
  431. };