@memberjunction/ng-scheduling 6.1.0-edge.5 → 6.1.0-edge.7
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.
- package/dist/lib/dialogs/scheduled-job-dialog.component.js +3 -3
- package/dist/lib/dialogs/scheduled-job-dialog.component.js.map +1 -1
- package/dist/lib/panels/scheduled-job-editor/scheduled-job-editor.component.js +3 -3
- package/dist/lib/panels/scheduled-job-editor/scheduled-job-editor.component.js.map +1 -1
- package/dist/lib/panels/scheduled-job-summary/scheduled-job-summary.component.js +3 -3
- package/dist/lib/panels/scheduled-job-summary/scheduled-job-summary.component.js.map +1 -1
- package/dist/lib/scheduling.module.js +4 -4
- package/dist/lib/scheduling.module.js.map +1 -1
- package/dist/lib/services/scheduled-job.service.js +3 -3
- package/dist/lib/services/scheduled-job.service.js.map +1 -1
- package/dist/lib/slide-panel/scheduled-job-slide-panel.component.js +3 -3
- package/dist/lib/slide-panel/scheduled-job-slide-panel.component.js.map +1 -1
- package/package.json +14 -14
|
@@ -22,8 +22,8 @@ export class ScheduledJobDialogComponent {
|
|
|
22
22
|
OnDeleted(_jobID) {
|
|
23
23
|
this.Close.emit({ Saved: false, Deleted: true });
|
|
24
24
|
}
|
|
25
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
26
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
25
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
26
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: ScheduledJobDialogComponent, isStandalone: false, selector: "mj-scheduled-job-dialog", inputs: { Visible: "Visible", ScheduledJobID: "ScheduledJobID", JobTypeID: "JobTypeID", DefaultConfiguration: "DefaultConfiguration", HideJobType: "HideJobType", Width: "Width" }, outputs: { Close: "Close" }, ngImport: i0, template: `
|
|
27
27
|
@if (Visible) {
|
|
28
28
|
<mj-dialog [Visible]="true" [Title]="DialogTitle" (Close)="OnClose()" [Width]="Width">
|
|
29
29
|
<mj-scheduled-job-editor
|
|
@@ -39,7 +39,7 @@ export class ScheduledJobDialogComponent {
|
|
|
39
39
|
}
|
|
40
40
|
`, isInline: true, dependencies: [{ kind: "component", type: i1.MJDialogComponent, selector: "mj-dialog", inputs: ["Visible", "Title", "Width", "Height", "Size", "Closeable", "MinWidth", "Role"], outputs: ["Close"] }, { kind: "component", type: i2.ScheduledJobEditorComponent, selector: "mj-scheduled-job-editor", inputs: ["ScheduledJobID", "JobTypeID", "DefaultConfiguration", "HideJobType"], outputs: ["Saved", "Deleted", "Cancelled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
41
41
|
}
|
|
42
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
42
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobDialogComponent, decorators: [{
|
|
43
43
|
type: Component,
|
|
44
44
|
args: [{
|
|
45
45
|
selector: 'mj-scheduled-job-dialog',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled-job-dialog.component.js","sourceRoot":"","sources":["../../../src/lib/dialogs/scheduled-job-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,YAAY,EACZ,uBAAuB,GAC1B,MAAM,eAAe,CAAC;;;;AA8BvB,MAAM,OAAO,2BAA2B;IAC3B,OAAO,GAAG,KAAK,CAAC;IAChB,cAAc,GAAkB,IAAI,CAAC;IACrC,SAAS,GAAkB,IAAI,CAAC;IAChC,oBAAoB,GAAkB,IAAI,CAAC;IAC3C,WAAW,GAAG,KAAK,CAAC;IACpB,KAAK,GAAG,GAAG,CAAC;IAEX,KAAK,GAAG,IAAI,YAAY,EAA4B,CAAC;IAE/D,IAAW,WAAW;QAClB,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACrE,CAAC;IAEM,OAAO;QACV,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACtC,CAAC;IAEM,OAAO,CAAC,GAAyB;QACpC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/C,CAAC;IAEM,SAAS,CAAC,MAAc;QAC3B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;
|
|
1
|
+
{"version":3,"file":"scheduled-job-dialog.component.js","sourceRoot":"","sources":["../../../src/lib/dialogs/scheduled-job-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,YAAY,EACZ,uBAAuB,GAC1B,MAAM,eAAe,CAAC;;;;AA8BvB,MAAM,OAAO,2BAA2B;IAC3B,OAAO,GAAG,KAAK,CAAC;IAChB,cAAc,GAAkB,IAAI,CAAC;IACrC,SAAS,GAAkB,IAAI,CAAC;IAChC,oBAAoB,GAAkB,IAAI,CAAC;IAC3C,WAAW,GAAG,KAAK,CAAC;IACpB,KAAK,GAAG,GAAG,CAAC;IAEX,KAAK,GAAG,IAAI,YAAY,EAA4B,CAAC;IAE/D,IAAW,WAAW;QAClB,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACrE,CAAC;IAEM,OAAO;QACV,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACtC,CAAC;IAEM,OAAO,CAAC,GAAyB;QACpC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/C,CAAC;IAEM,SAAS,CAAC,MAAc;QAC3B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;wGAxBQ,2BAA2B;4FAA3B,2BAA2B,qSAjB1B;;;;;;;;;;;;;;KAcT;;4FAGQ,2BAA2B;kBApBvC,SAAS;mBAAC;oBACP,QAAQ,EAAE,yBAAyB;oBACnC,UAAU,EAAE,KAAK;oBACjB,QAAQ,EAAE;;;;;;;;;;;;;;KAcT;oBACD,eAAe,EAAE,uBAAuB,CAAC,MAAM;iBAClD;;sBAEI,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBAEL,MAAM","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { MJScheduledJobEntity } from '@memberjunction/core-entities';\n\n/** Result emitted when the dialog closes */\nexport interface ScheduledJobDialogResult {\n Saved: boolean;\n Job?: MJScheduledJobEntity;\n Deleted?: boolean;\n}\n\n@Component({\n selector: 'mj-scheduled-job-dialog',\n standalone: false,\n template: `\n @if (Visible) {\n <mj-dialog [Visible]=\"true\" [Title]=\"DialogTitle\" (Close)=\"OnClose()\" [Width]=\"Width\">\n <mj-scheduled-job-editor\n [ScheduledJobID]=\"ScheduledJobID\"\n [JobTypeID]=\"JobTypeID\"\n [DefaultConfiguration]=\"DefaultConfiguration\"\n [HideJobType]=\"HideJobType\"\n (Saved)=\"OnSaved($event)\"\n (Deleted)=\"OnDeleted($event)\"\n (Cancelled)=\"OnClose()\">\n </mj-scheduled-job-editor>\n </mj-dialog>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobDialogComponent {\n @Input() Visible = false;\n @Input() ScheduledJobID: string | null = null;\n @Input() JobTypeID: string | null = null;\n @Input() DefaultConfiguration: string | null = null;\n @Input() HideJobType = false;\n @Input() Width = 580;\n\n @Output() Close = new EventEmitter<ScheduledJobDialogResult>();\n\n public get DialogTitle(): string {\n return this.ScheduledJobID ? 'Edit Schedule' : 'Create Schedule';\n }\n\n public OnClose(): void {\n this.Close.emit({ Saved: false });\n }\n\n public OnSaved(job: MJScheduledJobEntity): void {\n this.Close.emit({ Saved: true, Job: job });\n }\n\n public OnDeleted(_jobID: string): void {\n this.Close.emit({ Saved: false, Deleted: true });\n }\n}\n"]}
|
|
@@ -232,10 +232,10 @@ export class ScheduledJobEditorComponent extends BaseAngularComponent {
|
|
|
232
232
|
this.SuccessRuns = runs.filter(r => r.Status === 'Completed').length;
|
|
233
233
|
this.FailedRuns = runs.filter(r => r.Status === 'Failed').length;
|
|
234
234
|
}
|
|
235
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
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 });
|
|
235
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobEditorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
236
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", 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 });
|
|
237
237
|
}
|
|
238
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
238
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobEditorComponent, decorators: [{
|
|
239
239
|
type: Component,
|
|
240
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"] }]
|
|
241
241
|
}], propDecorators: { ScheduledJobID: [{
|
|
@@ -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 /** 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"]}
|
|
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"]}
|
|
@@ -68,10 +68,10 @@ export class ScheduledJobSummaryComponent {
|
|
|
68
68
|
this.IsLoading = false;
|
|
69
69
|
this.cdr.markForCheck();
|
|
70
70
|
}
|
|
71
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
72
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
71
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobSummaryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
72
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: ScheduledJobSummaryComponent, isStandalone: false, selector: "mj-scheduled-job-summary", inputs: { ScheduledJobID: "ScheduledJobID", ShowEditButton: "ShowEditButton" }, outputs: { EditRequested: "EditRequested" }, ngImport: i0, template: "@if (IsLoading) {\n <div class=\"summary-loading\">\n <mj-loading size=\"small\"></mj-loading>\n </div>\n} @else if (Job) {\n <div class=\"summary-card\">\n <div class=\"summary-header\">\n <div class=\"summary-name\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n {{ Job.Name }}\n </div>\n <span class=\"status-chip\" [class]=\"StatusClass\">{{ Job.Status }}</span>\n </div>\n\n <div class=\"summary-meta\">\n <div class=\"meta-item\">\n <i class=\"fa-regular fa-clock\"></i>\n <span>{{ Job.CronExpression }}</span>\n </div>\n @if (Job.Timezone && Job.Timezone !== 'UTC') {\n <div class=\"meta-item\">\n <i class=\"fa-solid fa-globe\"></i>\n <span>{{ Job.Timezone }}</span>\n </div>\n }\n </div>\n\n <div class=\"summary-stats\">\n <div class=\"stat\">\n <span class=\"stat-num\">{{ TotalRuns }}</span>\n <span class=\"stat-lbl\">Runs</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num success\">{{ SuccessRuns }}</span>\n <span class=\"stat-lbl\">OK</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num error\">{{ FailedRuns }}</span>\n <span class=\"stat-lbl\">Failed</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num\">{{ SuccessRate }}</span>\n <span class=\"stat-lbl\">Rate</span>\n </div>\n </div>\n\n @if (ShowEditButton) {\n <div class=\"summary-actions\">\n <button class=\"edit-btn\" (click)=\"OnEditClick()\">\n <i class=\"fa-solid fa-pen-to-square\"></i> Edit Schedule\n </button>\n </div>\n }\n </div>\n} @else {\n <mj-empty-state class=\"summary-empty\" Size=\"compact\"\n Icon=\"fa-regular fa-calendar-xmark\"\n Title=\"No schedule configured\" />\n}\n", styles: [".summary-loading {\n display: flex;\n justify-content: center;\n padding: 16px;\n}\n\n.summary-card {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 14px;\n background: var(--mj-bg-surface);\n}\n\n.summary-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 10px;\n}\n\n.summary-name {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.summary-name i {\n color: var(--mj-brand-primary);\n}\n\n.status-chip {\n font-size: 11px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.status-active {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.status-paused {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n\n.status-disabled {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.status-pending {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.summary-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 10px;\n}\n\n.meta-item {\n display: flex;\n align-items: center;\n gap: 5px;\n font-size: 12px;\n color: var(--mj-text-secondary);\n}\n\n.meta-item i {\n font-size: 11px;\n}\n\n.summary-stats {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.stat {\n text-align: center;\n flex: 1;\n}\n\n.stat-num {\n display: block;\n font-size: 16px;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.stat-num.success {\n color: var(--mj-status-success);\n}\n\n.stat-num.error {\n color: var(--mj-status-error);\n}\n\n.stat-lbl {\n display: block;\n font-size: 10px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.summary-actions {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.edit-btn {\n background: none;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n padding: 6px 12px;\n font-size: 12px;\n color: var(--mj-brand-primary);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n transition: background 0.15s;\n}\n\n.edit-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.summary-empty {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n color: var(--mj-text-muted);\n font-size: 13px;\n}\n\n.summary-empty i {\n font-size: 16px;\n}\n"], dependencies: [{ kind: "component", type: i1.LoadingComponent, selector: "mj-loading", inputs: ["text", "showText", "animationDuration", "size", "textColor", "logoColor", "logoGradient", "animation"] }, { kind: "component", type: i2.MJEmptyStateComponent, selector: "mj-empty-state", inputs: ["Icon", "Title", "Message", "ActionText", "ActionIcon", "ActionVariant", "Variant", "Size", "Role"], outputs: ["Action"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
73
73
|
}
|
|
74
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
74
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobSummaryComponent, decorators: [{
|
|
75
75
|
type: Component,
|
|
76
76
|
args: [{ selector: 'mj-scheduled-job-summary', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (IsLoading) {\n <div class=\"summary-loading\">\n <mj-loading size=\"small\"></mj-loading>\n </div>\n} @else if (Job) {\n <div class=\"summary-card\">\n <div class=\"summary-header\">\n <div class=\"summary-name\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n {{ Job.Name }}\n </div>\n <span class=\"status-chip\" [class]=\"StatusClass\">{{ Job.Status }}</span>\n </div>\n\n <div class=\"summary-meta\">\n <div class=\"meta-item\">\n <i class=\"fa-regular fa-clock\"></i>\n <span>{{ Job.CronExpression }}</span>\n </div>\n @if (Job.Timezone && Job.Timezone !== 'UTC') {\n <div class=\"meta-item\">\n <i class=\"fa-solid fa-globe\"></i>\n <span>{{ Job.Timezone }}</span>\n </div>\n }\n </div>\n\n <div class=\"summary-stats\">\n <div class=\"stat\">\n <span class=\"stat-num\">{{ TotalRuns }}</span>\n <span class=\"stat-lbl\">Runs</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num success\">{{ SuccessRuns }}</span>\n <span class=\"stat-lbl\">OK</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num error\">{{ FailedRuns }}</span>\n <span class=\"stat-lbl\">Failed</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num\">{{ SuccessRate }}</span>\n <span class=\"stat-lbl\">Rate</span>\n </div>\n </div>\n\n @if (ShowEditButton) {\n <div class=\"summary-actions\">\n <button class=\"edit-btn\" (click)=\"OnEditClick()\">\n <i class=\"fa-solid fa-pen-to-square\"></i> Edit Schedule\n </button>\n </div>\n }\n </div>\n} @else {\n <mj-empty-state class=\"summary-empty\" Size=\"compact\"\n Icon=\"fa-regular fa-calendar-xmark\"\n Title=\"No schedule configured\" />\n}\n", styles: [".summary-loading {\n display: flex;\n justify-content: center;\n padding: 16px;\n}\n\n.summary-card {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 14px;\n background: var(--mj-bg-surface);\n}\n\n.summary-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 10px;\n}\n\n.summary-name {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.summary-name i {\n color: var(--mj-brand-primary);\n}\n\n.status-chip {\n font-size: 11px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.status-active {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.status-paused {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n\n.status-disabled {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.status-pending {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.summary-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 10px;\n}\n\n.meta-item {\n display: flex;\n align-items: center;\n gap: 5px;\n font-size: 12px;\n color: var(--mj-text-secondary);\n}\n\n.meta-item i {\n font-size: 11px;\n}\n\n.summary-stats {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.stat {\n text-align: center;\n flex: 1;\n}\n\n.stat-num {\n display: block;\n font-size: 16px;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.stat-num.success {\n color: var(--mj-status-success);\n}\n\n.stat-num.error {\n color: var(--mj-status-error);\n}\n\n.stat-lbl {\n display: block;\n font-size: 10px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.summary-actions {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.edit-btn {\n background: none;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n padding: 6px 12px;\n font-size: 12px;\n color: var(--mj-brand-primary);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n transition: background 0.15s;\n}\n\n.edit-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.summary-empty {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n color: var(--mj-text-muted);\n font-size: 13px;\n}\n\n.summary-empty i {\n font-size: 16px;\n}\n"] }]
|
|
77
77
|
}], propDecorators: { ScheduledJobID: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled-job-summary.component.js","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-summary/scheduled-job-summary.component.ts","../../../../src/lib/panels/scheduled-job-summary/scheduled-job-summary.component.html"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"scheduled-job-summary.component.js","sourceRoot":"","sources":["../../../../src/lib/panels/scheduled-job-summary/scheduled-job-summary.component.ts","../../../../src/lib/panels/scheduled-job-summary/scheduled-job-summary.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n inject,\n OnInit,\n} from '@angular/core';\nimport { MJScheduledJobEntity, MJScheduledJobRunEntity } from '@memberjunction/core-entities';\nimport { ScheduledJobService } from '../../services/scheduled-job.service';\n\n@Component({\n selector: 'mj-scheduled-job-summary',\n standalone: false,\n templateUrl: './scheduled-job-summary.component.html',\n styleUrls: ['./scheduled-job-summary.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobSummaryComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n private scheduledJobService = inject(ScheduledJobService);\n\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.loadJob();\n }\n }\n }\n get ScheduledJobID(): string | null {\n return this._scheduledJobID;\n }\n\n @Input() ShowEditButton = true;\n\n @Output() EditRequested = new EventEmitter<string>();\n\n public IsLoading = false;\n public Job: MJScheduledJobEntity | null = null;\n public TotalRuns = 0;\n public SuccessRuns = 0;\n public FailedRuns = 0;\n public LastRun: MJScheduledJobRunEntity | null = null;\n\n private isInitialized = false;\n\n async ngOnInit(): Promise<void> {\n await this.loadJob();\n this.isInitialized = true;\n }\n\n public OnEditClick(): void {\n if (this._scheduledJobID) {\n this.EditRequested.emit(this._scheduledJobID);\n }\n }\n\n public get StatusClass(): string {\n const status = this.Job?.Status?.toLowerCase() ?? '';\n if (status === 'active') return 'status-active';\n if (status === 'paused') return 'status-paused';\n if (status === 'disabled') return 'status-disabled';\n return 'status-pending';\n }\n\n public get SuccessRate(): string {\n if (this.TotalRuns === 0) return 'N/A';\n return `${Math.round((this.SuccessRuns / this.TotalRuns) * 100)}%`;\n }\n\n private async loadJob(): Promise<void> {\n if (!this._scheduledJobID) {\n this.Job = null;\n this.cdr.markForCheck();\n return;\n }\n\n this.IsLoading = true;\n this.cdr.markForCheck();\n\n this.Job = await this.scheduledJobService.LoadJob(this._scheduledJobID);\n const runs = await this.scheduledJobService.LoadJobRuns(this._scheduledJobID, 50);\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 this.LastRun = runs.length > 0 ? runs[0] : null;\n\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n}\n","@if (IsLoading) {\n <div class=\"summary-loading\">\n <mj-loading size=\"small\"></mj-loading>\n </div>\n} @else if (Job) {\n <div class=\"summary-card\">\n <div class=\"summary-header\">\n <div class=\"summary-name\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n {{ Job.Name }}\n </div>\n <span class=\"status-chip\" [class]=\"StatusClass\">{{ Job.Status }}</span>\n </div>\n\n <div class=\"summary-meta\">\n <div class=\"meta-item\">\n <i class=\"fa-regular fa-clock\"></i>\n <span>{{ Job.CronExpression }}</span>\n </div>\n @if (Job.Timezone && Job.Timezone !== 'UTC') {\n <div class=\"meta-item\">\n <i class=\"fa-solid fa-globe\"></i>\n <span>{{ Job.Timezone }}</span>\n </div>\n }\n </div>\n\n <div class=\"summary-stats\">\n <div class=\"stat\">\n <span class=\"stat-num\">{{ TotalRuns }}</span>\n <span class=\"stat-lbl\">Runs</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num success\">{{ SuccessRuns }}</span>\n <span class=\"stat-lbl\">OK</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num error\">{{ FailedRuns }}</span>\n <span class=\"stat-lbl\">Failed</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-num\">{{ SuccessRate }}</span>\n <span class=\"stat-lbl\">Rate</span>\n </div>\n </div>\n\n @if (ShowEditButton) {\n <div class=\"summary-actions\">\n <button class=\"edit-btn\" (click)=\"OnEditClick()\">\n <i class=\"fa-solid fa-pen-to-square\"></i> Edit Schedule\n </button>\n </div>\n }\n </div>\n} @else {\n <mj-empty-state class=\"summary-empty\" Size=\"compact\"\n Icon=\"fa-regular fa-calendar-xmark\"\n Title=\"No schedule configured\" />\n}\n"]}
|
|
@@ -9,8 +9,8 @@ import { ScheduledJobSlidePanelComponent } from './slide-panel/scheduled-job-sli
|
|
|
9
9
|
import { ScheduledJobDialogComponent } from './dialogs/scheduled-job-dialog.component';
|
|
10
10
|
import * as i0 from "@angular/core";
|
|
11
11
|
export class SchedulingModule {
|
|
12
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
13
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.
|
|
12
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: SchedulingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
13
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.22", ngImport: i0, type: SchedulingModule, declarations: [ScheduledJobEditorComponent,
|
|
14
14
|
ScheduledJobSummaryComponent,
|
|
15
15
|
ScheduledJobSlidePanelComponent,
|
|
16
16
|
ScheduledJobDialogComponent], imports: [CommonModule,
|
|
@@ -21,13 +21,13 @@ export class SchedulingModule {
|
|
|
21
21
|
ScheduledJobSummaryComponent,
|
|
22
22
|
ScheduledJobSlidePanelComponent,
|
|
23
23
|
ScheduledJobDialogComponent] });
|
|
24
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.
|
|
24
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: SchedulingModule, imports: [CommonModule,
|
|
25
25
|
FormsModule,
|
|
26
26
|
SharedGenericModule,
|
|
27
27
|
MJDialogComponent,
|
|
28
28
|
MJEmptyStateComponent] });
|
|
29
29
|
}
|
|
30
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
30
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: SchedulingModule, decorators: [{
|
|
31
31
|
type: NgModule,
|
|
32
32
|
args: [{
|
|
33
33
|
declarations: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduling.module.js","sourceRoot":"","sources":["../../src/lib/scheduling.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAE5F,OAAO,EAAE,2BAA2B,EAAE,MAAM,8DAA8D,CAAC;AAC3G,OAAO,EAAE,4BAA4B,EAAE,MAAM,gEAAgE,CAAC;AAC9G,OAAO,EAAE,+BAA+B,EAAE,MAAM,mDAAmD,CAAC;AACpG,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;;AAuBvF,MAAM,OAAO,gBAAgB;
|
|
1
|
+
{"version":3,"file":"scheduling.module.js","sourceRoot":"","sources":["../../src/lib/scheduling.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAE5F,OAAO,EAAE,2BAA2B,EAAE,MAAM,8DAA8D,CAAC;AAC3G,OAAO,EAAE,4BAA4B,EAAE,MAAM,gEAAgE,CAAC;AAC9G,OAAO,EAAE,+BAA+B,EAAE,MAAM,mDAAmD,CAAC;AACpG,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;;AAuBvF,MAAM,OAAO,gBAAgB;wGAAhB,gBAAgB;yGAAhB,gBAAgB,iBAnBrB,2BAA2B;YAC3B,4BAA4B;YAC5B,+BAA+B;YAC/B,2BAA2B,aAG3B,YAAY;YACZ,WAAW;YACX,mBAAmB;YACnB,iBAAiB;YACjB,qBAAqB,aAGrB,2BAA2B;YAC3B,4BAA4B;YAC5B,+BAA+B;YAC/B,2BAA2B;yGAGtB,gBAAgB,YAbrB,YAAY;YACZ,WAAW;YACX,mBAAmB;YACnB,iBAAiB;YACjB,qBAAqB;;4FAShB,gBAAgB;kBArB5B,QAAQ;mBAAC;oBACN,YAAY,EAAE;wBACV,2BAA2B;wBAC3B,4BAA4B;wBAC5B,+BAA+B;wBAC/B,2BAA2B;qBAC9B;oBACD,OAAO,EAAE;wBACL,YAAY;wBACZ,WAAW;wBACX,mBAAmB;wBACnB,iBAAiB;wBACjB,qBAAqB;qBACxB;oBACD,OAAO,EAAE;wBACL,2BAA2B;wBAC3B,4BAA4B;wBAC5B,+BAA+B;wBAC/B,2BAA2B;qBAC9B;iBACJ","sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { SharedGenericModule } from '@memberjunction/ng-shared-generic';\nimport { MJDialogComponent, MJEmptyStateComponent } from '@memberjunction/ng-ui-components';\n\nimport { ScheduledJobEditorComponent } from './panels/scheduled-job-editor/scheduled-job-editor.component';\nimport { ScheduledJobSummaryComponent } from './panels/scheduled-job-summary/scheduled-job-summary.component';\nimport { ScheduledJobSlidePanelComponent } from './slide-panel/scheduled-job-slide-panel.component';\nimport { ScheduledJobDialogComponent } from './dialogs/scheduled-job-dialog.component';\n\n@NgModule({\n declarations: [\n ScheduledJobEditorComponent,\n ScheduledJobSummaryComponent,\n ScheduledJobSlidePanelComponent,\n ScheduledJobDialogComponent,\n ],\n imports: [\n CommonModule,\n FormsModule,\n SharedGenericModule,\n MJDialogComponent,\n MJEmptyStateComponent,\n ],\n exports: [\n ScheduledJobEditorComponent,\n ScheduledJobSummaryComponent,\n ScheduledJobSlidePanelComponent,\n ScheduledJobDialogComponent,\n ],\n})\nexport class SchedulingModule {}\n"]}
|
|
@@ -81,10 +81,10 @@ export class ScheduledJobService {
|
|
|
81
81
|
});
|
|
82
82
|
return result.Success ? result.Results : [];
|
|
83
83
|
}
|
|
84
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
85
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.
|
|
84
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
85
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobService, providedIn: 'root' });
|
|
86
86
|
}
|
|
87
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
87
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobService, decorators: [{
|
|
88
88
|
type: Injectable,
|
|
89
89
|
args: [{ providedIn: 'root' }]
|
|
90
90
|
}] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled-job.service.js","sourceRoot":"","sources":["../../../src/lib/services/scheduled-job.service.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"scheduled-job.service.js","sourceRoot":"","sources":["../../../src/lib/services/scheduled-job.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { IMetadataProvider, Metadata, RunView } from '@memberjunction/core';\nimport { MJScheduledJobEntity, MJScheduledJobTypeEntity, MJScheduledJobRunEntity } from '@memberjunction/core-entities';\n\n/**\n * Service for loading and caching scheduled job data.\n * Safe to call multiple times — caches job types after first load.\n *\n * Multi-provider note: callers under a non-default provider should set\n * `service.Provider = component.ProviderToUse` before invoking any methods.\n */\n@Injectable({ providedIn: 'root' })\nexport class ScheduledJobService {\n private _jobTypes: MJScheduledJobTypeEntity[] = [];\n private _jobTypesLoading: Promise<MJScheduledJobTypeEntity[]> | null = null;\n private _provider: IMetadataProvider | null = null;\n\n public get Provider(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n /** Cached list of all scheduled job types */\n public get JobTypes(): MJScheduledJobTypeEntity[] {\n return this._jobTypes;\n }\n\n /**\n * Load and cache all job types. Safe to call multiple times —\n * returns cached data after first successful load.\n */\n public async LoadJobTypes(): Promise<MJScheduledJobTypeEntity[]> {\n if (this._jobTypes.length > 0) {\n return this._jobTypes;\n }\n if (this._jobTypesLoading) {\n return this._jobTypesLoading;\n }\n\n this._jobTypesLoading = this.fetchJobTypes();\n try {\n this._jobTypes = await this._jobTypesLoading;\n return this._jobTypes;\n } finally {\n this._jobTypesLoading = null;\n }\n }\n\n /**\n * Load a single scheduled job by ID.\n * Returns null if not found or load fails.\n */\n public async LoadJob(jobID: string): Promise<MJScheduledJobEntity | null> {\n const md = this.Provider;\n const job = await md.GetEntityObject<MJScheduledJobEntity>('MJ: Scheduled Jobs', md.CurrentUser);\n const loaded = await job.Load(jobID);\n return loaded ? job : null;\n }\n\n /**\n * Load recent runs for a given job, ordered by most recent first.\n */\n public async LoadJobRuns(jobID: string, maxRows: number = 10): Promise<MJScheduledJobRunEntity[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const result = await rv.RunView<MJScheduledJobRunEntity>({\n EntityName: 'MJ: Scheduled Job Runs',\n ExtraFilter: `ScheduledJobID='${jobID}'`,\n OrderBy: 'StartedAt DESC',\n MaxRows: maxRows,\n ResultType: 'entity_object',\n });\n return result.Success ? result.Results : [];\n }\n\n /** Clear cached data so next LoadJobTypes call re-fetches */\n public ClearCache(): void {\n this._jobTypes = [];\n this._jobTypesLoading = null;\n }\n\n private async fetchJobTypes(): Promise<MJScheduledJobTypeEntity[]> {\n const rv = RunView.FromMetadataProvider(this.Provider);\n const result = await rv.RunView<MJScheduledJobTypeEntity>({\n EntityName: 'MJ: Scheduled Job Types',\n ExtraFilter: '',\n OrderBy: 'Name',\n ResultType: 'entity_object',\n });\n return result.Success ? result.Results : [];\n }\n}\n"]}
|
|
@@ -27,10 +27,10 @@ export class ScheduledJobSlidePanelComponent {
|
|
|
27
27
|
get PanelTitle() {
|
|
28
28
|
return this.ScheduledJobID ? 'Edit Schedule' : 'Create Schedule';
|
|
29
29
|
}
|
|
30
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
31
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
30
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobSlidePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
31
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: ScheduledJobSlidePanelComponent, isStandalone: false, selector: "mj-scheduled-job-slide-panel", inputs: { IsOpen: "IsOpen", ScheduledJobID: "ScheduledJobID", JobTypeID: "JobTypeID", DefaultConfiguration: "DefaultConfiguration", HideJobType: "HideJobType" }, outputs: { Close: "Close", Saved: "Saved", Deleted: "Deleted" }, host: { listeners: { "document:keydown.escape": "OnEscKey()" } }, ngImport: i0, template: "@if (IsOpen) {\n <div class=\"slide-panel-backdrop\" (click)=\"ClosePanel()\"></div>\n <div class=\"slide-panel\" [class.open]=\"IsOpen\">\n <div class=\"slide-panel-header\">\n <div class=\"header-title\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n <span>{{ PanelTitle }}</span>\n </div>\n <button class=\"close-btn\" (click)=\"ClosePanel()\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"slide-panel-body\">\n <mj-scheduled-job-editor\n [ScheduledJobID]=\"ScheduledJobID\"\n [JobTypeID]=\"JobTypeID\"\n [DefaultConfiguration]=\"DefaultConfiguration\"\n [HideJobType]=\"HideJobType\"\n (Saved)=\"OnSaved($event)\"\n (Deleted)=\"OnDeleted($event)\"\n (Cancelled)=\"ClosePanel()\">\n </mj-scheduled-job-editor>\n </div>\n </div>\n}\n", styles: [".slide-panel-backdrop {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 1000;\n animation: fadeIn 0.2s ease;\n}\n\n.slide-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 520px;\n max-width: 90vw;\n background: var(--mj-bg-surface-card);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n box-shadow: -4px 0 20px color-mix(in srgb, var(--mj-text-primary) 12%, transparent);\n transform: translateX(100%);\n transition: transform 0.25s ease;\n}\n\n.slide-panel.open {\n transform: translateX(0);\n}\n\n.slide-panel-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 20px;\n background: linear-gradient(135deg, var(--mj-brand-secondary) 0%, var(--mj-brand-primary) 100%);\n color: var(--mj-text-inverse);\n flex-shrink: 0;\n}\n\n.header-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 16px;\n font-weight: 600;\n}\n\n.close-btn {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.8);\n font-size: 18px;\n cursor: pointer;\n padding: 4px 8px;\n border-radius: 4px;\n transition: color 0.15s, background 0.15s;\n}\n\n.close-btn:hover {\n color: var(--mj-text-inverse);\n background: rgba(255, 255, 255, 0.15);\n}\n\n.slide-panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 0;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@media (max-width: 600px) {\n .slide-panel {\n width: 100vw;\n }\n}\n"], dependencies: [{ kind: "component", type: i1.ScheduledJobEditorComponent, selector: "mj-scheduled-job-editor", inputs: ["ScheduledJobID", "JobTypeID", "DefaultConfiguration", "HideJobType"], outputs: ["Saved", "Deleted", "Cancelled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
32
32
|
}
|
|
33
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
33
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ScheduledJobSlidePanelComponent, decorators: [{
|
|
34
34
|
type: Component,
|
|
35
35
|
args: [{ selector: 'mj-scheduled-job-slide-panel', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (IsOpen) {\n <div class=\"slide-panel-backdrop\" (click)=\"ClosePanel()\"></div>\n <div class=\"slide-panel\" [class.open]=\"IsOpen\">\n <div class=\"slide-panel-header\">\n <div class=\"header-title\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n <span>{{ PanelTitle }}</span>\n </div>\n <button class=\"close-btn\" (click)=\"ClosePanel()\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"slide-panel-body\">\n <mj-scheduled-job-editor\n [ScheduledJobID]=\"ScheduledJobID\"\n [JobTypeID]=\"JobTypeID\"\n [DefaultConfiguration]=\"DefaultConfiguration\"\n [HideJobType]=\"HideJobType\"\n (Saved)=\"OnSaved($event)\"\n (Deleted)=\"OnDeleted($event)\"\n (Cancelled)=\"ClosePanel()\">\n </mj-scheduled-job-editor>\n </div>\n </div>\n}\n", styles: [".slide-panel-backdrop {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 1000;\n animation: fadeIn 0.2s ease;\n}\n\n.slide-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 520px;\n max-width: 90vw;\n background: var(--mj-bg-surface-card);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n box-shadow: -4px 0 20px color-mix(in srgb, var(--mj-text-primary) 12%, transparent);\n transform: translateX(100%);\n transition: transform 0.25s ease;\n}\n\n.slide-panel.open {\n transform: translateX(0);\n}\n\n.slide-panel-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 20px;\n background: linear-gradient(135deg, var(--mj-brand-secondary) 0%, var(--mj-brand-primary) 100%);\n color: var(--mj-text-inverse);\n flex-shrink: 0;\n}\n\n.header-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 16px;\n font-weight: 600;\n}\n\n.close-btn {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.8);\n font-size: 18px;\n cursor: pointer;\n padding: 4px 8px;\n border-radius: 4px;\n transition: color 0.15s, background 0.15s;\n}\n\n.close-btn:hover {\n color: var(--mj-text-inverse);\n background: rgba(255, 255, 255, 0.15);\n}\n\n.slide-panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 0;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@media (max-width: 600px) {\n .slide-panel {\n width: 100vw;\n }\n}\n"] }]
|
|
36
36
|
}], propDecorators: { IsOpen: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled-job-slide-panel.component.js","sourceRoot":"","sources":["../../../src/lib/slide-panel/scheduled-job-slide-panel.component.ts","../../../src/lib/slide-panel/scheduled-job-slide-panel.component.html"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,YAAY,EACZ,uBAAuB,EACvB,YAAY,GACf,MAAM,eAAe,CAAC;;;AAUvB,MAAM,OAAO,+BAA+B;IAC/B,MAAM,GAAG,KAAK,CAAC;IACf,cAAc,GAAkB,IAAI,CAAC;IACrC,SAAS,GAAkB,IAAI,CAAC;IAChC,oBAAoB,GAAkB,IAAI,CAAC;IAC3C,WAAW,GAAG,KAAK,CAAC;IAEnB,KAAK,GAAG,IAAI,YAAY,EAAQ,CAAC;IACjC,KAAK,GAAG,IAAI,YAAY,EAAwB,CAAC;IACjD,OAAO,GAAG,IAAI,YAAY,EAAU,CAAC;IAGxC,QAAQ;QACX,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,UAAU,EAAE,CAAC;QACtB,CAAC;IACL,CAAC;IAEM,UAAU;QACb,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IAEM,OAAO,CAAC,GAAyB;QACpC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAEM,SAAS,CAAC,KAAa;QAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,IAAW,UAAU;QACjB,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACrE,CAAC;
|
|
1
|
+
{"version":3,"file":"scheduled-job-slide-panel.component.js","sourceRoot":"","sources":["../../../src/lib/slide-panel/scheduled-job-slide-panel.component.ts","../../../src/lib/slide-panel/scheduled-job-slide-panel.component.html"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,YAAY,EACZ,uBAAuB,EACvB,YAAY,GACf,MAAM,eAAe,CAAC;;;AAUvB,MAAM,OAAO,+BAA+B;IAC/B,MAAM,GAAG,KAAK,CAAC;IACf,cAAc,GAAkB,IAAI,CAAC;IACrC,SAAS,GAAkB,IAAI,CAAC;IAChC,oBAAoB,GAAkB,IAAI,CAAC;IAC3C,WAAW,GAAG,KAAK,CAAC;IAEnB,KAAK,GAAG,IAAI,YAAY,EAAQ,CAAC;IACjC,KAAK,GAAG,IAAI,YAAY,EAAwB,CAAC;IACjD,OAAO,GAAG,IAAI,YAAY,EAAU,CAAC;IAGxC,QAAQ;QACX,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,UAAU,EAAE,CAAC;QACtB,CAAC;IACL,CAAC;IAEM,UAAU;QACb,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IAEM,OAAO,CAAC,GAAyB;QACpC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAEM,SAAS,CAAC,KAAa;QAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,IAAW,UAAU;QACjB,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACrE,CAAC;wGAhCQ,+BAA+B;4FAA/B,+BAA+B,8XCjB5C,8/BAyBA;;4FDRa,+BAA+B;kBAP3C,SAAS;+BACI,8BAA8B,cAC5B,KAAK,mBAGA,uBAAuB,CAAC,MAAM;;sBAG9C,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBAEL,MAAM;;sBACN,MAAM;;sBACN,MAAM;;sBAEN,YAAY;uBAAC,yBAAyB","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n ChangeDetectionStrategy,\n HostListener,\n} from '@angular/core';\nimport { MJScheduledJobEntity } from '@memberjunction/core-entities';\n\n@Component({\n selector: 'mj-scheduled-job-slide-panel',\n standalone: false,\n templateUrl: './scheduled-job-slide-panel.component.html',\n styleUrls: ['./scheduled-job-slide-panel.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ScheduledJobSlidePanelComponent {\n @Input() IsOpen = false;\n @Input() ScheduledJobID: string | null = null;\n @Input() JobTypeID: string | null = null;\n @Input() DefaultConfiguration: string | null = null;\n @Input() HideJobType = false;\n\n @Output() Close = new EventEmitter<void>();\n @Output() Saved = new EventEmitter<MJScheduledJobEntity>();\n @Output() Deleted = new EventEmitter<string>();\n\n @HostListener('document:keydown.escape')\n public OnEscKey(): void {\n if (this.IsOpen) {\n this.ClosePanel();\n }\n }\n\n public ClosePanel(): void {\n this.Close.emit();\n }\n\n public OnSaved(job: MJScheduledJobEntity): void {\n this.Saved.emit(job);\n }\n\n public OnDeleted(jobID: string): void {\n this.Deleted.emit(jobID);\n }\n\n public get PanelTitle(): string {\n return this.ScheduledJobID ? 'Edit Schedule' : 'Create Schedule';\n }\n}\n","@if (IsOpen) {\n <div class=\"slide-panel-backdrop\" (click)=\"ClosePanel()\"></div>\n <div class=\"slide-panel\" [class.open]=\"IsOpen\">\n <div class=\"slide-panel-header\">\n <div class=\"header-title\">\n <i class=\"fa-solid fa-calendar-check\"></i>\n <span>{{ PanelTitle }}</span>\n </div>\n <button class=\"close-btn\" (click)=\"ClosePanel()\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"slide-panel-body\">\n <mj-scheduled-job-editor\n [ScheduledJobID]=\"ScheduledJobID\"\n [JobTypeID]=\"JobTypeID\"\n [DefaultConfiguration]=\"DefaultConfiguration\"\n [HideJobType]=\"HideJobType\"\n (Saved)=\"OnSaved($event)\"\n (Deleted)=\"OnDeleted($event)\"\n (Cancelled)=\"ClosePanel()\">\n </mj-scheduled-job-editor>\n </div>\n </div>\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-scheduling",
|
|
3
|
-
"version": "6.1.0-edge.
|
|
3
|
+
"version": "6.1.0-edge.7",
|
|
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",
|
|
@@ -16,24 +16,24 @@
|
|
|
16
16
|
"author": "",
|
|
17
17
|
"license": "BUSL-1.1",
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@angular/compiler": "21.
|
|
20
|
-
"@angular/compiler-cli": "21.
|
|
21
|
-
"@memberjunction/ng-test-utils": "6.1.0-edge.
|
|
19
|
+
"@angular/compiler": "21.2.22",
|
|
20
|
+
"@angular/compiler-cli": "21.2.22",
|
|
21
|
+
"@memberjunction/ng-test-utils": "6.1.0-edge.7",
|
|
22
22
|
"vitest": "^4.0.18"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@angular/common": "^21.
|
|
26
|
-
"@angular/core": "^21.
|
|
27
|
-
"@angular/forms": "^21.
|
|
25
|
+
"@angular/common": "^21.2.22",
|
|
26
|
+
"@angular/core": "^21.2.22",
|
|
27
|
+
"@angular/forms": "^21.2.22"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@memberjunction/core": "6.1.0-edge.
|
|
31
|
-
"@memberjunction/ng-base-types": "6.1.0-edge.
|
|
32
|
-
"@memberjunction/core-entities": "6.1.0-edge.
|
|
33
|
-
"@memberjunction/global": "6.1.0-edge.
|
|
34
|
-
"@memberjunction/ng-notifications": "6.1.0-edge.
|
|
35
|
-
"@memberjunction/ng-shared-generic": "6.1.0-edge.
|
|
36
|
-
"@memberjunction/ng-ui-components": "6.1.0-edge.
|
|
30
|
+
"@memberjunction/core": "6.1.0-edge.7",
|
|
31
|
+
"@memberjunction/ng-base-types": "6.1.0-edge.7",
|
|
32
|
+
"@memberjunction/core-entities": "6.1.0-edge.7",
|
|
33
|
+
"@memberjunction/global": "6.1.0-edge.7",
|
|
34
|
+
"@memberjunction/ng-notifications": "6.1.0-edge.7",
|
|
35
|
+
"@memberjunction/ng-shared-generic": "6.1.0-edge.7",
|
|
36
|
+
"@memberjunction/ng-ui-components": "6.1.0-edge.7",
|
|
37
37
|
"tslib": "^2.8.1",
|
|
38
38
|
"rxjs": "^7.8.2"
|
|
39
39
|
},
|