From 7bb1e24bda2ed9db0bad0fafcf256680c2c05e8a Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 13 Oct 2025 22:17:32 +0300 Subject: [PATCH] Fixed Admin Panel Settings menus Attachments and Cron. Thanks to xet7 ! --- client/00-startup.js | 3 - .../settings/attachmentSettings.jade | 95 ++- .../components/settings/attachmentSettings.js | 504 ---------------- client/components/settings/cronSettings.jade | 74 ++- client/components/settings/cronSettings.js | 552 ------------------ client/components/settings/settingBody.jade | 179 ++++-- client/components/settings/settingBody.js | 313 +++++++--- client/components/settings/settingsTabs.css | 67 --- 8 files changed, 485 insertions(+), 1302 deletions(-) delete mode 100644 client/components/settings/attachmentSettings.js delete mode 100644 client/components/settings/cronSettings.js delete mode 100644 client/components/settings/settingsTabs.css diff --git a/client/00-startup.js b/client/00-startup.js index 85f0cca9a..a6f049322 100644 --- a/client/00-startup.js +++ b/client/00-startup.js @@ -15,6 +15,3 @@ import '/client/components/migrationProgress'; // Import cron settings import '/client/components/settings/cronSettings'; - -// Import settings tabs CSS -import '/client/components/settings/settingsTabs.css'; diff --git a/client/components/settings/attachmentSettings.jade b/client/components/settings/attachmentSettings.jade index d95054656..669f84131 100644 --- a/client/components/settings/attachmentSettings.jade +++ b/client/components/settings/attachmentSettings.jade @@ -1,29 +1,74 @@ template(name="attachmentSettings") - .attachment-settings-content - .settings-tabs - ul.tab-nav - li(class="{{#if showStorageSettings}}active{{/if}}") - a.js-attachment-storage-settings(data-id="storage-settings") - i.fa.fa-cog - | {{_ 'attachment-storage-settings'}} - li(class="{{#if showMigration}}active{{/if}}") - a.js-attachment-migration(data-id="attachment-migration") - i.fa.fa-arrow-right - | {{_ 'attachment-migration'}} - li(class="{{#if showMonitoring}}active{{/if}}") - a.js-attachment-monitoring(data-id="attachment-monitoring") - i.fa.fa-chart-line - | {{_ 'attachment-monitoring'}} - - .tab-content - if loading - +spinner - else if showStorageSettings - +storageSettings - else if showMigration - +attachmentMigration - else if showMonitoring - +attachmentMonitoring + ul#attachment-setting.setting-detail + li + h3 {{_ 'attachment-storage-configuration'}} + .form-group + label {{_ 'writable-path'}} + input.wekan-form-control#filesystem-path(type="text" value="{{filesystemPath}}" readonly) + small.form-text.text-muted {{_ 'filesystem-path-description'}} + + .form-group + label {{_ 'attachments-path'}} + input.wekan-form-control#attachments-path(type="text" value="{{attachmentsPath}}" readonly) + small.form-text.text-muted {{_ 'attachments-path-description'}} + + .form-group + label {{_ 'avatars-path'}} + input.wekan-form-control#avatars-path(type="text" value="{{avatarsPath}}" readonly) + small.form-text.text-muted {{_ 'avatars-path-description'}} + + li + h3 {{_ 'mongodb-gridfs-storage'}} + .form-group + label {{_ 'gridfs-enabled'}} + input.wekan-form-control#gridfs-enabled(type="checkbox" checked="{{gridfsEnabled}}" disabled) + small.form-text.text-muted {{_ 'gridfs-enabled-description'}} + + li + h3 {{_ 's3-minio-storage'}} + .form-group + label {{_ 's3-enabled'}} + input.wekan-form-control#s3-enabled(type="checkbox" checked="{{s3Enabled}}" disabled) + small.form-text.text-muted {{_ 's3-enabled-description'}} + + .form-group + label {{_ 's3-endpoint'}} + input.wekan-form-control#s3-endpoint(type="text" value="{{s3Endpoint}}" readonly) + small.form-text.text-muted {{_ 's3-endpoint-description'}} + + .form-group + label {{_ 's3-bucket'}} + input.wekan-form-control#s3-bucket(type="text" value="{{s3Bucket}}" readonly) + small.form-text.text-muted {{_ 's3-bucket-description'}} + + .form-group + label {{_ 's3-region'}} + input.wekan-form-control#s3-region(type="text" value="{{s3Region}}" readonly) + small.form-text.text-muted {{_ 's3-region-description'}} + + .form-group + label {{_ 's3-access-key'}} + input.wekan-form-control#s3-access-key(type="text" placeholder="{{_ 's3-access-key-placeholder'}}" readonly) + small.form-text.text-muted {{_ 's3-access-key-description'}} + + .form-group + label {{_ 's3-secret-key'}} + input.wekan-form-control#s3-secret-key(type="password" placeholder="{{_ 's3-secret-key-placeholder'}}") + small.form-text.text-muted {{_ 's3-secret-key-description'}} + + .form-group + label {{_ 's3-ssl-enabled'}} + input.wekan-form-control#s3-ssl-enabled(type="checkbox" checked="{{s3SslEnabled}}" disabled) + small.form-text.text-muted {{_ 's3-ssl-enabled-description'}} + + .form-group + label {{_ 's3-port'}} + input.wekan-form-control#s3-port(type="number" value="{{s3Port}}" readonly) + small.form-text.text-muted {{_ 's3-port-description'}} + + .form-group + button.js-test-s3-connection.btn.btn-secondary {{_ 'test-s3-connection'}} + button.js-save-s3-settings.btn.btn-primary {{_ 'save-s3-settings'}} template(name="storageSettings") .storage-settings diff --git a/client/components/settings/attachmentSettings.js b/client/components/settings/attachmentSettings.js deleted file mode 100644 index 9e84bad5e..000000000 --- a/client/components/settings/attachmentSettings.js +++ /dev/null @@ -1,504 +0,0 @@ -import { ReactiveCache } from '/imports/reactiveCache'; -import { TAPi18n } from '/imports/i18n'; - -import { Meteor } from 'meteor/meteor'; -import { Session } from 'meteor/session'; -import { Tracker } from 'meteor/tracker'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { BlazeComponent } from 'meteor/peerlibrary:blaze-components'; -import { Chart } from 'chart.js'; - -// Global reactive variables for attachment settings -const attachmentSettings = { - loading: new ReactiveVar(false), - showStorageSettings: new ReactiveVar(false), - showMigration: new ReactiveVar(false), - showMonitoring: new ReactiveVar(false), - - // Storage configuration - filesystemPath: new ReactiveVar(''), - attachmentsPath: new ReactiveVar(''), - avatarsPath: new ReactiveVar(''), - gridfsEnabled: new ReactiveVar(false), - s3Enabled: new ReactiveVar(false), - s3Endpoint: new ReactiveVar(''), - s3Bucket: new ReactiveVar(''), - s3Region: new ReactiveVar(''), - s3SslEnabled: new ReactiveVar(false), - s3Port: new ReactiveVar(443), - - // Migration settings - migrationBatchSize: new ReactiveVar(10), - migrationDelayMs: new ReactiveVar(1000), - migrationCpuThreshold: new ReactiveVar(70), - migrationProgress: new ReactiveVar(0), - migrationStatus: new ReactiveVar('idle'), - migrationLog: new ReactiveVar(''), - - // Monitoring data - totalAttachments: new ReactiveVar(0), - filesystemAttachments: new ReactiveVar(0), - gridfsAttachments: new ReactiveVar(0), - s3Attachments: new ReactiveVar(0), - totalSize: new ReactiveVar(0), - filesystemSize: new ReactiveVar(0), - gridfsSize: new ReactiveVar(0), - s3Size: new ReactiveVar(0), - - // Migration state - isMigrationRunning: new ReactiveVar(false), - isMigrationPaused: new ReactiveVar(false), - migrationQueue: new ReactiveVar([]), - currentMigration: new ReactiveVar(null) -}; - -// Main attachment settings component -BlazeComponent.extendComponent({ - onCreated() { - this.loading = attachmentSettings.loading; - this.showStorageSettings = attachmentSettings.showStorageSettings; - this.showMigration = attachmentSettings.showMigration; - this.showMonitoring = attachmentSettings.showMonitoring; - - // Set default sub-menu state - this.showStorageSettings.set(true); - this.showMigration.set(false); - this.showMonitoring.set(false); - - // Load initial data - this.loadStorageConfiguration(); - this.loadMigrationSettings(); - this.loadMonitoringData(); - }, - - // Template helpers for this component - loading() { - return this.loading.get(); - }, - showStorageSettings() { - return this.showStorageSettings.get(); - }, - showMigration() { - return this.showMigration.get(); - }, - showMonitoring() { - return this.showMonitoring.get(); - }, - - events() { - return [ - { - 'click a.js-attachment-storage-settings': this.switchToStorageSettings, - 'click a.js-attachment-migration': this.switchToMigration, - 'click a.js-attachment-monitoring': this.switchToMonitoring, - } - ]; - }, - - switchToStorageSettings(event) { - this.switchMenu(event, 'storage-settings'); - this.showStorageSettings.set(true); - this.showMigration.set(false); - this.showMonitoring.set(false); - }, - - switchToMigration(event) { - this.switchMenu(event, 'attachment-migration'); - this.showStorageSettings.set(false); - this.showMigration.set(true); - this.showMonitoring.set(false); - }, - - switchToMonitoring(event) { - this.switchMenu(event, 'attachment-monitoring'); - this.showStorageSettings.set(false); - this.showMigration.set(false); - this.showMonitoring.set(true); - }, - - switchMenu(event, targetId) { - const target = $(event.target); - if (!target.hasClass('active')) { - this.loading.set(true); - - $('.side-menu li.active').removeClass('active'); - target.parent().addClass('active'); - - // Load data based on target - if (targetId === 'storage-settings') { - this.loadStorageConfiguration(); - } else if (targetId === 'attachment-migration') { - this.loadMigrationSettings(); - } else if (targetId === 'attachment-monitoring') { - this.loadMonitoringData(); - } - - this.loading.set(false); - } - }, - - loadStorageConfiguration() { - Meteor.call('getAttachmentStorageConfiguration', (error, result) => { - if (!error && result) { - attachmentSettings.filesystemPath.set(result.filesystemPath || ''); - attachmentSettings.attachmentsPath.set(result.attachmentsPath || ''); - attachmentSettings.avatarsPath.set(result.avatarsPath || ''); - attachmentSettings.gridfsEnabled.set(result.gridfsEnabled || false); - attachmentSettings.s3Enabled.set(result.s3Enabled || false); - attachmentSettings.s3Endpoint.set(result.s3Endpoint || ''); - attachmentSettings.s3Bucket.set(result.s3Bucket || ''); - attachmentSettings.s3Region.set(result.s3Region || ''); - attachmentSettings.s3SslEnabled.set(result.s3SslEnabled || false); - attachmentSettings.s3Port.set(result.s3Port || 443); - } - }); - }, - - loadMigrationSettings() { - Meteor.call('getAttachmentMigrationSettings', (error, result) => { - if (!error && result) { - attachmentSettings.migrationBatchSize.set(result.batchSize || 10); - attachmentSettings.migrationDelayMs.set(result.delayMs || 1000); - attachmentSettings.migrationCpuThreshold.set(result.cpuThreshold || 70); - attachmentSettings.migrationStatus.set(result.status || 'idle'); - attachmentSettings.migrationProgress.set(result.progress || 0); - } - }); - }, - - loadMonitoringData() { - Meteor.call('getAttachmentMonitoringData', (error, result) => { - if (!error && result) { - attachmentSettings.totalAttachments.set(result.totalAttachments || 0); - attachmentSettings.filesystemAttachments.set(result.filesystemAttachments || 0); - attachmentSettings.gridfsAttachments.set(result.gridfsAttachments || 0); - attachmentSettings.s3Attachments.set(result.s3Attachments || 0); - attachmentSettings.totalSize.set(result.totalSize || 0); - attachmentSettings.filesystemSize.set(result.filesystemSize || 0); - attachmentSettings.gridfsSize.set(result.gridfsSize || 0); - attachmentSettings.s3Size.set(result.s3Size || 0); - } - }); - } -}).register('attachmentSettings'); - -// Storage settings component -BlazeComponent.extendComponent({ - onCreated() { - this.filesystemPath = attachmentSettings.filesystemPath; - this.attachmentsPath = attachmentSettings.attachmentsPath; - this.avatarsPath = attachmentSettings.avatarsPath; - this.gridfsEnabled = attachmentSettings.gridfsEnabled; - this.s3Enabled = attachmentSettings.s3Enabled; - this.s3Endpoint = attachmentSettings.s3Endpoint; - this.s3Bucket = attachmentSettings.s3Bucket; - this.s3Region = attachmentSettings.s3Region; - this.s3SslEnabled = attachmentSettings.s3SslEnabled; - this.s3Port = attachmentSettings.s3Port; - }, - - events() { - return [ - { - 'click button.js-test-s3-connection': this.testS3Connection, - 'click button.js-save-s3-settings': this.saveS3Settings, - 'change input#s3-secret-key': this.updateS3SecretKey - } - ]; - }, - - testS3Connection() { - const secretKey = $('#s3-secret-key').val(); - if (!secretKey) { - alert(TAPi18n.__('s3-secret-key-required')); - return; - } - - Meteor.call('testS3Connection', { secretKey }, (error, result) => { - if (error) { - alert(TAPi18n.__('s3-connection-failed') + ': ' + error.reason); - } else { - alert(TAPi18n.__('s3-connection-success')); - } - }); - }, - - saveS3Settings() { - const secretKey = $('#s3-secret-key').val(); - if (!secretKey) { - alert(TAPi18n.__('s3-secret-key-required')); - return; - } - - Meteor.call('saveS3Settings', { secretKey }, (error, result) => { - if (error) { - alert(TAPi18n.__('s3-settings-save-failed') + ': ' + error.reason); - } else { - alert(TAPi18n.__('s3-settings-saved')); - $('#s3-secret-key').val(''); // Clear the password field - } - }); - }, - - updateS3SecretKey(event) { - // This method can be used to validate the secret key format - const secretKey = event.target.value; - // Add validation logic here if needed - } -}).register('storageSettings'); - -// Migration component -BlazeComponent.extendComponent({ - onCreated() { - this.migrationBatchSize = attachmentSettings.migrationBatchSize; - this.migrationDelayMs = attachmentSettings.migrationDelayMs; - this.migrationCpuThreshold = attachmentSettings.migrationCpuThreshold; - this.migrationProgress = attachmentSettings.migrationProgress; - this.migrationStatus = attachmentSettings.migrationStatus; - this.migrationLog = attachmentSettings.migrationLog; - this.isMigrationRunning = attachmentSettings.isMigrationRunning; - this.isMigrationPaused = attachmentSettings.isMigrationPaused; - - // Subscribe to migration updates - this.subscription = Meteor.subscribe('attachmentMigrationStatus'); - - // Set up reactive updates - this.autorun(() => { - const status = attachmentSettings.migrationStatus.get(); - if (status === 'running') { - this.isMigrationRunning.set(true); - } else { - this.isMigrationRunning.set(false); - } - }); - }, - - onDestroyed() { - if (this.subscription) { - this.subscription.stop(); - } - }, - - events() { - return [ - { - 'click button.js-migrate-all-to-filesystem': () => this.startMigration('filesystem'), - 'click button.js-migrate-all-to-gridfs': () => this.startMigration('gridfs'), - 'click button.js-migrate-all-to-s3': () => this.startMigration('s3'), - 'click button.js-pause-migration': this.pauseMigration, - 'click button.js-resume-migration': this.resumeMigration, - 'click button.js-stop-migration': this.stopMigration, - 'change input#migration-batch-size': this.updateBatchSize, - 'change input#migration-delay-ms': this.updateDelayMs, - 'change input#migration-cpu-threshold': this.updateCpuThreshold - } - ]; - }, - - startMigration(targetStorage) { - const batchSize = parseInt($('#migration-batch-size').val()) || 10; - const delayMs = parseInt($('#migration-delay-ms').val()) || 1000; - const cpuThreshold = parseInt($('#migration-cpu-threshold').val()) || 70; - - Meteor.call('startAttachmentMigration', { - targetStorage, - batchSize, - delayMs, - cpuThreshold - }, (error, result) => { - if (error) { - alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason); - } else { - this.addToLog(TAPi18n.__('migration-started') + ': ' + targetStorage); - } - }); - }, - - pauseMigration() { - Meteor.call('pauseAttachmentMigration', (error, result) => { - if (error) { - alert(TAPi18n.__('migration-pause-failed') + ': ' + error.reason); - } else { - this.addToLog(TAPi18n.__('migration-paused')); - } - }); - }, - - resumeMigration() { - Meteor.call('resumeAttachmentMigration', (error, result) => { - if (error) { - alert(TAPi18n.__('migration-resume-failed') + ': ' + error.reason); - } else { - this.addToLog(TAPi18n.__('migration-resumed')); - } - }); - }, - - stopMigration() { - if (confirm(TAPi18n.__('migration-stop-confirm'))) { - Meteor.call('stopAttachmentMigration', (error, result) => { - if (error) { - alert(TAPi18n.__('migration-stop-failed') + ': ' + error.reason); - } else { - this.addToLog(TAPi18n.__('migration-stopped')); - } - }); - } - }, - - updateBatchSize(event) { - const value = parseInt(event.target.value); - if (value >= 1 && value <= 100) { - attachmentSettings.migrationBatchSize.set(value); - } - }, - - updateDelayMs(event) { - const value = parseInt(event.target.value); - if (value >= 100 && value <= 10000) { - attachmentSettings.migrationDelayMs.set(value); - } - }, - - updateCpuThreshold(event) { - const value = parseInt(event.target.value); - if (value >= 10 && value <= 90) { - attachmentSettings.migrationCpuThreshold.set(value); - } - }, - - addToLog(message) { - const timestamp = new Date().toISOString(); - const currentLog = attachmentSettings.migrationLog.get(); - const newLog = `[${timestamp}] ${message}\n${currentLog}`; - attachmentSettings.migrationLog.set(newLog); - } -}).register('attachmentMigration'); - -// Monitoring component -BlazeComponent.extendComponent({ - onCreated() { - this.totalAttachments = attachmentSettings.totalAttachments; - this.filesystemAttachments = attachmentSettings.filesystemAttachments; - this.gridfsAttachments = attachmentSettings.gridfsAttachments; - this.s3Attachments = attachmentSettings.s3Attachments; - this.totalSize = attachmentSettings.totalSize; - this.filesystemSize = attachmentSettings.filesystemSize; - this.gridfsSize = attachmentSettings.gridfsSize; - this.s3Size = attachmentSettings.s3Size; - - // Subscribe to monitoring updates - this.subscription = Meteor.subscribe('attachmentMonitoringData'); - - // Set up chart - this.autorun(() => { - this.updateChart(); - }); - }, - - onDestroyed() { - if (this.subscription) { - this.subscription.stop(); - } - }, - - events() { - return [ - { - 'click button.js-refresh-monitoring': this.refreshMonitoring, - 'click button.js-export-monitoring': this.exportMonitoring - } - ]; - }, - - refreshMonitoring() { - Meteor.call('refreshAttachmentMonitoringData', (error, result) => { - if (error) { - alert(TAPi18n.__('monitoring-refresh-failed') + ': ' + error.reason); - } - }); - }, - - exportMonitoring() { - Meteor.call('exportAttachmentMonitoringData', (error, result) => { - if (error) { - alert(TAPi18n.__('monitoring-export-failed') + ': ' + error.reason); - } else { - // Download the exported data - const blob = new Blob([JSON.stringify(result, null, 2)], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'wekan-attachment-monitoring.json'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } - }); - }, - - updateChart() { - const ctx = document.getElementById('storage-distribution-chart'); - if (!ctx) return; - - const filesystemCount = this.filesystemAttachments.get(); - const gridfsCount = this.gridfsAttachments.get(); - const s3Count = this.s3Attachments.get(); - - if (this.chart) { - this.chart.destroy(); - } - - this.chart = new Chart(ctx, { - type: 'doughnut', - data: { - labels: [ - TAPi18n.__('filesystem-storage'), - TAPi18n.__('gridfs-storage'), - TAPi18n.__('s3-storage') - ], - datasets: [{ - data: [filesystemCount, gridfsCount, s3Count], - backgroundColor: [ - '#28a745', - '#007bff', - '#ffc107' - ] - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - position: 'bottom' - } - } - } - }); - } -}).register('attachmentMonitoring'); - -// Template helpers for attachmentSettings -Template.attachmentSettings.helpers({ - loading() { - const instance = Template.instance(); - return instance.loading && instance.loading.get(); - }, - showStorageSettings() { - const instance = Template.instance(); - return instance.showStorageSettings && instance.showStorageSettings.get(); - }, - showMigration() { - const instance = Template.instance(); - return instance.showMigration && instance.showMigration.get(); - }, - showMonitoring() { - const instance = Template.instance(); - return instance.showMonitoring && instance.showMonitoring.get(); - }, -}); - -// Export the attachment settings for use in other components -export { attachmentSettings }; diff --git a/client/components/settings/cronSettings.jade b/client/components/settings/cronSettings.jade index f19c61c8e..b53dd41cb 100644 --- a/client/components/settings/cronSettings.jade +++ b/client/components/settings/cronSettings.jade @@ -1,35 +1,47 @@ template(name="cronSettings") - .cron-settings-content - .settings-tabs - ul.tab-nav - li(class="{{#if showMigrations}}active{{/if}}") - a.js-cron-migrations(data-id="cron-migrations") - i.fa.fa-database - | {{_ 'cron-migrations'}} - li(class="{{#if showBoardOperations}}active{{/if}}") - a.js-cron-board-operations(data-id="cron-board-operations") - i.fa.fa-tasks - | {{_ 'board-operations'}} - li(class="{{#if showJobs}}active{{/if}}") - a.js-cron-jobs(data-id="cron-jobs") - i.fa.fa-clock-o - | {{_ 'cron-jobs'}} - li(class="{{#if showAddJob}}active{{/if}}") - a.js-cron-add(data-id="cron-add") - i.fa.fa-plus - | {{_ 'add-cron-job'}} + ul#cron-setting.setting-detail + li + h3 {{_ 'cron-migrations'}} + .form-group + label {{_ 'migration-status'}} + .status-indicator + span.status-label {{_ 'status'}}: + span.status-value {{migrationStatus}} + .progress-section + .progress + .progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100") + | {{migrationProgress}}% + .progress-text + | {{migrationProgress}}% {{_ 'complete'}} + + .form-group + button.js-start-all-migrations.btn.btn-primary {{_ 'start-all-migrations'}} + button.js-pause-all-migrations.btn.btn-warning {{_ 'pause-all-migrations'}} + button.js-stop-all-migrations.btn.btn-danger {{_ 'stop-all-migrations'}} - .tab-content - if loading - +spinner - else if showMigrations - +cronMigrations - else if showBoardOperations - +cronBoardOperations - else if showJobs - +cronJobs - else if showAddJob - +cronAddJob + li + h3 {{_ 'board-operations'}} + .form-group + label {{_ 'scheduled-board-operations'}} + button.js-schedule-board-cleanup.btn.btn-primary {{_ 'schedule-board-cleanup'}} + button.js-schedule-board-archive.btn.btn-warning {{_ 'schedule-board-archive'}} + button.js-schedule-board-backup.btn.btn-info {{_ 'schedule-board-backup'}} + + li + h3 {{_ 'cron-jobs'}} + .form-group + label {{_ 'active-cron-jobs'}} + each cronJobs + .job-item + .job-info + .job-name {{name}} + .job-schedule {{schedule}} + .job-description {{description}} + .job-actions + button.js-pause-job.btn.btn-sm.btn-warning(data-job-id="{{_id}}") {{_ 'pause'}} + button.js-delete-job.btn.btn-sm.btn-danger(data-job-id="{{_id}}") {{_ 'delete'}} + .add-job-section + button.js-add-cron-job.btn.btn-success {{_ 'add-cron-job'}} template(name="cronMigrations") .cron-migrations @@ -51,7 +63,7 @@ template(name="cronMigrations") .migration-progress .progress-overview .progress-bar - .progress-fill(style="width: {{migrationProgress}}%") + .progress-fill(style="width: {{migrationProgress}}%") .progress-text {{migrationProgress}}% .progress-label {{_ 'overall-progress'}} diff --git a/client/components/settings/cronSettings.js b/client/components/settings/cronSettings.js deleted file mode 100644 index 7fb1af2ce..000000000 --- a/client/components/settings/cronSettings.js +++ /dev/null @@ -1,552 +0,0 @@ -import { Template } from 'meteor/templating'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Meteor } from 'meteor/meteor'; -import { TAPi18n } from '/imports/i18n'; - -// Reactive variables for cron settings -const migrationProgress = new ReactiveVar(0); -const migrationStatus = new ReactiveVar(''); -const migrationCurrentStep = new ReactiveVar(''); -const migrationSteps = new ReactiveVar([]); -const isMigrating = new ReactiveVar(false); -const cronJobs = new ReactiveVar([]); - -Template.cronSettings.onCreated(function() { - this.loading = new ReactiveVar(true); - this.showMigrations = new ReactiveVar(true); - this.showBoardOperations = new ReactiveVar(false); - this.showJobs = new ReactiveVar(false); - this.showAddJob = new ReactiveVar(false); - - // Board operations pagination - this.currentPage = new ReactiveVar(1); - this.pageSize = new ReactiveVar(20); - this.searchTerm = new ReactiveVar(''); - this.boardOperations = new ReactiveVar([]); - this.operationStats = new ReactiveVar({}); - this.pagination = new ReactiveVar({}); - this.queueStats = new ReactiveVar({}); - this.systemResources = new ReactiveVar({}); - this.boardMigrationStats = new ReactiveVar({}); - - // Load initial data - loadCronData(this); -}); - -Template.cronSettings.helpers({ - loading() { - const instance = Template.instance(); - return instance && instance.loading ? instance.loading.get() : true; - }, - - showMigrations() { - const instance = Template.instance(); - return instance && instance.showMigrations ? instance.showMigrations.get() : true; - }, - - showBoardOperations() { - const instance = Template.instance(); - return instance && instance.showBoardOperations ? instance.showBoardOperations.get() : false; - }, - - showJobs() { - const instance = Template.instance(); - return instance && instance.showJobs ? instance.showJobs.get() : false; - }, - - showAddJob() { - const instance = Template.instance(); - return instance && instance.showAddJob ? instance.showAddJob.get() : false; - }, - - migrationProgress() { - return migrationProgress.get(); - }, - - migrationStatus() { - return migrationStatus.get(); - }, - - migrationCurrentStep() { - return migrationCurrentStep.get(); - }, - - migrationSteps() { - const steps = migrationSteps.get(); - const currentStep = migrationCurrentStep.get(); - - return steps.map(step => ({ - ...step, - isCurrentStep: step.name === currentStep - })); - }, - - cronJobs() { - return cronJobs.get(); - }, - - formatDate(date) { - if (!date) return '-'; - return new Date(date).toLocaleString(); - }, - - boardOperations() { - const instance = Template.instance(); - return instance && instance.boardOperations ? instance.boardOperations.get() : []; - }, - - operationStats() { - const instance = Template.instance(); - return instance && instance.operationStats ? instance.operationStats.get() : {}; - }, - - pagination() { - const instance = Template.instance(); - return instance && instance.pagination ? instance.pagination.get() : {}; - }, - - queueStats() { - const instance = Template.instance(); - return instance && instance.queueStats ? instance.queueStats.get() : {}; - }, - - systemResources() { - const instance = Template.instance(); - return instance && instance.systemResources ? instance.systemResources.get() : {}; - }, - - boardMigrationStats() { - const instance = Template.instance(); - return instance && instance.boardMigrationStats ? instance.boardMigrationStats.get() : {}; - }, - - formatDateTime(date) { - if (!date) return '-'; - return new Date(date).toLocaleString(); - }, - - formatDuration(startTime, endTime) { - if (!startTime) return '-'; - const start = new Date(startTime); - const end = endTime ? new Date(endTime) : new Date(); - const diffMs = end - start; - const diffMins = Math.floor(diffMs / 60000); - const diffSecs = Math.floor((diffMs % 60000) / 1000); - - if (diffMins > 0) { - return `${diffMins}m ${diffSecs}s`; - } else { - return `${diffSecs}s`; - } - } -}); - -Template.cronSettings.switchMenu = function(event, targetID) { - const instance = Template.instance(); - - // Reset all tabs - instance.showMigrations.set(false); - instance.showBoardOperations.set(false); - instance.showJobs.set(false); - instance.showAddJob.set(false); - - // Set the selected tab - if (targetID === 'cron-migrations') { - instance.showMigrations.set(true); - } else if (targetID === 'cron-board-operations') { - instance.showBoardOperations.set(true); - } else if (targetID === 'cron-jobs') { - instance.showJobs.set(true); - } else if (targetID === 'cron-add') { - instance.showAddJob.set(true); - } -}; - -Template.cronSettings.events({ - 'click .js-cron-migrations'(event) { - event.preventDefault(); - const instance = Template.instance(); - instance.showMigrations.set(true); - instance.showJobs.set(false); - instance.showAddJob.set(false); - }, - - 'click .js-cron-board-operations'(event) { - event.preventDefault(); - const instance = Template.instance(); - instance.showMigrations.set(false); - instance.showBoardOperations.set(true); - instance.showJobs.set(false); - instance.showAddJob.set(false); - loadBoardOperations(instance); - }, - - 'click .js-cron-jobs'(event) { - event.preventDefault(); - const instance = Template.instance(); - instance.showMigrations.set(false); - instance.showBoardOperations.set(false); - instance.showJobs.set(true); - instance.showAddJob.set(false); - loadCronJobs(instance); - }, - - 'click .js-cron-add'(event) { - event.preventDefault(); - const instance = Template.instance(); - instance.showMigrations.set(false); - instance.showJobs.set(false); - instance.showAddJob.set(true); - }, - - 'click .js-start-all-migrations'(event) { - event.preventDefault(); - Meteor.call('cron.startAllMigrations', (error, result) => { - if (error) { - console.error('Failed to start migrations:', error); - alert('Failed to start migrations: ' + error.message); - } else { - // Migrations started successfully - pollMigrationProgress(Template.instance()); - } - }); - }, - - 'click .js-pause-all-migrations'(event) { - event.preventDefault(); - // Pause all migration cron jobs - const jobs = cronJobs.get(); - jobs.forEach(job => { - if (job.name.startsWith('migration_')) { - Meteor.call('cron.pauseJob', job.name); - } - }); - }, - - 'click .js-stop-all-migrations'(event) { - event.preventDefault(); - // Stop all migration cron jobs - const jobs = cronJobs.get(); - jobs.forEach(job => { - if (job.name.startsWith('migration_')) { - Meteor.call('cron.stopJob', job.name); - } - }); - }, - - 'click .js-refresh-jobs'(event) { - event.preventDefault(); - loadCronJobs(Template.instance()); - }, - - 'click .js-start-job'(event) { - event.preventDefault(); - const jobName = $(event.currentTarget).data('job'); - Meteor.call('cron.startJob', jobName, (error, result) => { - if (error) { - console.error('Failed to start job:', error); - alert('Failed to start job: ' + error.message); - } else { - console.log('Job started successfully'); - loadCronJobs(Template.instance()); - } - }); - }, - - 'click .js-pause-job'(event) { - event.preventDefault(); - const jobName = $(event.currentTarget).data('job'); - Meteor.call('cron.pauseJob', jobName, (error, result) => { - if (error) { - console.error('Failed to pause job:', error); - alert('Failed to pause job: ' + error.message); - } else { - console.log('Job paused successfully'); - loadCronJobs(Template.instance()); - } - }); - }, - - 'click .js-stop-job'(event) { - event.preventDefault(); - const jobName = $(event.currentTarget).data('job'); - Meteor.call('cron.stopJob', jobName, (error, result) => { - if (error) { - console.error('Failed to stop job:', error); - alert('Failed to stop job: ' + error.message); - } else { - console.log('Job stopped successfully'); - loadCronJobs(Template.instance()); - } - }); - }, - - 'click .js-remove-job'(event) { - event.preventDefault(); - const jobName = $(event.currentTarget).data('job'); - if (confirm('Are you sure you want to remove this job?')) { - Meteor.call('cron.removeJob', jobName, (error, result) => { - if (error) { - console.error('Failed to remove job:', error); - alert('Failed to remove job: ' + error.message); - } else { - console.log('Job removed successfully'); - loadCronJobs(Template.instance()); - } - }); - } - }, - - 'submit .js-add-cron-job-form'(event) { - event.preventDefault(); - const form = event.currentTarget; - const formData = new FormData(form); - - const jobData = { - name: formData.get('name'), - description: formData.get('description'), - schedule: formData.get('schedule'), - weight: parseInt(formData.get('weight')) - }; - - Meteor.call('cron.addJob', jobData, (error, result) => { - if (error) { - console.error('Failed to add job:', error); - alert('Failed to add job: ' + error.message); - } else { - console.log('Job added successfully'); - form.reset(); - Template.instance().showJobs.set(true); - Template.instance().showAddJob.set(false); - loadCronJobs(Template.instance()); - } - }); - }, - - 'click .js-cancel-add-job'(event) { - event.preventDefault(); - const instance = Template.instance(); - instance.showJobs.set(true); - instance.showAddJob.set(false); - }, - - 'click .js-refresh-board-operations'(event) { - event.preventDefault(); - loadBoardOperations(Template.instance()); - }, - - 'click .js-start-test-operation'(event) { - event.preventDefault(); - const testBoardId = 'test-board-' + Date.now(); - const operationData = { - sourceBoardId: 'source-board', - targetBoardId: 'target-board', - copyOptions: { includeCards: true, includeAttachments: true } - }; - - Meteor.call('cron.startBoardOperation', testBoardId, 'copy_board', operationData, (error, result) => { - if (error) { - console.error('Failed to start test operation:', error); - alert('Failed to start test operation: ' + error.message); - } else { - console.log('Test operation started:', result); - loadBoardOperations(Template.instance()); - } - }); - }, - - 'input .js-search-board-operations'(event) { - const searchTerm = $(event.currentTarget).val(); - const instance = Template.instance(); - instance.searchTerm.set(searchTerm); - instance.currentPage.set(1); - loadBoardOperations(instance); - }, - - 'click .js-prev-page'(event) { - event.preventDefault(); - const instance = Template.instance(); - const currentPage = instance.currentPage.get(); - if (currentPage > 1) { - instance.currentPage.set(currentPage - 1); - loadBoardOperations(instance); - } - }, - - 'click .js-next-page'(event) { - event.preventDefault(); - const instance = Template.instance(); - const currentPage = instance.currentPage.get(); - const pagination = instance.pagination.get(); - if (currentPage < pagination.totalPages) { - instance.currentPage.set(currentPage + 1); - loadBoardOperations(instance); - } - }, - - 'click .js-pause-operation'(event) { - event.preventDefault(); - const operationId = $(event.currentTarget).data('operation'); - // Implementation for pausing operation - console.log('Pause operation:', operationId); - }, - - 'click .js-resume-operation'(event) { - event.preventDefault(); - const operationId = $(event.currentTarget).data('operation'); - // Implementation for resuming operation - console.log('Resume operation:', operationId); - }, - - 'click .js-stop-operation'(event) { - event.preventDefault(); - const operationId = $(event.currentTarget).data('operation'); - if (confirm('Are you sure you want to stop this operation?')) { - // Implementation for stopping operation - console.log('Stop operation:', operationId); - } - }, - - 'click .js-view-details'(event) { - event.preventDefault(); - const operationId = $(event.currentTarget).data('operation'); - // Implementation for viewing operation details - console.log('View details for operation:', operationId); - }, - - 'click .js-force-board-scan'(event) { - event.preventDefault(); - Meteor.call('cron.forceBoardMigrationScan', (error, result) => { - if (error) { - console.error('Failed to force board scan:', error); - alert('Failed to force board scan: ' + error.message); - } else { - // Board scan started successfully - // Refresh the data - loadBoardOperations(Template.instance()); - } - }); - } -}); - -// Helper functions for cron settings -function loadCronData(instance) { - instance.loading.set(true); - - // Load migration progress - Meteor.call('cron.getMigrationProgress', (error, result) => { - if (result) { - migrationProgress.set(result.progress); - migrationStatus.set(result.status); - migrationCurrentStep.set(result.currentStep); - migrationSteps.set(result.steps); - isMigrating.set(result.isMigrating); - } - }); - - // Load cron jobs - loadCronJobs(instance); - - instance.loading.set(false); -} - -function loadCronJobs(instance) { - Meteor.call('cron.getJobs', (error, result) => { - if (result) { - cronJobs.set(result); - } - }); -} - -function loadBoardOperations(instance) { - const page = instance.currentPage.get(); - const limit = instance.pageSize.get(); - const searchTerm = instance.searchTerm.get(); - - Meteor.call('cron.getAllBoardOperations', page, limit, searchTerm, (error, result) => { - if (result) { - instance.boardOperations.set(result.operations); - instance.pagination.set({ - total: result.total, - page: result.page, - limit: result.limit, - totalPages: result.totalPages, - start: ((result.page - 1) * result.limit) + 1, - end: Math.min(result.page * result.limit, result.total), - hasPrev: result.page > 1, - hasNext: result.page < result.totalPages - }); - } - }); - - // Load operation stats - Meteor.call('cron.getBoardOperationStats', (error, result) => { - if (result) { - instance.operationStats.set(result); - } - }); - - // Load queue stats - Meteor.call('cron.getQueueStats', (error, result) => { - if (result) { - instance.queueStats.set(result); - } - }); - - // Load system resources - Meteor.call('cron.getSystemResources', (error, result) => { - if (result) { - instance.systemResources.set(result); - } - }); - - // Load board migration stats - Meteor.call('cron.getBoardMigrationStats', (error, result) => { - if (result) { - instance.boardMigrationStats.set(result); - } - }); -} - -function pollMigrationProgress(instance) { - const pollInterval = setInterval(() => { - Meteor.call('cron.getMigrationProgress', (error, result) => { - if (result) { - migrationProgress.set(result.progress); - migrationStatus.set(result.status); - migrationCurrentStep.set(result.currentStep); - migrationSteps.set(result.steps); - isMigrating.set(result.isMigrating); - - // Stop polling if migration is complete - if (!result.isMigrating && result.progress === 100) { - clearInterval(pollInterval); - } - } - }); - }, 1000); -} - -// Template helpers for cronSettings -Template.cronSettings.helpers({ - loading() { - const instance = Template.instance(); - return instance.loading && instance.loading.get(); - }, - showMigrations() { - const instance = Template.instance(); - return instance.showMigrations && instance.showMigrations.get(); - }, - showBoardOperations() { - const instance = Template.instance(); - return instance.showBoardOperations && instance.showBoardOperations.get(); - }, - showJobs() { - const instance = Template.instance(); - return instance.showJobs && instance.showJobs.get(); - }, - showAddJob() { - const instance = Template.instance(); - return instance.showAddJob && instance.showAddJob.get(); - }, -}); diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade index 2507be70f..dbd7a92d5 100644 --- a/client/components/settings/settingBody.jade +++ b/client/components/settings/settingBody.jade @@ -4,115 +4,224 @@ template(name="setting") | {{_ 'error-notAuthorized'}} else .content-title.ext-box - if loading.get - +spinner - else if generalSetting.get + if isGeneralSetting span i.fa.fa-sign-in | {{_ 'registration'}} - else if emailSetting.get + else if isEmailSetting span i.fa.fa-envelope | {{_ 'email'}} - else if accountSetting.get + else if isAccountSetting span i.fa.fa-users | {{_ 'accounts'}} - else if tableVisibilityModeSetting.get + else if isTableVisibilityModeSetting span i.fa.fa-eye | {{_ 'tableVisibilityMode'}} - else if announcementSetting.get + else if isAnnouncementSetting span i.fa.fa-bullhorn | {{_ 'admin-announcement'}} - else if accessibilitySetting.get + else if isAccessibilitySetting span i.fa.fa-universal-access | {{_ 'accessibility'}} - else if layoutSetting.get + else if isLayoutSetting span i.fa.fa-object-group | {{_ 'layout'}} - else if webhookSetting.get + else if isWebhookSetting span i.fa.fa-globe | {{_ 'global-webhook'}} - else if attachmentSettings.get + else if isAttachmentSettings span i.fa.fa-paperclip | {{_ 'attachments'}} - else if cronSettings.get + else if isCronSettings span i.fa.fa-clock-o | {{_ 'cron'}} .content-body .side-menu ul - li(class="{{#if generalSetting}}active{{/if}}") + li(class="{{#if isGeneralSetting}}active{{/if}}") a.js-setting-menu(data-id="registration-setting") i.fa.fa-sign-in | {{_ 'registration'}} unless isSandstorm - li(class="{{#if emailSetting}}active{{/if}}") + li(class="{{#if isEmailSetting}}active{{/if}}") a.js-setting-menu(data-id="email-setting") i.fa.fa-envelope | {{_ 'email'}} - li(class="{{#if accountSetting}}active{{/if}}") + li(class="{{#if isAccountSetting}}active{{/if}}") a.js-setting-menu(data-id="account-setting") i.fa.fa-users | {{_ 'accounts'}} - li(class="{{#if tableVisibilityModeSetting}}active{{/if}}") + li(class="{{#if isTableVisibilityModeSetting}}active{{/if}}") a.js-setting-menu(data-id="tableVisibilityMode-setting") i.fa.fa-eye | {{_ 'tableVisibilityMode'}} - li(class="{{#if announcementSetting}}active{{/if}}") + li(class="{{#if isAnnouncementSetting}}active{{/if}}") a.js-setting-menu(data-id="announcement-setting") i.fa.fa-bullhorn | {{_ 'admin-announcement'}} - li(class="{{#if accessibilitySetting}}active{{/if}}") + li(class="{{#if isAccessibilitySetting}}active{{/if}}") a.js-setting-menu(data-id="accessibility-setting") i.fa.fa-universal-access | {{_ 'accessibility'}} - li(class="{{#if layoutSetting}}active{{/if}}") + li(class="{{#if isLayoutSetting}}active{{/if}}") a.js-setting-menu(data-id="layout-setting") i.fa.fa-object-group | {{_ 'layout'}} - li(class="{{#if webhookSetting}}active{{/if}}") + li(class="{{#if isWebhookSetting}}active{{/if}}") a.js-setting-menu(data-id="webhook-setting") i.fa.fa-globe | {{_ 'global-webhook'}} - li(class="{{#if attachmentSettings}}active{{/if}}") + li(class="{{#if isAttachmentSettings}}active{{/if}}") a.js-setting-menu(data-id="attachment-settings") i.fa.fa-paperclip | {{_ 'attachments'}} - li(class="{{#if cronSettings}}active{{/if}}") + li(class="{{#if isCronSettings}}active{{/if}}") a.js-setting-menu(data-id="cron-settings") i.fa.fa-clock-o | {{_ 'cron'}} .main-body - if loading.get + if isLoading +spinner - else if attachmentSettings.get - +attachmentSettings - else if cronSettings.get - +cronSettings - else if generalSetting.get + else if isAttachmentSettings + ul#attachment-setting.setting-detail + li + h3 {{_ 'attachment-storage-configuration'}} + .form-group + label {{_ 'writable-path'}} + input.wekan-form-control#filesystem-path(type="text" value="{{filesystemPath}}" readonly) + small.form-text.text-muted {{_ 'filesystem-path-description'}} + + .form-group + label {{_ 'attachments-path'}} + input.wekan-form-control#attachments-path(type="text" value="{{attachmentsPath}}" readonly) + small.form-text.text-muted {{_ 'attachments-path-description'}} + + .form-group + label {{_ 'avatars-path'}} + input.wekan-form-control#avatars-path(type="text" value="{{avatarsPath}}" readonly) + small.form-text.text-muted {{_ 'avatars-path-description'}} + + li + h3 {{_ 'mongodb-gridfs-storage'}} + .form-group + label {{_ 'gridfs-enabled'}} + input.wekan-form-control#gridfs-enabled(type="checkbox" checked="{{gridfsEnabled}}" disabled) + small.form-text.text-muted {{_ 'gridfs-enabled-description'}} + + li + h3 {{_ 's3-minio-storage'}} + .form-group + label {{_ 's3-enabled'}} + input.wekan-form-control#s3-enabled(type="checkbox" checked="{{s3Enabled}}" disabled) + small.form-text.text-muted {{_ 's3-enabled-description'}} + + .form-group + label {{_ 's3-endpoint'}} + input.wekan-form-control#s3-endpoint(type="text" value="{{s3Endpoint}}" readonly) + small.form-text.text-muted {{_ 's3-endpoint-description'}} + + .form-group + label {{_ 's3-bucket'}} + input.wekan-form-control#s3-bucket(type="text" value="{{s3Bucket}}" readonly) + small.form-text.text-muted {{_ 's3-bucket-description'}} + + .form-group + label {{_ 's3-region'}} + input.wekan-form-control#s3-region(type="text" value="{{s3Region}}" readonly) + small.form-text.text-muted {{_ 's3-region-description'}} + + .form-group + label {{_ 's3-access-key'}} + input.wekan-form-control#s3-access-key(type="text" placeholder="{{_ 's3-access-key-placeholder'}}" readonly) + small.form-text.text-muted {{_ 's3-access-key-description'}} + + .form-group + label {{_ 's3-secret-key'}} + input.wekan-form-control#s3-secret-key(type="password" placeholder="{{_ 's3-secret-key-placeholder'}}") + small.form-text.text-muted {{_ 's3-secret-key-description'}} + + .form-group + label {{_ 's3-ssl-enabled'}} + input.wekan-form-control#s3-ssl-enabled(type="checkbox" checked="{{s3SslEnabled}}" disabled) + small.form-text.text-muted {{_ 's3-ssl-enabled-description'}} + + .form-group + label {{_ 's3-port'}} + input.wekan-form-control#s3-port(type="number" value="{{s3Port}}" readonly) + small.form-text.text-muted {{_ 's3-port-description'}} + + .form-group + button.js-test-s3-connection.btn.btn-secondary {{_ 'test-s3-connection'}} + button.js-save-s3-settings.btn.btn-primary {{_ 'save-s3-settings'}} + else if isCronSettings + ul#cron-setting.setting-detail + li + h3 {{_ 'cron-migrations'}} + .form-group + label {{_ 'migration-status'}} + .status-indicator + span.status-label {{_ 'status'}}: + span.status-value {{migrationStatus}} + .progress-section + .progress + .progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100") + | {{migrationProgress}}% + .progress-text + | {{migrationProgress}}% {{_ 'complete'}} + + .form-group + button.js-start-all-migrations.btn.btn-primary {{_ 'start-all-migrations'}} + button.js-pause-all-migrations.btn.btn-warning {{_ 'pause-all-migrations'}} + button.js-stop-all-migrations.btn.btn-danger {{_ 'stop-all-migrations'}} + + li + h3 {{_ 'board-operations'}} + .form-group + label {{_ 'scheduled-board-operations'}} + button.js-schedule-board-cleanup.btn.btn-primary {{_ 'schedule-board-cleanup'}} + button.js-schedule-board-archive.btn.btn-warning {{_ 'schedule-board-archive'}} + button.js-schedule-board-backup.btn.btn-info {{_ 'schedule-board-backup'}} + + li + h3 {{_ 'cron-jobs'}} + .form-group + label {{_ 'active-cron-jobs'}} + each cronJobs + .job-item + .job-info + .job-name {{name}} + .job-schedule {{schedule}} + .job-description {{description}} + .job-actions + button.js-pause-job.btn.btn-sm.btn-warning(data-job-id="{{_id}}") {{_ 'pause'}} + button.js-delete-job.btn.btn-sm.btn-danger(data-job-id="{{_id}}") {{_ 'delete'}} + .add-job-section + button.js-add-cron-job.btn.btn-success {{_ 'add-cron-job'}} + else if isGeneralSetting +general - else if emailSetting.get + else if isEmailSetting unless isSandstorm +email - else if accountSetting.get + else if isAccountSetting +accountSettings - else if tableVisibilityModeSetting.get + else if isTableVisibilityModeSetting +tableVisibilityModeSettings - else if announcementSetting.get + else if isAnnouncementSetting +announcementSettings - else if accessibilitySetting.get + else if isAccessibilitySetting +accessibilitySettings - else if layoutSetting.get + else if isLayoutSetting +layoutSettings - else if webhookSetting.get + else if isWebhookSetting +webhookSettings template(name="webhookSettings") diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js index 8830b8db5..4a9517f81 100644 --- a/client/components/settings/settingBody.js +++ b/client/components/settings/settingBody.js @@ -34,11 +34,239 @@ BlazeComponent.extendComponent({ setError(error) { this.error.set(error); }, + + // Template helpers moved to BlazeComponent - using different names to avoid conflicts + isGeneralSetting() { + return this.generalSetting && this.generalSetting.get(); + }, + isEmailSetting() { + return this.emailSetting && this.emailSetting.get(); + }, + isAccountSetting() { + return this.accountSetting && this.accountSetting.get(); + }, + isTableVisibilityModeSetting() { + return this.tableVisibilityModeSetting && this.tableVisibilityModeSetting.get(); + }, + isAnnouncementSetting() { + return this.announcementSetting && this.announcementSetting.get(); + }, + isAccessibilitySetting() { + return this.accessibilitySetting && this.accessibilitySetting.get(); + }, + isLayoutSetting() { + return this.layoutSetting && this.layoutSetting.get(); + }, + isWebhookSetting() { + return this.webhookSetting && this.webhookSetting.get(); + }, + isAttachmentSettings() { + return this.attachmentSettings && this.attachmentSettings.get(); + }, + isCronSettings() { + return this.cronSettings && this.cronSettings.get(); + }, + isLoading() { + return this.loading && this.loading.get(); + }, + + // Attachment settings helpers + filesystemPath() { + return process.env.WRITABLE_PATH || '/data'; + }, + + attachmentsPath() { + const writablePath = process.env.WRITABLE_PATH || '/data'; + return `${writablePath}/attachments`; + }, + + avatarsPath() { + const writablePath = process.env.WRITABLE_PATH || '/data'; + return `${writablePath}/avatars`; + }, + + gridfsEnabled() { + return process.env.GRIDFS_ENABLED === 'true'; + }, + + s3Enabled() { + return process.env.S3_ENABLED === 'true'; + }, + + s3Endpoint() { + return process.env.S3_ENDPOINT || ''; + }, + + s3Bucket() { + return process.env.S3_BUCKET || ''; + }, + + s3Region() { + return process.env.S3_REGION || ''; + }, + + s3SslEnabled() { + return process.env.S3_SSL_ENABLED === 'true'; + }, + + s3Port() { + return process.env.S3_PORT || 443; + }, + + // Cron settings helpers + migrationStatus() { + return 'idle'; // Placeholder + }, + + migrationProgress() { + return 0; // Placeholder + }, + + cronJobs() { + return []; // Placeholder + }, setLoading(w) { this.loading.set(w); }, + // Event handlers for attachment settings + 'click button.js-test-s3-connection'(event) { + event.preventDefault(); + const secretKey = $('#s3-secret-key').val(); + if (!secretKey) { + alert(TAPi18n.__('s3-secret-key-required')); + return; + } + + Meteor.call('testS3Connection', { secretKey }, (error, result) => { + if (error) { + alert(TAPi18n.__('s3-connection-failed') + ': ' + error.reason); + } else { + alert(TAPi18n.__('s3-connection-success')); + } + }); + }, + + 'click button.js-save-s3-settings'(event) { + event.preventDefault(); + const secretKey = $('#s3-secret-key').val(); + if (!secretKey) { + alert(TAPi18n.__('s3-secret-key-required')); + return; + } + + Meteor.call('saveS3Settings', { secretKey }, (error, result) => { + if (error) { + alert(TAPi18n.__('s3-settings-save-failed') + ': ' + error.reason); + } else { + alert(TAPi18n.__('s3-settings-saved')); + $('#s3-secret-key').val(''); // Clear the password field + } + }); + }, + + // Event handlers for cron settings + 'click button.js-start-all-migrations'(event) { + event.preventDefault(); + Meteor.call('startAllMigrations', (error, result) => { + if (error) { + alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason); + } else { + alert(TAPi18n.__('migration-started')); + } + }); + }, + + 'click button.js-pause-all-migrations'(event) { + event.preventDefault(); + Meteor.call('pauseAllMigrations', (error, result) => { + if (error) { + alert(TAPi18n.__('migration-pause-failed') + ': ' + error.reason); + } else { + alert(TAPi18n.__('migration-paused')); + } + }); + }, + + 'click button.js-stop-all-migrations'(event) { + event.preventDefault(); + if (confirm(TAPi18n.__('migration-stop-confirm'))) { + Meteor.call('stopAllMigrations', (error, result) => { + if (error) { + alert(TAPi18n.__('migration-stop-failed') + ': ' + error.reason); + } else { + alert(TAPi18n.__('migration-stopped')); + } + }); + } + }, + + 'click button.js-schedule-board-cleanup'(event) { + event.preventDefault(); + Meteor.call('scheduleBoardCleanup', (error, result) => { + if (error) { + alert(TAPi18n.__('board-cleanup-failed') + ': ' + error.reason); + } else { + alert(TAPi18n.__('board-cleanup-scheduled')); + } + }); + }, + + 'click button.js-schedule-board-archive'(event) { + event.preventDefault(); + Meteor.call('scheduleBoardArchive', (error, result) => { + if (error) { + alert(TAPi18n.__('board-archive-failed') + ': ' + error.reason); + } else { + alert(TAPi18n.__('board-archive-scheduled')); + } + }); + }, + + 'click button.js-schedule-board-backup'(event) { + event.preventDefault(); + Meteor.call('scheduleBoardBackup', (error, result) => { + if (error) { + alert(TAPi18n.__('board-backup-failed') + ': ' + error.reason); + } else { + alert(TAPi18n.__('board-backup-scheduled')); + } + }); + }, + + 'click button.js-pause-job'(event) { + event.preventDefault(); + const jobId = $(event.target).data('job-id'); + Meteor.call('pauseCronJob', jobId, (error, result) => { + if (error) { + alert(TAPi18n.__('cron-job-pause-failed') + ': ' + error.reason); + } else { + alert(TAPi18n.__('cron-job-paused')); + } + }); + }, + + 'click button.js-delete-job'(event) { + event.preventDefault(); + const jobId = $(event.target).data('job-id'); + if (confirm(TAPi18n.__('cron-job-delete-confirm'))) { + Meteor.call('deleteCronJob', jobId, (error, result) => { + if (error) { + alert(TAPi18n.__('cron-job-delete-failed') + ': ' + error.reason); + } else { + alert(TAPi18n.__('cron-job-deleted')); + } + }); + } + }, + + 'click button.js-add-cron-job'(event) { + event.preventDefault(); + // Placeholder for adding a new cron job (e.g., open a modal) + alert(TAPi18n.__('add-cron-job-placeholder')); + }, + checkField(selector) { const value = $(selector).val(); if (!value || value.trim() === '') { @@ -104,37 +332,6 @@ BlazeComponent.extendComponent({ $('#display-authentication-method').toggleClass('is-checked'); }, - switchAttachmentTab(event) { - event.preventDefault(); - const target = $(event.target); - const targetID = target.data('id'); - - // Update active tab - $('.tab-nav li.active').removeClass('active'); - target.parent().addClass('active'); - - // Call the attachment settings component method if available - if (window.attachmentSettings && window.attachmentSettings.switchMenu) { - window.attachmentSettings.switchMenu(event, targetID); - } - }, - - switchCronTab(event) { - event.preventDefault(); - const target = $(event.target); - const targetID = target.data('id'); - - // Update active tab - $('.tab-nav li.active').removeClass('active'); - target.parent().addClass('active'); - - // Call the cron settings template method if available - const cronTemplate = Template.instance(); - if (cronTemplate && cronTemplate.switchMenu) { - cronTemplate.switchMenu(event, targetID); - } - }, - initializeAttachmentSubMenu() { // Set default sub-menu state for attachment settings // This will be handled by the attachment settings component @@ -357,13 +554,6 @@ BlazeComponent.extendComponent({ 'click button.js-save-layout': this.saveLayout, 'click a.js-toggle-display-authentication-method': this .toggleDisplayAuthenticationMethod, - 'click a.js-attachment-storage-settings': this.switchAttachmentTab, - 'click a.js-attachment-migration': this.switchAttachmentTab, - 'click a.js-attachment-monitoring': this.switchAttachmentTab, - 'click a.js-cron-migrations': this.switchCronTab, - 'click a.js-cron-board-operations': this.switchCronTab, - 'click a.js-cron-jobs': this.switchCronTab, - 'click a.js-cron-add': this.switchCronTab, }, ]; }, @@ -609,50 +799,3 @@ Template.selectSpinnerName.helpers({ }, }); -// Template helpers for the setting template -Template.setting.helpers({ - generalSetting() { - const instance = Template.instance(); - return instance.generalSetting && instance.generalSetting.get(); - }, - emailSetting() { - const instance = Template.instance(); - return instance.emailSetting && instance.emailSetting.get(); - }, - accountSetting() { - const instance = Template.instance(); - return instance.accountSetting && instance.accountSetting.get(); - }, - tableVisibilityModeSetting() { - const instance = Template.instance(); - return instance.tableVisibilityModeSetting && instance.tableVisibilityModeSetting.get(); - }, - announcementSetting() { - const instance = Template.instance(); - return instance.announcementSetting && instance.announcementSetting.get(); - }, - accessibilitySetting() { - const instance = Template.instance(); - return instance.accessibilitySetting && instance.accessibilitySetting.get(); - }, - layoutSetting() { - const instance = Template.instance(); - return instance.layoutSetting && instance.layoutSetting.get(); - }, - webhookSetting() { - const instance = Template.instance(); - return instance.webhookSetting && instance.webhookSetting.get(); - }, - attachmentSettings() { - const instance = Template.instance(); - return instance.attachmentSettings && instance.attachmentSettings.get(); - }, - cronSettings() { - const instance = Template.instance(); - return instance.cronSettings && instance.cronSettings.get(); - }, - loading() { - const instance = Template.instance(); - return instance.loading && instance.loading.get(); - }, -}); diff --git a/client/components/settings/settingsTabs.css b/client/components/settings/settingsTabs.css deleted file mode 100644 index 50f9c1dd4..000000000 --- a/client/components/settings/settingsTabs.css +++ /dev/null @@ -1,67 +0,0 @@ -/* Settings Tabs Styles */ -.settings-tabs { - margin-bottom: 20px; - border-bottom: 1px solid #e0e0e0; -} - -.tab-nav { - list-style: none; - margin: 0; - padding: 0; - display: flex; - flex-wrap: wrap; -} - -.tab-nav li { - margin: 0; - padding: 0; -} - -.tab-nav li a { - display: block; - padding: 12px 20px; - text-decoration: none; - color: #666; - border-bottom: 3px solid transparent; - transition: all 0.3s ease; - font-weight: 500; -} - -.tab-nav li a:hover { - color: #333; - background-color: #f5f5f5; -} - -.tab-nav li.active a { - color: #2196F3; - border-bottom-color: #2196F3; - background-color: #f8f9fa; -} - -.tab-nav li a i { - margin-right: 8px; - width: 16px; - text-align: center; -} - -.tab-content { - padding: 20px 0; - min-height: 400px; -} - -/* Responsive design */ -@media (max-width: 768px) { - .tab-nav { - flex-direction: column; - } - - .tab-nav li a { - border-bottom: 1px solid #e0e0e0; - border-right: none; - } - - .tab-nav li.active a { - border-bottom-color: #2196F3; - border-right: 3px solid #2196F3; - } -}