mirror of
https://github.com/wekan/wekan.git
synced 2026-01-21 16:56:11 +01:00
Added Cron Manager to Admin Panel for long running jobs, like running migrations when opening board, copying or moving boards swimlanes lists cards etc.
Thanks to xet7 !
This commit is contained in:
parent
e90bc744d9
commit
da68b01502
10 changed files with 2577 additions and 10 deletions
|
|
@ -12,3 +12,6 @@ import '/imports/components/boardConversionProgress';
|
|||
// Import migration manager and progress UI
|
||||
import '/imports/lib/migrationManager';
|
||||
import '/imports/components/migrationProgress';
|
||||
|
||||
// Import cron settings
|
||||
import '/imports/components/settings/cronSettings';
|
||||
|
|
|
|||
806
client/components/settings/cronSettings.css
Normal file
806
client/components/settings/cronSettings.css
Normal file
|
|
@ -0,0 +1,806 @@
|
|||
/* Cron Settings Styles */
|
||||
.cron-settings-content {
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.cron-migrations {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.migration-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.migration-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.migration-header h2 i {
|
||||
margin-right: 10px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.migration-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.migration-controls .btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.migration-controls .btn-primary {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.migration-controls .btn-primary:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.migration-controls .btn-warning {
|
||||
background-color: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.migration-controls .btn-warning:hover {
|
||||
background-color: #e0a800;
|
||||
}
|
||||
|
||||
.migration-controls .btn-danger {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.migration-controls .btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
.migration-progress {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.progress-overview {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
border-radius: 6px;
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.4),
|
||||
transparent
|
||||
);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.current-step {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.current-step i {
|
||||
margin-right: 8px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.migration-status {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
background-color: #e3f2fd;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #bbdefb;
|
||||
}
|
||||
|
||||
.migration-status i {
|
||||
margin-right: 8px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.migration-steps {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.migration-steps h3 {
|
||||
margin: 0 0 20px 0;
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.steps-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.migration-step {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.migration-step:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.migration-step.completed {
|
||||
background-color: #d4edda;
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
|
||||
.migration-step.current {
|
||||
background-color: #cce7ff;
|
||||
border-left: 4px solid #667eea;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(102, 126, 234, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.step-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
margin-right: 12px;
|
||||
font-size: 18px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.step-icon i.fa-check-circle {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.step-icon i.fa-cog.fa-spin {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.step-icon i.fa-circle-o {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.step-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.step-description {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.step-progress {
|
||||
text-align: right;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.step-progress .progress-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-progress-bar {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.step-progress-bar .progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* Cron Jobs Styles */
|
||||
.cron-jobs {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.jobs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.jobs-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.jobs-header h2 i {
|
||||
margin-right: 10px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.jobs-controls .btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.jobs-controls .btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.jobs-controls .btn-success:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.jobs-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.table thead {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.table th {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.table td {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-badge.status-running {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-badge.status-stopped {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.status-badge.status-paused {
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.status-badge.status-completed {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.status-badge.status-error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.btn-group .btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-group .btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-group .btn-success:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.btn-group .btn-warning {
|
||||
background-color: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-group .btn-warning:hover {
|
||||
background-color: #e0a800;
|
||||
}
|
||||
|
||||
.btn-group .btn-danger {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-group .btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
/* Add Job Form Styles */
|
||||
.cron-add-job {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.add-job-header {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.add-job-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.add-job-header h2 i {
|
||||
margin-right: 10px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.add-job-form {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
.form-control[type="number"] {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.form-actions .btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-actions .btn-primary {
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-actions .btn-primary:hover {
|
||||
background-color: #5a6fd8;
|
||||
}
|
||||
|
||||
.form-actions .btn-default {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-actions .btn-default:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
/* Board Operations Styles */
|
||||
.cron-board-operations {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.board-operations-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.board-operations-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.board-operations-header h2 i {
|
||||
margin-right: 10px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.board-operations-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.board-operations-controls .btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.board-operations-controls .btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.board-operations-controls .btn-success:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.board-operations-controls .btn-primary {
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.board-operations-controls .btn-primary:hover {
|
||||
background-color: #5a6fd8;
|
||||
}
|
||||
|
||||
.board-operations-stats {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.board-operations-search {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.search-box .form-control {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #999;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.board-operations-list {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.operations-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.operations-header h3 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.operations-table {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.operations-table .table {
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.operations-table .table th {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.operations-table .table td {
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.board-id {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
background: #f8f9fa;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operation-type {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.progress-container .progress-bar {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-container .progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-container .progress-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
min-width: 35px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.pagination .btn {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.pagination .btn:hover {
|
||||
background: #f8f9fa;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.pagination .btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.page-info {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.migration-header,
|
||||
.jobs-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.migration-controls,
|
||||
.jobs-controls {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.add-job-form {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
271
client/components/settings/cronSettings.jade
Normal file
271
client/components/settings/cronSettings.jade
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
template(name="cronSettings")
|
||||
.setting-content.cron-settings-content
|
||||
unless currentUser.isAdmin
|
||||
| {{_ 'error-notAuthorized'}}
|
||||
else
|
||||
.content-body
|
||||
.side-menu
|
||||
ul
|
||||
li
|
||||
a.js-cron-migrations(data-id="cron-migrations")
|
||||
i.fa.fa-database
|
||||
| {{_ 'cron-migrations'}}
|
||||
li
|
||||
a.js-cron-board-operations(data-id="cron-board-operations")
|
||||
i.fa.fa-tasks
|
||||
| {{_ 'board-operations'}}
|
||||
li
|
||||
a.js-cron-jobs(data-id="cron-jobs")
|
||||
i.fa.fa-clock-o
|
||||
| {{_ 'cron-jobs'}}
|
||||
li
|
||||
a.js-cron-add(data-id="cron-add")
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-cron-job'}}
|
||||
|
||||
.main-body
|
||||
if loading.get
|
||||
+spinner
|
||||
else if showMigrations.get
|
||||
+cronMigrations
|
||||
else if showBoardOperations.get
|
||||
+cronBoardOperations
|
||||
else if showJobs.get
|
||||
+cronJobs
|
||||
else if showAddJob.get
|
||||
+cronAddJob
|
||||
|
||||
template(name="cronMigrations")
|
||||
.cron-migrations
|
||||
.migration-header
|
||||
h2
|
||||
i.fa.fa-database
|
||||
| {{_ 'database-migrations'}}
|
||||
.migration-controls
|
||||
button.btn.btn-primary.js-start-all-migrations
|
||||
i.fa.fa-play
|
||||
| {{_ 'start-all-migrations'}}
|
||||
button.btn.btn-warning.js-pause-all-migrations
|
||||
i.fa.fa-pause
|
||||
| {{_ 'pause-all-migrations'}}
|
||||
button.btn.btn-danger.js-stop-all-migrations
|
||||
i.fa.fa-stop
|
||||
| {{_ 'stop-all-migrations'}}
|
||||
|
||||
.migration-progress
|
||||
.progress-overview
|
||||
.progress-bar
|
||||
.progress-fill(style="width: {{migrationProgress}}%")
|
||||
.progress-text {{migrationProgress}}%
|
||||
.progress-label {{_ 'overall-progress'}}
|
||||
|
||||
.current-step
|
||||
i.fa.fa-cog.fa-spin
|
||||
| {{migrationCurrentStep}}
|
||||
|
||||
.migration-status
|
||||
i.fa.fa-info-circle
|
||||
| {{migrationStatus}}
|
||||
|
||||
.migration-steps
|
||||
h3 {{_ 'migration-steps'}}
|
||||
.steps-list
|
||||
each migrationSteps
|
||||
.migration-step(class="{{#if completed}}completed{{/if}}" class="{{#if isCurrentStep}}current{{/if}}")
|
||||
.step-header
|
||||
.step-icon
|
||||
if completed
|
||||
i.fa.fa-check-circle
|
||||
else if isCurrentStep
|
||||
i.fa.fa-cog.fa-spin
|
||||
else
|
||||
i.fa.fa-circle-o
|
||||
.step-info
|
||||
.step-name {{name}}
|
||||
.step-description {{description}}
|
||||
.step-progress
|
||||
if completed
|
||||
.progress-text 100%
|
||||
else if isCurrentStep
|
||||
.progress-text {{progress}}%
|
||||
else
|
||||
.progress-text 0%
|
||||
if isCurrentStep
|
||||
.step-progress-bar
|
||||
.progress-fill(style="width: {{progress}}%")
|
||||
|
||||
template(name="cronBoardOperations")
|
||||
.cron-board-operations
|
||||
.board-operations-header
|
||||
h2
|
||||
i.fa.fa-tasks
|
||||
| {{_ 'board-operations'}}
|
||||
.board-operations-controls
|
||||
button.btn.btn-success.js-refresh-board-operations
|
||||
i.fa.fa-refresh
|
||||
| {{_ 'refresh'}}
|
||||
button.btn.btn-primary.js-start-test-operation
|
||||
i.fa.fa-play
|
||||
| {{_ 'start-test-operation'}}
|
||||
|
||||
.board-operations-stats
|
||||
.stats-grid
|
||||
.stat-item
|
||||
.stat-value {{operationStats.total}}
|
||||
.stat-label {{_ 'total-operations'}}
|
||||
.stat-item
|
||||
.stat-value {{operationStats.running}}
|
||||
.stat-label {{_ 'running'}}
|
||||
.stat-item
|
||||
.stat-value {{operationStats.completed}}
|
||||
.stat-label {{_ 'completed'}}
|
||||
.stat-item
|
||||
.stat-value {{operationStats.error}}
|
||||
.stat-label {{_ 'errors'}}
|
||||
|
||||
.board-operations-search
|
||||
.search-box
|
||||
input.form-control.js-search-board-operations(type="text" placeholder="{{_ 'search-boards-or-operations'}}")
|
||||
i.fa.fa-search.search-icon
|
||||
|
||||
.board-operations-list
|
||||
.operations-header
|
||||
h3 {{_ 'board-operations'}} ({{pagination.total}})
|
||||
.pagination-info
|
||||
| {{_ 'showing'}} {{pagination.start}} - {{pagination.end}} {{_ 'of'}} {{pagination.total}}
|
||||
|
||||
.operations-table
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th {{_ 'board-id'}}
|
||||
th {{_ 'operation-type'}}
|
||||
th {{_ 'status'}}
|
||||
th {{_ 'progress'}}
|
||||
th {{_ 'start-time'}}
|
||||
th {{_ 'duration'}}
|
||||
th {{_ 'actions'}}
|
||||
tbody
|
||||
each boardOperations
|
||||
tr
|
||||
td
|
||||
.board-id {{boardId}}
|
||||
td
|
||||
.operation-type {{operationType}}
|
||||
td
|
||||
span.status-badge(class="status-{{status}}") {{status}}
|
||||
td
|
||||
.progress-container
|
||||
.progress-bar
|
||||
.progress-fill(style="width: {{progress}}%")
|
||||
.progress-text {{progress}}%
|
||||
td {{formatDateTime startTime}}
|
||||
td {{formatDuration startTime endTime}}
|
||||
td
|
||||
.btn-group
|
||||
if isRunning
|
||||
button.btn.btn-sm.btn-warning.js-pause-operation(data-operation="{{id}}")
|
||||
i.fa.fa-pause
|
||||
else
|
||||
button.btn.btn-sm.btn-success.js-resume-operation(data-operation="{{id}}")
|
||||
i.fa.fa-play
|
||||
button.btn.btn-sm.btn-danger.js-stop-operation(data-operation="{{id}}")
|
||||
i.fa.fa-stop
|
||||
button.btn.btn-sm.btn-info.js-view-details(data-operation="{{id}}")
|
||||
i.fa.fa-info-circle
|
||||
|
||||
.pagination
|
||||
if pagination.hasPrev
|
||||
button.btn.btn-sm.btn-default.js-prev-page
|
||||
i.fa.fa-chevron-left
|
||||
| {{_ 'previous'}}
|
||||
.page-info
|
||||
| {{_ 'page'}} {{pagination.page}} {{_ 'of'}} {{pagination.totalPages}}
|
||||
if pagination.hasNext
|
||||
button.btn.btn-sm.btn-default.js-next-page
|
||||
| {{_ 'next'}}
|
||||
i.fa.fa-chevron-right
|
||||
|
||||
template(name="cronJobs")
|
||||
.cron-jobs
|
||||
.jobs-header
|
||||
h2
|
||||
i.fa.fa-clock-o
|
||||
| {{_ 'cron-jobs'}}
|
||||
.jobs-controls
|
||||
button.btn.btn-success.js-refresh-jobs
|
||||
i.fa.fa-refresh
|
||||
| {{_ 'refresh'}}
|
||||
|
||||
.jobs-list
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th {{_ 'job-name'}}
|
||||
th {{_ 'schedule'}}
|
||||
th {{_ 'status'}}
|
||||
th {{_ 'last-run'}}
|
||||
th {{_ 'next-run'}}
|
||||
th {{_ 'actions'}}
|
||||
tbody
|
||||
each cronJobs
|
||||
tr
|
||||
td {{name}}
|
||||
td {{schedule}}
|
||||
td
|
||||
span.status-badge(class="status-{{status}}") {{status}}
|
||||
td {{formatDate lastRun}}
|
||||
td {{formatDate nextRun}}
|
||||
td
|
||||
.btn-group
|
||||
if isRunning
|
||||
button.btn.btn-sm.btn-warning.js-pause-job(data-job="{{name}}")
|
||||
i.fa.fa-pause
|
||||
else
|
||||
button.btn.btn-sm.btn-success.js-start-job(data-job="{{name}}")
|
||||
i.fa.fa-play
|
||||
button.btn.btn-sm.btn-danger.js-stop-job(data-job="{{name}}")
|
||||
i.fa.fa-stop
|
||||
button.btn.btn-sm.btn-danger.js-remove-job(data-job="{{name}}")
|
||||
i.fa.fa-trash
|
||||
|
||||
template(name="cronAddJob")
|
||||
.cron-add-job
|
||||
.add-job-header
|
||||
h2
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-cron-job'}}
|
||||
|
||||
.add-job-form
|
||||
form.js-add-cron-job-form
|
||||
.form-group
|
||||
label(for="job-name") {{_ 'job-name'}}
|
||||
input.form-control#job-name(type="text" name="name" required)
|
||||
|
||||
.form-group
|
||||
label(for="job-description") {{_ 'job-description'}}
|
||||
textarea.form-control#job-description(name="description" rows="3")
|
||||
|
||||
.form-group
|
||||
label(for="job-schedule") {{_ 'schedule'}}
|
||||
select.form-control#job-schedule(name="schedule")
|
||||
option(value="every 1 minute") {{_ 'every-1-minute'}}
|
||||
option(value="every 5 minutes") {{_ 'every-5-minutes'}}
|
||||
option(value="every 10 minutes") {{_ 'every-10-minutes'}}
|
||||
option(value="every 30 minutes") {{_ 'every-30-minutes'}}
|
||||
option(value="every 1 hour") {{_ 'every-1-hour'}}
|
||||
option(value="every 6 hours") {{_ 'every-6-hours'}}
|
||||
option(value="every 1 day") {{_ 'every-1-day'}}
|
||||
option(value="once") {{_ 'run-once'}}
|
||||
|
||||
.form-group
|
||||
label(for="job-weight") {{_ 'weight'}}
|
||||
input.form-control#job-weight(type="number" name="weight" value="1" min="1" max="10")
|
||||
|
||||
.form-actions
|
||||
button.btn.btn-primary(type="submit")
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-job'}}
|
||||
button.btn.btn-default.js-cancel-add-job
|
||||
i.fa.fa-times
|
||||
| {{_ 'cancel'}}
|
||||
446
client/components/settings/cronSettings.js
Normal file
446
client/components/settings/cronSettings.js
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
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({});
|
||||
|
||||
// Load initial data
|
||||
this.loadCronData();
|
||||
});
|
||||
|
||||
Template.cronSettings.helpers({
|
||||
loading() {
|
||||
return Template.instance().loading.get();
|
||||
},
|
||||
|
||||
showMigrations() {
|
||||
return Template.instance().showMigrations.get();
|
||||
},
|
||||
|
||||
showBoardOperations() {
|
||||
return Template.instance().showBoardOperations.get();
|
||||
},
|
||||
|
||||
showJobs() {
|
||||
return Template.instance().showJobs.get();
|
||||
},
|
||||
|
||||
showAddJob() {
|
||||
return Template.instance().showAddJob.get();
|
||||
},
|
||||
|
||||
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() {
|
||||
return Template.instance().boardOperations.get();
|
||||
},
|
||||
|
||||
operationStats() {
|
||||
return Template.instance().operationStats.get();
|
||||
},
|
||||
|
||||
pagination() {
|
||||
return Template.instance().pagination.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.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);
|
||||
instance.loadBoardOperations();
|
||||
},
|
||||
|
||||
'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);
|
||||
instance.loadCronJobs();
|
||||
},
|
||||
|
||||
'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 {
|
||||
console.log('Migrations started successfully');
|
||||
Template.instance().pollMigrationProgress();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'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();
|
||||
Template.instance().loadCronJobs();
|
||||
},
|
||||
|
||||
'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');
|
||||
Template.instance().loadCronJobs();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'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');
|
||||
Template.instance().loadCronJobs();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'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');
|
||||
Template.instance().loadCronJobs();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'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');
|
||||
Template.instance().loadCronJobs();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
'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);
|
||||
Template.instance().loadCronJobs();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'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();
|
||||
Template.instance().loadBoardOperations();
|
||||
},
|
||||
|
||||
'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);
|
||||
Template.instance().loadBoardOperations();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'input .js-search-board-operations'(event) {
|
||||
const searchTerm = $(event.currentTarget).val();
|
||||
const instance = Template.instance();
|
||||
instance.searchTerm.set(searchTerm);
|
||||
instance.currentPage.set(1);
|
||||
instance.loadBoardOperations();
|
||||
},
|
||||
|
||||
'click .js-prev-page'(event) {
|
||||
event.preventDefault();
|
||||
const instance = Template.instance();
|
||||
const currentPage = instance.currentPage.get();
|
||||
if (currentPage > 1) {
|
||||
instance.currentPage.set(currentPage - 1);
|
||||
instance.loadBoardOperations();
|
||||
}
|
||||
},
|
||||
|
||||
'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);
|
||||
instance.loadBoardOperations();
|
||||
}
|
||||
},
|
||||
|
||||
'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);
|
||||
}
|
||||
});
|
||||
|
||||
Template.cronSettings.prototype.loadCronData = function() {
|
||||
this.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
|
||||
this.loadCronJobs();
|
||||
|
||||
this.loading.set(false);
|
||||
};
|
||||
|
||||
Template.cronSettings.prototype.loadCronJobs = function() {
|
||||
Meteor.call('cron.getJobs', (error, result) => {
|
||||
if (result) {
|
||||
cronJobs.set(result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Template.cronSettings.prototype.loadBoardOperations = function() {
|
||||
const instance = this;
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Template.cronSettings.prototype.pollMigrationProgress = function() {
|
||||
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);
|
||||
};
|
||||
|
|
@ -46,6 +46,10 @@ template(name="setting")
|
|||
a.js-setting-menu(data-id="attachment-settings")
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'attachment-settings'}}
|
||||
li
|
||||
a.js-setting-menu(data-id="cron-settings")
|
||||
i.fa.fa-clock-o
|
||||
| {{_ 'cron-settings'}}
|
||||
.main-body
|
||||
if loading.get
|
||||
+spinner
|
||||
|
|
@ -68,6 +72,8 @@ template(name="setting")
|
|||
+webhookSettings
|
||||
else if attachmentSettings.get
|
||||
+attachmentSettings
|
||||
else if cronSettings.get
|
||||
+cronSettings
|
||||
|
||||
template(name="webhookSettings")
|
||||
span
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ BlazeComponent.extendComponent({
|
|||
this.layoutSetting = new ReactiveVar(false);
|
||||
this.webhookSetting = new ReactiveVar(false);
|
||||
this.attachmentSettings = new ReactiveVar(false);
|
||||
this.cronSettings = new ReactiveVar(false);
|
||||
|
||||
Meteor.subscribe('setting');
|
||||
Meteor.subscribe('mailServer');
|
||||
|
|
@ -115,6 +116,7 @@ BlazeComponent.extendComponent({
|
|||
this.layoutSetting.set('layout-setting' === targetID);
|
||||
this.webhookSetting.set('webhook-setting' === targetID);
|
||||
this.attachmentSettings.set('attachment-settings' === targetID);
|
||||
this.cronSettings.set('cron-settings' === targetID);
|
||||
this.tableVisibilityModeSetting.set('tableVisibilityMode-setting' === targetID);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -624,7 +624,7 @@ class MigrationManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Start migration process
|
||||
* Start migration process using cron system
|
||||
*/
|
||||
async startMigration() {
|
||||
if (isMigrating.get()) {
|
||||
|
|
@ -636,17 +636,17 @@ class MigrationManager {
|
|||
this.startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Start server-side migration
|
||||
Meteor.call('migration.start', (error, result) => {
|
||||
// Start server-side cron migrations
|
||||
Meteor.call('cron.startAllMigrations', (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to start migration:', error);
|
||||
console.error('Failed to start cron migrations:', error);
|
||||
migrationStatus.set(`Migration failed: ${error.message}`);
|
||||
isMigrating.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Poll for progress updates
|
||||
this.pollMigrationProgress();
|
||||
this.pollCronMigrationProgress();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
|
|
@ -656,13 +656,13 @@ class MigrationManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Poll for migration progress updates
|
||||
* Poll for cron migration progress updates
|
||||
*/
|
||||
pollMigrationProgress() {
|
||||
pollCronMigrationProgress() {
|
||||
const pollInterval = setInterval(() => {
|
||||
Meteor.call('migration.getProgress', (error, result) => {
|
||||
Meteor.call('cron.getMigrationProgress', (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to get migration progress:', error);
|
||||
console.error('Failed to get cron migration progress:', error);
|
||||
clearInterval(pollInterval);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue