@memberjunction/ng-scheduling 5.38.0 → 5.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,6 +31,10 @@ export declare class ScheduledJobEditorComponent extends BaseAngularComponent im
31
31
  Configuration: string;
32
32
  NotifyOnSuccess: boolean;
33
33
  NotifyOnFailure: boolean;
34
+ /** When true AND the job has never run, the scheduler queues it for immediate
35
+ * execution on the next poll instead of waiting for the next cron tick.
36
+ * Useful for newly-seeded jobs that should not wait up to a full cron interval. */
37
+ RunImmediatelyIfNeverRun: boolean;
34
38
  TotalRuns: number;
35
39
  SuccessRuns: number;
36
40
  FailedRuns: number;
@@ -1 +1 @@
1
- {"version":3,"file":"scheduled-job-editor.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,YAAY,EAGZ,MAAM,EAET,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAE/F,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAsBrE,qBAOa,2BAA4B,SAAQ,oBAAqB,YAAW,MAAM;IACnF,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,mBAAmB,CAA+B;IAG1D,OAAO,CAAC,eAAe,CAAuB;IAE9C,IACI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAOtC;IACD,IAAI,cAAc,IAAI,MAAM,GAAG,IAAI,CAElC;IAEQ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C,WAAW,UAAS;IAGnB,KAAK,qCAA4C;IACjD,OAAO,uBAA8B;IACrC,SAAS,qBAA4B;IAGxC,SAAS,UAAS;IAClB,QAAQ,UAAS;IACjB,KAAK,UAAS;IACd,iBAAiB,UAAS;IAE1B,GAAG,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IACxC,QAAQ,EAAE,wBAAwB,EAAE,CAAM;IAC1C,SAAS,EAAE,MAAM,EAAE,CAAoB;IAGvC,IAAI,SAAM;IACV,WAAW,SAAM;IACjB,iBAAiB,SAAM;IACvB,cAAc,SAAM;IACpB,QAAQ,SAAS;IACjB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAY;IAC5E,eAAe,SAAU;IACzB,aAAa,SAAM;IACnB,eAAe,UAAS;IACxB,eAAe,UAAQ;IAGvB,SAAS,SAAK;IACd,WAAW,SAAK;IAChB,UAAU,SAAK;IAEtB,SAAgB,aAAa,WAA+C;IAC5E,SAAgB,kBAAkB,WAAmC;IAErE,OAAO,CAAC,aAAa,CAAS;IAE9B,IAAW,OAAO,IAAI,OAAO,CAI5B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAYlB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC3B,aAAa,IAAI,IAAI;IAKrB,YAAY,IAAI,IAAI;IAKd,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBhC,MAAM,IAAI,IAAI;YAMP,eAAe;YAQf,eAAe;IAW7B,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,iBAAiB;YAaX,iBAAiB;YAUjB,YAAY;yCAlOjB,2BAA2B;2CAA3B,2BAA2B;CAwOvC"}
1
+ {"version":3,"file":"scheduled-job-editor.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,YAAY,EAGZ,MAAM,EAET,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAE/F,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAsBrE,qBAOa,2BAA4B,SAAQ,oBAAqB,YAAW,MAAM;IACnF,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,mBAAmB,CAA+B;IAG1D,OAAO,CAAC,eAAe,CAAuB;IAE9C,IACI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAOtC;IACD,IAAI,cAAc,IAAI,MAAM,GAAG,IAAI,CAElC;IAEQ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C,WAAW,UAAS;IAGnB,KAAK,qCAA4C;IACjD,OAAO,uBAA8B;IACrC,SAAS,qBAA4B;IAGxC,SAAS,UAAS;IAClB,QAAQ,UAAS;IACjB,KAAK,UAAS;IACd,iBAAiB,UAAS;IAE1B,GAAG,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IACxC,QAAQ,EAAE,wBAAwB,EAAE,CAAM;IAC1C,SAAS,EAAE,MAAM,EAAE,CAAoB;IAGvC,IAAI,SAAM;IACV,WAAW,SAAM;IACjB,iBAAiB,SAAM;IACvB,cAAc,SAAM;IACpB,QAAQ,SAAS;IACjB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAY;IAC5E,eAAe,SAAU;IACzB,aAAa,SAAM;IACnB,eAAe,UAAS;IACxB,eAAe,UAAQ;IAC9B;;wFAEoF;IAC7E,wBAAwB,UAAS;IAGjC,SAAS,SAAK;IACd,WAAW,SAAK;IAChB,UAAU,SAAK;IAEtB,SAAgB,aAAa,WAA+C;IAC5E,SAAgB,kBAAkB,WAAmC;IAErE,OAAO,CAAC,aAAa,CAAS;IAE9B,IAAW,OAAO,IAAI,OAAO,CAI5B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAYlB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC3B,aAAa,IAAI,IAAI;IAKrB,YAAY,IAAI,IAAI;IAKd,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBhC,MAAM,IAAI,IAAI;YAMP,eAAe;YAQf,eAAe;IAW7B,OAAO,CAAC,UAAU;IAoBlB,OAAO,CAAC,sBAAsB;IAc9B,OAAO,CAAC,iBAAiB;YAcX,iBAAiB;YAUjB,YAAY;yCAzOjB,2BAA2B;2CAA3B,2BAA2B;CA+OvC"}
@@ -65,6 +65,10 @@ export class ScheduledJobEditorComponent extends BaseAngularComponent {
65
65
  Configuration = '';
66
66
  NotifyOnSuccess = false;
67
67
  NotifyOnFailure = true;
68
+ /** When true AND the job has never run, the scheduler queues it for immediate
69
+ * execution on the next poll instead of waiting for the next cron tick.
70
+ * Useful for newly-seeded jobs that should not wait up to a full cron interval. */
71
+ RunImmediatelyIfNeverRun = false;
68
72
  // Stats (edit mode)
69
73
  TotalRuns = 0;
70
74
  SuccessRuns = 0;
@@ -181,6 +185,7 @@ export class ScheduledJobEditorComponent extends BaseAngularComponent {
181
185
  this.Configuration = this.DefaultConfiguration ?? '';
182
186
  this.NotifyOnSuccess = false;
183
187
  this.NotifyOnFailure = true;
188
+ this.RunImmediatelyIfNeverRun = false;
184
189
  this.TotalRuns = 0;
185
190
  this.SuccessRuns = 0;
186
191
  this.FailedRuns = 0;
@@ -197,6 +202,7 @@ export class ScheduledJobEditorComponent extends BaseAngularComponent {
197
202
  this.Configuration = job.Configuration ?? '';
198
203
  this.NotifyOnSuccess = job.Get('NotifyOnSuccess') ?? false;
199
204
  this.NotifyOnFailure = job.Get('NotifyOnFailure') ?? true;
205
+ this.RunImmediatelyIfNeverRun = job.RunImmediatelyIfNeverRun ?? false;
200
206
  }
201
207
  applyFormToEntity(job) {
202
208
  job.Name = this.Name.trim();
@@ -205,10 +211,11 @@ export class ScheduledJobEditorComponent extends BaseAngularComponent {
205
211
  job.CronExpression = this.CronExpression.trim();
206
212
  job.Timezone = this.Timezone;
207
213
  job.Status = this.Status;
208
- job.Set('ConcurrencyMode', this.ConcurrencyMode);
214
+ job.ConcurrencyMode = this.ConcurrencyMode;
209
215
  job.Configuration = this.Configuration;
210
- job.Set('NotifyOnSuccess', this.NotifyOnSuccess);
211
- job.Set('NotifyOnFailure', this.NotifyOnFailure);
216
+ job.NotifyOnSuccess = this.NotifyOnSuccess;
217
+ job.NotifyOnFailure = this.NotifyOnFailure;
218
+ job.RunImmediatelyIfNeverRun = this.RunImmediatelyIfNeverRun;
212
219
  }
213
220
  async getOrCreateEntity() {
214
221
  if (this.Job && !this.IsNew) {
@@ -226,11 +233,11 @@ export class ScheduledJobEditorComponent extends BaseAngularComponent {
226
233
  this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;
227
234
  }
228
235
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobEditorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
229
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ScheduledJobEditorComponent, isStandalone: false, selector: "mj-scheduled-job-editor", inputs: { ScheduledJobID: "ScheduledJobID", JobTypeID: "JobTypeID", DefaultConfiguration: "DefaultConfiguration", HideJobType: "HideJobType" }, outputs: { Saved: "Saved", Deleted: "Deleted", Cancelled: "Cancelled" }, usesInheritance: true, ngImport: i0, template: "@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n", styles: [".editor-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 40px;\n}\n\n.editor-container {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 16px;\n}\n\n.form-section {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n background: var(--mj-bg-surface);\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.section-title i {\n color: var(--mj-brand-primary);\n font-size: 14px;\n}\n\n.form-group {\n margin-bottom: 12px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n margin-bottom: 4px;\n}\n\n.required {\n color: var(--mj-status-error);\n}\n\n.form-group input,\n.form-group textarea,\n.form-group select {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n font-size: 13px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n box-sizing: border-box;\n transition: border-color 0.15s;\n}\n\n.form-group input:focus,\n.form-group textarea:focus,\n.form-group select:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.code-textarea {\n font-family: 'Consolas', 'Monaco', 'Courier New', monospace;\n font-size: 12px;\n line-height: 1.5;\n}\n\n.hint {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n}\n\n.toggle-row {\n margin-bottom: 8px;\n}\n\n.toggle-label {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-primary);\n cursor: pointer;\n}\n\n.toggle-label input[type=\"checkbox\"] {\n width: auto;\n}\n\n.stats-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n display: block;\n font-size: 20px;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.stat-value.success {\n color: var(--mj-status-success);\n}\n\n.stat-value.error {\n color: var(--mj-status-error);\n}\n\n.stat-label {\n display: block;\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n.editor-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 8px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.footer-right {\n display: flex;\n gap: 8px;\n margin-left: auto;\n}\n\n.btn {\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: background 0.15s, opacity 0.15s;\n}\n\n.btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.btn-secondary {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: var(--mj-bg-surface-active);\n}\n\n.btn-danger {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.btn-danger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 20%, var(--mj-bg-surface));\n}\n\n.confirm-overlay {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.confirm-dialog {\n background: var(--mj-bg-surface);\n border-radius: 10px;\n padding: 24px;\n max-width: 360px;\n text-align: center;\n box-shadow: var(--mj-shadow-lg);\n}\n\n.confirm-dialog p {\n margin: 0 0 16px;\n color: var(--mj-text-primary);\n font-size: 14px;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: center;\n}\n\n@media (max-width: 480px) {\n .stats-grid {\n grid-template-columns: 1fr;\n }\n\n .editor-footer {\n flex-direction: column;\n gap: 8px;\n }\n\n .footer-right {\n margin-left: 0;\n width: 100%;\n }\n\n .footer-right .btn {\n flex: 1;\n justify-content: center;\n }\n}\n"], dependencies: [{ kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2.LoadingComponent, selector: "mj-loading", inputs: ["text", "showText", "animationDuration", "size", "textColor", "logoColor", "logoGradient", "animation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
236
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ScheduledJobEditorComponent, isStandalone: false, selector: "mj-scheduled-job-editor", inputs: { ScheduledJobID: "ScheduledJobID", JobTypeID: "JobTypeID", DefaultConfiguration: "DefaultConfiguration", HideJobType: "HideJobType" }, outputs: { Saved: "Saved", Deleted: "Deleted", Cancelled: "Cancelled" }, usesInheritance: true, ngImport: i0, template: "@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"RunImmediatelyIfNeverRun\" />\n Run immediately on first poll if never run\n </label>\n <div class=\"hint\">When checked, a job that has never run will execute on the next polling cycle instead of waiting for the next cron tick. Useful for newly-seeded jobs.</div>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n", styles: [".editor-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 40px;\n}\n\n.editor-container {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 16px;\n}\n\n.form-section {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n background: var(--mj-bg-surface);\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.section-title i {\n color: var(--mj-brand-primary);\n font-size: 14px;\n}\n\n.form-group {\n margin-bottom: 12px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n margin-bottom: 4px;\n}\n\n.required {\n color: var(--mj-status-error);\n}\n\n.form-group input,\n.form-group textarea,\n.form-group select {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n font-size: 13px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n box-sizing: border-box;\n transition: border-color 0.15s;\n}\n\n.form-group input:focus,\n.form-group textarea:focus,\n.form-group select:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.code-textarea {\n font-family: 'Consolas', 'Monaco', 'Courier New', monospace;\n font-size: 12px;\n line-height: 1.5;\n}\n\n.hint {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n}\n\n.toggle-row {\n margin-bottom: 8px;\n}\n\n.toggle-label {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-primary);\n cursor: pointer;\n}\n\n.toggle-label input[type=\"checkbox\"] {\n width: auto;\n}\n\n.stats-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n display: block;\n font-size: 20px;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.stat-value.success {\n color: var(--mj-status-success);\n}\n\n.stat-value.error {\n color: var(--mj-status-error);\n}\n\n.stat-label {\n display: block;\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n.editor-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 8px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.footer-right {\n display: flex;\n gap: 8px;\n margin-left: auto;\n}\n\n.btn {\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: background 0.15s, opacity 0.15s;\n}\n\n.btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.btn-secondary {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: var(--mj-bg-surface-active);\n}\n\n.btn-danger {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.btn-danger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 20%, var(--mj-bg-surface));\n}\n\n.confirm-overlay {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.confirm-dialog {\n background: var(--mj-bg-surface);\n border-radius: 10px;\n padding: 24px;\n max-width: 360px;\n text-align: center;\n box-shadow: var(--mj-shadow-lg);\n}\n\n.confirm-dialog p {\n margin: 0 0 16px;\n color: var(--mj-text-primary);\n font-size: 14px;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: center;\n}\n\n@media (max-width: 480px) {\n .stats-grid {\n grid-template-columns: 1fr;\n }\n\n .editor-footer {\n flex-direction: column;\n gap: 8px;\n }\n\n .footer-right {\n margin-left: 0;\n width: 100%;\n }\n\n .footer-right .btn {\n flex: 1;\n justify-content: center;\n }\n}\n"], dependencies: [{ kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2.LoadingComponent, selector: "mj-loading", inputs: ["text", "showText", "animationDuration", "size", "textColor", "logoColor", "logoGradient", "animation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
230
237
  }
231
238
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScheduledJobEditorComponent, decorators: [{
232
239
  type: Component,
233
- args: [{ selector: 'mj-scheduled-job-editor', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n", styles: [".editor-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 40px;\n}\n\n.editor-container {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 16px;\n}\n\n.form-section {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n background: var(--mj-bg-surface);\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.section-title i {\n color: var(--mj-brand-primary);\n font-size: 14px;\n}\n\n.form-group {\n margin-bottom: 12px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n margin-bottom: 4px;\n}\n\n.required {\n color: var(--mj-status-error);\n}\n\n.form-group input,\n.form-group textarea,\n.form-group select {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n font-size: 13px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n box-sizing: border-box;\n transition: border-color 0.15s;\n}\n\n.form-group input:focus,\n.form-group textarea:focus,\n.form-group select:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.code-textarea {\n font-family: 'Consolas', 'Monaco', 'Courier New', monospace;\n font-size: 12px;\n line-height: 1.5;\n}\n\n.hint {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n}\n\n.toggle-row {\n margin-bottom: 8px;\n}\n\n.toggle-label {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-primary);\n cursor: pointer;\n}\n\n.toggle-label input[type=\"checkbox\"] {\n width: auto;\n}\n\n.stats-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n display: block;\n font-size: 20px;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.stat-value.success {\n color: var(--mj-status-success);\n}\n\n.stat-value.error {\n color: var(--mj-status-error);\n}\n\n.stat-label {\n display: block;\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n.editor-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 8px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.footer-right {\n display: flex;\n gap: 8px;\n margin-left: auto;\n}\n\n.btn {\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: background 0.15s, opacity 0.15s;\n}\n\n.btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.btn-secondary {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: var(--mj-bg-surface-active);\n}\n\n.btn-danger {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.btn-danger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 20%, var(--mj-bg-surface));\n}\n\n.confirm-overlay {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.confirm-dialog {\n background: var(--mj-bg-surface);\n border-radius: 10px;\n padding: 24px;\n max-width: 360px;\n text-align: center;\n box-shadow: var(--mj-shadow-lg);\n}\n\n.confirm-dialog p {\n margin: 0 0 16px;\n color: var(--mj-text-primary);\n font-size: 14px;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: center;\n}\n\n@media (max-width: 480px) {\n .stats-grid {\n grid-template-columns: 1fr;\n }\n\n .editor-footer {\n flex-direction: column;\n gap: 8px;\n }\n\n .footer-right {\n margin-left: 0;\n width: 100%;\n }\n\n .footer-right .btn {\n flex: 1;\n justify-content: center;\n }\n}\n"] }]
240
+ args: [{ selector: 'mj-scheduled-job-editor', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"RunImmediatelyIfNeverRun\" />\n Run immediately on first poll if never run\n </label>\n <div class=\"hint\">When checked, a job that has never run will execute on the next polling cycle instead of waiting for the next cron tick. Useful for newly-seeded jobs.</div>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n", styles: [".editor-loading {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 40px;\n}\n\n.editor-container {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 16px;\n}\n\n.form-section {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n background: var(--mj-bg-surface);\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.section-title i {\n color: var(--mj-brand-primary);\n font-size: 14px;\n}\n\n.form-group {\n margin-bottom: 12px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n margin-bottom: 4px;\n}\n\n.required {\n color: var(--mj-status-error);\n}\n\n.form-group input,\n.form-group textarea,\n.form-group select {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n font-size: 13px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface);\n box-sizing: border-box;\n transition: border-color 0.15s;\n}\n\n.form-group input:focus,\n.form-group textarea:focus,\n.form-group select:focus {\n outline: none;\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.code-textarea {\n font-family: 'Consolas', 'Monaco', 'Courier New', monospace;\n font-size: 12px;\n line-height: 1.5;\n}\n\n.hint {\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n}\n\n.toggle-row {\n margin-bottom: 8px;\n}\n\n.toggle-label {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-primary);\n cursor: pointer;\n}\n\n.toggle-label input[type=\"checkbox\"] {\n width: auto;\n}\n\n.stats-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n display: block;\n font-size: 20px;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.stat-value.success {\n color: var(--mj-status-success);\n}\n\n.stat-value.error {\n color: var(--mj-status-error);\n}\n\n.stat-label {\n display: block;\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n.editor-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 8px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.footer-right {\n display: flex;\n gap: 8px;\n margin-left: auto;\n}\n\n.btn {\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: background 0.15s, opacity 0.15s;\n}\n\n.btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.btn-secondary {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: var(--mj-bg-surface-active);\n}\n\n.btn-danger {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.btn-danger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 20%, var(--mj-bg-surface));\n}\n\n.confirm-overlay {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.confirm-dialog {\n background: var(--mj-bg-surface);\n border-radius: 10px;\n padding: 24px;\n max-width: 360px;\n text-align: center;\n box-shadow: var(--mj-shadow-lg);\n}\n\n.confirm-dialog p {\n margin: 0 0 16px;\n color: var(--mj-text-primary);\n font-size: 14px;\n}\n\n.confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: center;\n}\n\n@media (max-width: 480px) {\n .stats-grid {\n grid-template-columns: 1fr;\n }\n\n .editor-footer {\n flex-direction: column;\n gap: 8px;\n }\n\n .footer-right {\n margin-left: 0;\n width: 100%;\n }\n\n .footer-right .btn {\n flex: 1;\n justify-content: center;\n }\n}\n"] }]
234
241
  }], propDecorators: { ScheduledJobID: [{
235
242
  type: Input
236
243
  }], JobTypeID: [{
@@ -1 +1 @@
1
- {"version":3,"file":"scheduled-job-editor.component.js","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.ts","../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n OnInit,\n inject,\n} from '@angular/core';\nimport { MJScheduledJobEntity, MJScheduledJobTypeEntity } from '@memberjunction/core-entities';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { ScheduledJobService } from '../../services/scheduled-job.service';\n\n/** IANA timezone list subset for the dropdown */\nconst COMMON_TIMEZONES = [\n 'UTC',\n 'America/New_York',\n 'America/Chicago',\n 'America/Denver',\n 'America/Los_Angeles',\n 'America/Phoenix',\n 'America/Anchorage',\n 'Pacific/Honolulu',\n 'Europe/London',\n 'Europe/Paris',\n 'Europe/Berlin',\n 'Asia/Tokyo',\n 'Asia/Shanghai',\n 'Asia/Kolkata',\n 'Australia/Sydney',\n];\n\n@Component({\n selector: 'mj-scheduled-job-editor',\n standalone: false,\n templateUrl: './scheduled-job-editor.component.html',\n styleUrls: ['./scheduled-job-editor.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobEditorComponent extends BaseAngularComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n private scheduledJobService = inject(ScheduledJobService);\n\n // ── Inputs ────────────────────────────────────────────────\n private _scheduledJobID: string | null = null;\n\n @Input()\n set ScheduledJobID(value: string | null) {\n if (value !== this._scheduledJobID) {\n this._scheduledJobID = value;\n if (this.isInitialized) {\n this.loadOrCreateJob();\n }\n }\n }\n get ScheduledJobID(): string | null {\n return this._scheduledJobID;\n }\n\n @Input() JobTypeID: string | null = null;\n @Input() DefaultConfiguration: string | null = null;\n @Input() HideJobType = false;\n\n // ── Outputs ───────────────────────────────────────────────\n @Output() Saved = new EventEmitter<MJScheduledJobEntity>();\n @Output() Deleted = new EventEmitter<string>();\n @Output() Cancelled = new EventEmitter<void>();\n\n // ── State ─────────────────────────────────────────────────\n public IsLoading = false;\n public IsSaving = false;\n public IsNew = false;\n public ShowDeleteConfirm = false;\n\n public Job: MJScheduledJobEntity | null = null;\n public JobTypes: MJScheduledJobTypeEntity[] = [];\n public Timezones: string[] = COMMON_TIMEZONES;\n\n // Form fields\n public Name = '';\n public Description = '';\n public SelectedJobTypeID = '';\n public CronExpression = '';\n public Timezone = 'UTC';\n public Status: 'Active' | 'Disabled' | 'Expired' | 'Paused' | 'Pending' = 'Active';\n public ConcurrencyMode = 'Skip';\n public Configuration = '';\n public NotifyOnSuccess = false;\n public NotifyOnFailure = true;\n\n // Stats (edit mode)\n public TotalRuns = 0;\n public SuccessRuns = 0;\n public FailedRuns = 0;\n\n public readonly StatusOptions = ['Pending', 'Active', 'Paused', 'Disabled'];\n public readonly ConcurrencyOptions = ['Skip', 'Queue', 'Concurrent'];\n\n private isInitialized = false;\n\n public get CanSave(): boolean {\n return this.Name.trim().length > 0\n && this.SelectedJobTypeID.length > 0\n && this.CronExpression.trim().length > 0;\n }\n\n async ngOnInit(): Promise<void> {\n this.IsLoading = true;\n this.cdr.markForCheck();\n\n this.JobTypes = await this.scheduledJobService.LoadJobTypes();\n await this.loadOrCreateJob();\n\n this.isInitialized = true;\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n\n public async Save(): Promise<void> {\n if (!this.CanSave || this.IsSaving) return;\n\n this.IsSaving = true;\n this.cdr.markForCheck();\n\n try {\n const job = await this.getOrCreateEntity();\n this.applyFormToEntity(job);\n\n const saved = await job.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(\n this.IsNew ? 'Scheduled job created successfully' : 'Scheduled job updated successfully',\n 'success', 3000\n );\n this.Saved.emit(job);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification(\n `Failed to save: ${job.LatestResult?.Message ?? 'Unknown error'}`,\n 'error', 5000\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n MJNotificationService.Instance.CreateSimpleNotification(`Error saving: ${msg}`, 'error', 5000);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public ConfirmDelete(): void {\n this.ShowDeleteConfirm = true;\n this.cdr.markForCheck();\n }\n\n public CancelDelete(): void {\n this.ShowDeleteConfirm = false;\n this.cdr.markForCheck();\n }\n\n public async DeleteJob(): Promise<void> {\n if (!this.Job || this.IsNew) return;\n\n this.ShowDeleteConfirm = false;\n this.IsSaving = true;\n this.cdr.markForCheck();\n\n try {\n const jobID = this.Job.ID;\n const deleted = await this.Job.Delete();\n if (deleted) {\n MJNotificationService.Instance.CreateSimpleNotification('Scheduled job deleted', 'success', 3000);\n this.Deleted.emit(jobID);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification('Failed to delete job', 'error', 5000);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n MJNotificationService.Instance.CreateSimpleNotification(`Error deleting: ${msg}`, 'error', 5000);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public Cancel(): void {\n this.Cancelled.emit();\n }\n\n // ── Private ───────────────────────────────────────────────\n\n private async loadOrCreateJob(): Promise<void> {\n if (this._scheduledJobID) {\n await this.loadExistingJob(this._scheduledJobID);\n } else {\n this.initNewJob();\n }\n }\n\n private async loadExistingJob(jobID: string): Promise<void> {\n this.IsNew = false;\n const job = await this.scheduledJobService.LoadJob(jobID);\n if (job) {\n this.Job = job;\n this.populateFormFromEntity(job);\n await this.loadRunStats(jobID);\n }\n this.cdr.markForCheck();\n }\n\n private initNewJob(): void {\n this.IsNew = true;\n this.Job = null;\n this.Name = '';\n this.Description = '';\n this.SelectedJobTypeID = this.JobTypeID ?? '';\n this.CronExpression = '';\n this.Timezone = 'UTC';\n this.Status = 'Active';\n this.ConcurrencyMode = 'Skip';\n this.Configuration = this.DefaultConfiguration ?? '';\n this.NotifyOnSuccess = false;\n this.NotifyOnFailure = true;\n this.TotalRuns = 0;\n this.SuccessRuns = 0;\n this.FailedRuns = 0;\n this.cdr.markForCheck();\n }\n\n private populateFormFromEntity(job: MJScheduledJobEntity): void {\n this.Name = job.Name ?? '';\n this.Description = job.Description ?? '';\n this.SelectedJobTypeID = job.JobTypeID ?? '';\n this.CronExpression = job.CronExpression ?? '';\n this.Timezone = job.Timezone ?? 'UTC';\n this.Status = job.Status ?? 'Active';\n this.ConcurrencyMode = job.Get('ConcurrencyMode') as string ?? 'Skip';\n this.Configuration = job.Configuration ?? '';\n this.NotifyOnSuccess = job.Get('NotifyOnSuccess') as boolean ?? false;\n this.NotifyOnFailure = job.Get('NotifyOnFailure') as boolean ?? true;\n }\n\n private applyFormToEntity(job: MJScheduledJobEntity): void {\n job.Name = this.Name.trim();\n job.Description = this.Description.trim();\n job.JobTypeID = this.SelectedJobTypeID;\n job.CronExpression = this.CronExpression.trim();\n job.Timezone = this.Timezone;\n job.Status = this.Status;\n job.Set('ConcurrencyMode', this.ConcurrencyMode);\n job.Configuration = this.Configuration;\n job.Set('NotifyOnSuccess', this.NotifyOnSuccess);\n job.Set('NotifyOnFailure', this.NotifyOnFailure);\n }\n\n private async getOrCreateEntity(): Promise<MJScheduledJobEntity> {\n if (this.Job && !this.IsNew) {\n return this.Job;\n }\n const md = this.ProviderToUse;\n const job = await md.GetEntityObject<MJScheduledJobEntity>('MJ: Scheduled Jobs', md.CurrentUser);\n job.NewRecord();\n return job;\n }\n\n private async loadRunStats(jobID: string): Promise<void> {\n const runs = await this.scheduledJobService.LoadJobRuns(jobID, 100);\n this.TotalRuns = runs.length;\n this.SuccessRuns = runs.filter(r => r.Status === 'Completed').length;\n this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;\n }\n}\n","@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n"]}
1
+ {"version":3,"file":"scheduled-job-editor.component.js","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.ts","../../../../src/lib/panels/scheduled-job-editor/scheduled-job-editor.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n OnInit,\n inject,\n} from '@angular/core';\nimport { MJScheduledJobEntity, MJScheduledJobTypeEntity } from '@memberjunction/core-entities';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { ScheduledJobService } from '../../services/scheduled-job.service';\n\n/** IANA timezone list subset for the dropdown */\nconst COMMON_TIMEZONES = [\n 'UTC',\n 'America/New_York',\n 'America/Chicago',\n 'America/Denver',\n 'America/Los_Angeles',\n 'America/Phoenix',\n 'America/Anchorage',\n 'Pacific/Honolulu',\n 'Europe/London',\n 'Europe/Paris',\n 'Europe/Berlin',\n 'Asia/Tokyo',\n 'Asia/Shanghai',\n 'Asia/Kolkata',\n 'Australia/Sydney',\n];\n\n@Component({\n selector: 'mj-scheduled-job-editor',\n standalone: false,\n templateUrl: './scheduled-job-editor.component.html',\n styleUrls: ['./scheduled-job-editor.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobEditorComponent extends BaseAngularComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n private scheduledJobService = inject(ScheduledJobService);\n\n // ── Inputs ────────────────────────────────────────────────\n private _scheduledJobID: string | null = null;\n\n @Input()\n set ScheduledJobID(value: string | null) {\n if (value !== this._scheduledJobID) {\n this._scheduledJobID = value;\n if (this.isInitialized) {\n this.loadOrCreateJob();\n }\n }\n }\n get ScheduledJobID(): string | null {\n return this._scheduledJobID;\n }\n\n @Input() JobTypeID: string | null = null;\n @Input() DefaultConfiguration: string | null = null;\n @Input() HideJobType = false;\n\n // ── Outputs ───────────────────────────────────────────────\n @Output() Saved = new EventEmitter<MJScheduledJobEntity>();\n @Output() Deleted = new EventEmitter<string>();\n @Output() Cancelled = new EventEmitter<void>();\n\n // ── State ─────────────────────────────────────────────────\n public IsLoading = false;\n public IsSaving = false;\n public IsNew = false;\n public ShowDeleteConfirm = false;\n\n public Job: MJScheduledJobEntity | null = null;\n public JobTypes: MJScheduledJobTypeEntity[] = [];\n public Timezones: string[] = COMMON_TIMEZONES;\n\n // Form fields\n public Name = '';\n public Description = '';\n public SelectedJobTypeID = '';\n public CronExpression = '';\n public Timezone = 'UTC';\n public Status: 'Active' | 'Disabled' | 'Expired' | 'Paused' | 'Pending' = 'Active';\n public ConcurrencyMode = 'Skip';\n public Configuration = '';\n public NotifyOnSuccess = false;\n public NotifyOnFailure = true;\n /** When true AND the job has never run, the scheduler queues it for immediate\n * execution on the next poll instead of waiting for the next cron tick.\n * Useful for newly-seeded jobs that should not wait up to a full cron interval. */\n public RunImmediatelyIfNeverRun = false;\n\n // Stats (edit mode)\n public TotalRuns = 0;\n public SuccessRuns = 0;\n public FailedRuns = 0;\n\n public readonly StatusOptions = ['Pending', 'Active', 'Paused', 'Disabled'];\n public readonly ConcurrencyOptions = ['Skip', 'Queue', 'Concurrent'];\n\n private isInitialized = false;\n\n public get CanSave(): boolean {\n return this.Name.trim().length > 0\n && this.SelectedJobTypeID.length > 0\n && this.CronExpression.trim().length > 0;\n }\n\n async ngOnInit(): Promise<void> {\n this.IsLoading = true;\n this.cdr.markForCheck();\n\n this.JobTypes = await this.scheduledJobService.LoadJobTypes();\n await this.loadOrCreateJob();\n\n this.isInitialized = true;\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n\n public async Save(): Promise<void> {\n if (!this.CanSave || this.IsSaving) return;\n\n this.IsSaving = true;\n this.cdr.markForCheck();\n\n try {\n const job = await this.getOrCreateEntity();\n this.applyFormToEntity(job);\n\n const saved = await job.Save();\n if (saved) {\n MJNotificationService.Instance.CreateSimpleNotification(\n this.IsNew ? 'Scheduled job created successfully' : 'Scheduled job updated successfully',\n 'success', 3000\n );\n this.Saved.emit(job);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification(\n `Failed to save: ${job.LatestResult?.Message ?? 'Unknown error'}`,\n 'error', 5000\n );\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n MJNotificationService.Instance.CreateSimpleNotification(`Error saving: ${msg}`, 'error', 5000);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public ConfirmDelete(): void {\n this.ShowDeleteConfirm = true;\n this.cdr.markForCheck();\n }\n\n public CancelDelete(): void {\n this.ShowDeleteConfirm = false;\n this.cdr.markForCheck();\n }\n\n public async DeleteJob(): Promise<void> {\n if (!this.Job || this.IsNew) return;\n\n this.ShowDeleteConfirm = false;\n this.IsSaving = true;\n this.cdr.markForCheck();\n\n try {\n const jobID = this.Job.ID;\n const deleted = await this.Job.Delete();\n if (deleted) {\n MJNotificationService.Instance.CreateSimpleNotification('Scheduled job deleted', 'success', 3000);\n this.Deleted.emit(jobID);\n } else {\n MJNotificationService.Instance.CreateSimpleNotification('Failed to delete job', 'error', 5000);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n MJNotificationService.Instance.CreateSimpleNotification(`Error deleting: ${msg}`, 'error', 5000);\n } finally {\n this.IsSaving = false;\n this.cdr.markForCheck();\n }\n }\n\n public Cancel(): void {\n this.Cancelled.emit();\n }\n\n // ── Private ───────────────────────────────────────────────\n\n private async loadOrCreateJob(): Promise<void> {\n if (this._scheduledJobID) {\n await this.loadExistingJob(this._scheduledJobID);\n } else {\n this.initNewJob();\n }\n }\n\n private async loadExistingJob(jobID: string): Promise<void> {\n this.IsNew = false;\n const job = await this.scheduledJobService.LoadJob(jobID);\n if (job) {\n this.Job = job;\n this.populateFormFromEntity(job);\n await this.loadRunStats(jobID);\n }\n this.cdr.markForCheck();\n }\n\n private initNewJob(): void {\n this.IsNew = true;\n this.Job = null;\n this.Name = '';\n this.Description = '';\n this.SelectedJobTypeID = this.JobTypeID ?? '';\n this.CronExpression = '';\n this.Timezone = 'UTC';\n this.Status = 'Active';\n this.ConcurrencyMode = 'Skip';\n this.Configuration = this.DefaultConfiguration ?? '';\n this.NotifyOnSuccess = false;\n this.NotifyOnFailure = true;\n this.RunImmediatelyIfNeverRun = false;\n this.TotalRuns = 0;\n this.SuccessRuns = 0;\n this.FailedRuns = 0;\n this.cdr.markForCheck();\n }\n\n private populateFormFromEntity(job: MJScheduledJobEntity): void {\n this.Name = job.Name ?? '';\n this.Description = job.Description ?? '';\n this.SelectedJobTypeID = job.JobTypeID ?? '';\n this.CronExpression = job.CronExpression ?? '';\n this.Timezone = job.Timezone ?? 'UTC';\n this.Status = job.Status ?? 'Active';\n this.ConcurrencyMode = job.Get('ConcurrencyMode') as string ?? 'Skip';\n this.Configuration = job.Configuration ?? '';\n this.NotifyOnSuccess = job.Get('NotifyOnSuccess') as boolean ?? false;\n this.NotifyOnFailure = job.Get('NotifyOnFailure') as boolean ?? true;\n this.RunImmediatelyIfNeverRun = job.RunImmediatelyIfNeverRun ?? false;\n }\n\n private applyFormToEntity(job: MJScheduledJobEntity): void {\n job.Name = this.Name.trim();\n job.Description = this.Description.trim();\n job.JobTypeID = this.SelectedJobTypeID;\n job.CronExpression = this.CronExpression.trim();\n job.Timezone = this.Timezone;\n job.Status = this.Status;\n job.ConcurrencyMode = this.ConcurrencyMode as 'Skip' | 'Queue' | 'Concurrent';\n job.Configuration = this.Configuration;\n job.NotifyOnSuccess = this.NotifyOnSuccess;\n job.NotifyOnFailure = this.NotifyOnFailure;\n job.RunImmediatelyIfNeverRun = this.RunImmediatelyIfNeverRun;\n }\n\n private async getOrCreateEntity(): Promise<MJScheduledJobEntity> {\n if (this.Job && !this.IsNew) {\n return this.Job;\n }\n const md = this.ProviderToUse;\n const job = await md.GetEntityObject<MJScheduledJobEntity>('MJ: Scheduled Jobs', md.CurrentUser);\n job.NewRecord();\n return job;\n }\n\n private async loadRunStats(jobID: string): Promise<void> {\n const runs = await this.scheduledJobService.LoadJobRuns(jobID, 100);\n this.TotalRuns = runs.length;\n this.SuccessRuns = runs.filter(r => r.Status === 'Completed').length;\n this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;\n }\n}\n","@if (IsLoading) {\n <div class=\"editor-loading\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n} @else {\n <div class=\"editor-container\">\n <!-- General Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-circle-info\"></i> General\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobName\">Name <span class=\"required\">*</span></label>\n <input id=\"jobName\" type=\"text\" [(ngModel)]=\"Name\" placeholder=\"Job name\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"jobDesc\">Description</label>\n <textarea id=\"jobDesc\" [(ngModel)]=\"Description\" rows=\"2\" placeholder=\"Optional description\"></textarea>\n </div>\n\n @if (!HideJobType) {\n <div class=\"form-group\">\n <label for=\"jobType\">Job Type <span class=\"required\">*</span></label>\n <select id=\"jobType\" [(ngModel)]=\"SelectedJobTypeID\">\n <option value=\"\">-- Select Type --</option>\n @for (jt of JobTypes; track jt.ID) {\n <option [value]=\"jt.ID\">{{ jt.Name }}</option>\n }\n </select>\n </div>\n }\n\n <div class=\"form-group\">\n <label for=\"jobStatus\">Status</label>\n <select id=\"jobStatus\" [(ngModel)]=\"Status\">\n @for (s of StatusOptions; track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Schedule Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-clock\"></i> Schedule\n </div>\n\n <div class=\"form-group\">\n <label for=\"cronExpr\">Cron Expression <span class=\"required\">*</span></label>\n <input id=\"cronExpr\" type=\"text\" [(ngModel)]=\"CronExpression\"\n placeholder=\"e.g. 0 */15 * * * *\" />\n <div class=\"hint\">Standard cron format: second minute hour day month weekday</div>\n </div>\n\n <div class=\"form-group\">\n <label for=\"timezone\">Timezone</label>\n <select id=\"timezone\" [(ngModel)]=\"Timezone\">\n @for (tz of Timezones; track tz) {\n <option [value]=\"tz\">{{ tz }}</option>\n }\n </select>\n </div>\n\n <div class=\"form-group\">\n <label for=\"concurrency\">Concurrency</label>\n <select id=\"concurrency\" [(ngModel)]=\"ConcurrencyMode\">\n @for (c of ConcurrencyOptions; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"RunImmediatelyIfNeverRun\" />\n Run immediately on first poll if never run\n </label>\n <div class=\"hint\">When checked, a job that has never run will execute on the next polling cycle instead of waiting for the next cron tick. Useful for newly-seeded jobs.</div>\n </div>\n </div>\n\n <!-- Configuration Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-code\"></i> Configuration\n </div>\n\n <div class=\"form-group\">\n <label for=\"config\">Configuration JSON</label>\n <textarea id=\"config\" [(ngModel)]=\"Configuration\" rows=\"6\"\n class=\"code-textarea\"\n placeholder='{ \"key\": \"value\" }'></textarea>\n </div>\n </div>\n\n <!-- Notifications Section -->\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-bell\"></i> Notifications\n </div>\n\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnSuccess\" />\n Notify on success\n </label>\n </div>\n <div class=\"toggle-row\">\n <label class=\"toggle-label\">\n <input type=\"checkbox\" [(ngModel)]=\"NotifyOnFailure\" />\n Notify on failure\n </label>\n </div>\n </div>\n\n <!-- Statistics (edit mode only) -->\n @if (!IsNew && TotalRuns > 0) {\n <div class=\"form-section\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-chart-bar\"></i> Statistics\n </div>\n <div class=\"stats-grid\">\n <div class=\"stat-item\">\n <span class=\"stat-value\">{{ TotalRuns }}</span>\n <span class=\"stat-label\">Total Runs</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value success\">{{ SuccessRuns }}</span>\n <span class=\"stat-label\">Succeeded</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-value error\">{{ FailedRuns }}</span>\n <span class=\"stat-label\">Failed</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Footer Actions -->\n <div class=\"editor-footer\">\n @if (!IsNew) {\n <button class=\"btn btn-danger\" (click)=\"ConfirmDelete()\" [disabled]=\"IsSaving\">\n <i class=\"fa-solid fa-trash\"></i> Delete\n </button>\n }\n <div class=\"footer-right\">\n <button class=\"btn btn-primary\" (click)=\"Save()\" [disabled]=\"!CanSave || IsSaving\">\n @if (IsSaving) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n {{ IsNew ? 'Create' : 'Save' }}\n </button>\n <button class=\"btn btn-secondary\" (click)=\"Cancel()\" [disabled]=\"IsSaving\">\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Delete Confirmation -->\n @if (ShowDeleteConfirm) {\n <div class=\"confirm-overlay\" (click)=\"CancelDelete()\">\n <div class=\"confirm-dialog\" (click)=\"$event.stopPropagation()\">\n <p>Are you sure you want to delete this scheduled job?</p>\n <div class=\"confirm-actions\">\n <button class=\"btn btn-danger\" (click)=\"DeleteJob()\">Delete</button>\n <button class=\"btn btn-secondary\" (click)=\"CancelDelete()\">Cancel</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-scheduling",
3
- "version": "5.38.0",
3
+ "version": "5.39.0",
4
4
  "description": "MemberJunction: Reusable Angular components for viewing and editing Scheduled Jobs - panels, slide-in, dialog, and service",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -31,13 +31,13 @@
31
31
  "@angular/forms": "21.1.3"
32
32
  },
33
33
  "dependencies": {
34
- "@memberjunction/core": "5.38.0",
35
- "@memberjunction/ng-base-types": "5.38.0",
36
- "@memberjunction/core-entities": "5.38.0",
37
- "@memberjunction/global": "5.38.0",
38
- "@memberjunction/ng-notifications": "5.38.0",
39
- "@memberjunction/ng-shared-generic": "5.38.0",
40
- "@memberjunction/ng-ui-components": "5.38.0",
34
+ "@memberjunction/core": "5.39.0",
35
+ "@memberjunction/ng-base-types": "5.39.0",
36
+ "@memberjunction/core-entities": "5.39.0",
37
+ "@memberjunction/global": "5.39.0",
38
+ "@memberjunction/ng-notifications": "5.39.0",
39
+ "@memberjunction/ng-shared-generic": "5.39.0",
40
+ "@memberjunction/ng-ui-components": "5.39.0",
41
41
  "tslib": "^2.8.1",
42
42
  "rxjs": "^7.8.2"
43
43
  },