web-mojo 2.2.11 → 2.2.14
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/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +473 -491
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +4 -4
- package/dist/chunks/{ChatView-BxIeRwBQ.js → ChatView-DGulpthL.js} +2 -2
- package/dist/chunks/{ChatView-BxIeRwBQ.js.map → ChatView-DGulpthL.js.map} +1 -1
- package/dist/chunks/{ChatView-DIzLD6LF.js → ChatView-eFzjsHBL.js} +9 -9
- package/dist/chunks/{ChatView-DIzLD6LF.js.map → ChatView-eFzjsHBL.js.map} +1 -1
- package/dist/chunks/{Collection-BgX6OcUC.js → Collection-BlP54kxB.js} +2 -2
- package/dist/chunks/{Collection-BgX6OcUC.js.map → Collection-BlP54kxB.js.map} +1 -1
- package/dist/chunks/{Collection-BcCcol8p.js → Collection-CTkDG1NZ.js} +2 -2
- package/dist/chunks/{Collection-BcCcol8p.js.map → Collection-CTkDG1NZ.js.map} +1 -1
- package/dist/chunks/{ContextMenu-C4_MWleT.js → ContextMenu-BH5SaDXX.js} +2 -2
- package/dist/chunks/{ContextMenu-C4_MWleT.js.map → ContextMenu-BH5SaDXX.js.map} +1 -1
- package/dist/chunks/{ContextMenu-DYLw4o0S.js → ContextMenu-Capwv7d-.js} +3 -3
- package/dist/chunks/{ContextMenu-DYLw4o0S.js.map → ContextMenu-Capwv7d-.js.map} +1 -1
- package/dist/chunks/{DataView-i7kpJjVQ.js → DataView-BpXdthN2.js} +2 -2
- package/dist/chunks/{DataView-i7kpJjVQ.js.map → DataView-BpXdthN2.js.map} +1 -1
- package/dist/chunks/{DataView-BYNjIf--.js → DataView-R_LkYBAw.js} +2 -2
- package/dist/chunks/{DataView-BYNjIf--.js.map → DataView-R_LkYBAw.js.map} +1 -1
- package/dist/chunks/{Dialog-DX5h2QA9.js → Dialog--hl_Uh6X.js} +2 -2
- package/dist/chunks/{Dialog-DX5h2QA9.js.map → Dialog--hl_Uh6X.js.map} +1 -1
- package/dist/chunks/{Dialog-Cu_Dx46k.js → Dialog-RzLLLfJD.js} +5 -5
- package/dist/chunks/{Dialog-Cu_Dx46k.js.map → Dialog-RzLLLfJD.js.map} +1 -1
- package/dist/chunks/{FormView-B_90L1RY.js → FormView--WuITh01.js} +2 -2
- package/dist/chunks/{FormView-B_90L1RY.js.map → FormView--WuITh01.js.map} +1 -1
- package/dist/chunks/{FormView-Bwofbd8S.js → FormView-C1emfj3B.js} +2 -2
- package/dist/chunks/{FormView-Bwofbd8S.js.map → FormView-C1emfj3B.js.map} +1 -1
- package/dist/chunks/{ListView-PvjChHYI.js → ListView-CNkYumcc.js} +5 -5
- package/dist/chunks/ListView-CNkYumcc.js.map +1 -0
- package/dist/chunks/{ListView-OWwlcGGg.js → ListView-O9AO02Rf.js} +2 -2
- package/dist/chunks/ListView-O9AO02Rf.js.map +1 -0
- package/dist/chunks/{MetricsMiniChartWidget-vXr5pxpm.js → MetricsMiniChartWidget-DoxqoF1X.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-vXr5pxpm.js.map → MetricsMiniChartWidget-DoxqoF1X.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-DL5stA6A.js → MetricsMiniChartWidget-DyVs4Wt0.js} +4 -4
- package/dist/chunks/{MetricsMiniChartWidget-DL5stA6A.js.map → MetricsMiniChartWidget-DyVs4Wt0.js.map} +1 -1
- package/dist/chunks/{PDFViewer-DSmi78S6.js → PDFViewer-BxFcG82d.js} +2 -2
- package/dist/chunks/{PDFViewer-DSmi78S6.js.map → PDFViewer-BxFcG82d.js.map} +1 -1
- package/dist/chunks/{PDFViewer--jlqnuVw.js → PDFViewer-CHX2NLkG.js} +3 -3
- package/dist/chunks/{PDFViewer--jlqnuVw.js.map → PDFViewer-CHX2NLkG.js.map} +1 -1
- package/dist/chunks/{Rest-ChN4Ntac.js → Rest-0oRgqNjX.js} +16 -1
- package/dist/chunks/Rest-0oRgqNjX.js.map +1 -0
- package/dist/chunks/Rest-P-KCJpjB.js +2 -0
- package/dist/chunks/Rest-P-KCJpjB.js.map +1 -0
- package/dist/chunks/{TokenManager-DC1jXSK4.js → TokenManager-CBXqj6Iw.js} +5 -5
- package/dist/chunks/{TokenManager-DC1jXSK4.js.map → TokenManager-CBXqj6Iw.js.map} +1 -1
- package/dist/chunks/{TokenManager-CEOPgnsw.js → TokenManager-CCfcK4aA.js} +2 -2
- package/dist/chunks/{TokenManager-CEOPgnsw.js.map → TokenManager-CCfcK4aA.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-JHjYcYbU.js → WebSocketClient-BbPsISrp.js} +2 -2
- package/dist/chunks/{WebSocketClient-JHjYcYbU.js.map → WebSocketClient-BbPsISrp.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-bLYhu2Wv.js → WebSocketClient-D53hpvM8.js} +2 -2
- package/dist/chunks/{WebSocketClient-bLYhu2Wv.js.map → WebSocketClient-D53hpvM8.js.map} +1 -1
- package/dist/chunks/{version-CM7fRavU.js → version-DCTYSNWj.js} +4 -4
- package/dist/chunks/{version-CM7fRavU.js.map → version-DCTYSNWj.js.map} +1 -1
- package/dist/chunks/{version-CAOxcUyu.js → version-DnlcM3tJ.js} +2 -2
- package/dist/chunks/{version-CAOxcUyu.js.map → version-DnlcM3tJ.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +6 -6
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +15 -15
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +5 -5
- package/dist/map.cjs.js +1 -1
- package/dist/map.es.js +2 -2
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.es.js +4 -4
- package/package.json +1 -1
- package/dist/chunks/ListView-OWwlcGGg.js.map +0 -1
- package/dist/chunks/ListView-PvjChHYI.js.map +0 -1
- package/dist/chunks/Rest-ChN4Ntac.js.map +0 -1
- package/dist/chunks/Rest-DhD-U1vp.js +0 -2
- package/dist/chunks/Rest-DhD-U1vp.js.map +0 -1
package/dist/admin.es.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { P as Page, U as User, e as UserDataView, g as UserDeviceList, i as UserDeviceLocationList, C as ContextMenu, d as UserForms, c as UserList, a as Group, G as GroupList, b as GroupForms, f as UserDevice } from "./chunks/ContextMenu-
|
|
2
|
-
import { V as View, M as MOJOUtils, r as rest } from "./chunks/Rest-
|
|
3
|
-
import "./chunks/WebSocketClient-
|
|
4
|
-
import { D as Dialog$1 } from "./chunks/Dialog-
|
|
5
|
-
import { W } from "./chunks/Dialog-
|
|
6
|
-
import { M as MetricsChart, c as MetricsMiniChartWidget, P as PieChart } from "./chunks/MetricsMiniChartWidget-
|
|
7
|
-
import { aj as MemberList, T as TableView, B as IncidentEventList, ap as PushDeviceList, ai as LogList, c as TabView, b as TablePage, M as Member, ak as MemberForms, ay as GeoLocatedIP, az as GeoLocatedIPList, aB as TicketList, J as IncidentList, a0 as IncidentStats, V as IncidentHistoryList, U as IncidentHistory, H as Incident, C as ChatView, K as IncidentForms, I as IncidentEvent, G as IncidentEventForms, aD as TicketNoteList, aC as TicketNote, aA as Ticket, aE as TicketForms, aF as TicketCategories, W as RuleSet, a2 as MatchByOptions, a1 as BundleByOptions, _ as RuleList, X as RuleSetList, k as EmailDomainForms, j as EmailDomainList, E as EmailDomain, n as MailboxForms, m as MailboxList, l as Mailbox, s as EmailTemplate, u as EmailTemplateForms, t as EmailTemplateList, o as SentMessage, q as SentMessageList, av as PushDeliveryList, aw as PushConfigForms, at as PushConfigList, ax as PushTemplateForms, ar as PushTemplateList, a6 as Job, ac as JobEventList, aa as JobLogList, a8 as JobForms,
|
|
8
|
-
import DataView from "./chunks/DataView-
|
|
9
|
-
import { F as FormView, a as applyFileDropMixin } from "./chunks/FormView
|
|
1
|
+
import { P as Page, U as User, e as UserDataView, g as UserDeviceList, i as UserDeviceLocationList, C as ContextMenu, d as UserForms, c as UserList, a as Group, G as GroupList, b as GroupForms, f as UserDevice } from "./chunks/ContextMenu-Capwv7d-.js";
|
|
2
|
+
import { V as View, M as MOJOUtils, r as rest } from "./chunks/Rest-0oRgqNjX.js";
|
|
3
|
+
import "./chunks/WebSocketClient-D53hpvM8.js";
|
|
4
|
+
import { D as Dialog$1 } from "./chunks/Dialog-RzLLLfJD.js";
|
|
5
|
+
import { W } from "./chunks/Dialog-RzLLLfJD.js";
|
|
6
|
+
import { M as MetricsChart, c as MetricsMiniChartWidget, P as PieChart } from "./chunks/MetricsMiniChartWidget-DyVs4Wt0.js";
|
|
7
|
+
import { aj as MemberList, T as TableView, B as IncidentEventList, ap as PushDeviceList, ai as LogList, c as TabView, b as TablePage, M as Member, ak as MemberForms, ay as GeoLocatedIP, az as GeoLocatedIPList, aB as TicketList, J as IncidentList, a0 as IncidentStats, V as IncidentHistoryList, U as IncidentHistory, H as Incident, C as ChatView, K as IncidentForms, I as IncidentEvent, G as IncidentEventForms, aD as TicketNoteList, aC as TicketNote, aA as Ticket, aE as TicketForms, aF as TicketCategories, W as RuleSet, a2 as MatchByOptions, a1 as BundleByOptions, _ as RuleList, X as RuleSetList, k as EmailDomainForms, j as EmailDomainList, E as EmailDomain, n as MailboxForms, m as MailboxList, l as Mailbox, s as EmailTemplate, u as EmailTemplateForms, t as EmailTemplateList, o as SentMessage, q as SentMessageList, av as PushDeliveryList, aw as PushConfigForms, at as PushConfigList, ax as PushTemplateForms, ar as PushTemplateList, a6 as Job, ac as JobEventList, aa as JobLogList, a8 as JobForms, ad as JobsEngineStats, a7 as JobList, af as JobRunnerList, ag as JobRunnerForms, ae as JobRunner, ah as Log, al as MetricsPermission, an as MetricsForms, am as MetricsPermissionList, x as FileManagerForms, w as FileManagerList, y as File, A as FileForms, z as FileList, i as S3BucketForms, h as S3BucketList } from "./chunks/ChatView-eFzjsHBL.js";
|
|
8
|
+
import DataView from "./chunks/DataView-BpXdthN2.js";
|
|
9
|
+
import { F as FormView, a as applyFileDropMixin } from "./chunks/FormView--WuITh01.js";
|
|
10
10
|
import { MapView } from "./map.es.js";
|
|
11
|
-
import { M as Model, C as Collection } from "./chunks/Collection-
|
|
12
|
-
import { L as LightboxGallery, P as PDFViewer } from "./chunks/PDFViewer
|
|
13
|
-
import { B, a, V, b, c, d } from "./chunks/version-
|
|
11
|
+
import { M as Model, C as Collection } from "./chunks/Collection-CTkDG1NZ.js";
|
|
12
|
+
import { L as LightboxGallery, P as PDFViewer } from "./chunks/PDFViewer-CHX2NLkG.js";
|
|
13
|
+
import { B, a, V, b, c, d } from "./chunks/version-DCTYSNWj.js";
|
|
14
14
|
class AdminHeaderView extends View {
|
|
15
15
|
constructor(options = {}) {
|
|
16
16
|
super({
|
|
@@ -3316,7 +3316,7 @@ class IncidentDashboardPage extends Page {
|
|
|
3316
3316
|
title: '<i class="bi bi-geo-alt me-2"></i> Incidents by Country',
|
|
3317
3317
|
endpoint: "/api/metrics/fetch",
|
|
3318
3318
|
account: "incident",
|
|
3319
|
-
category: "
|
|
3319
|
+
category: "incidents_by_country",
|
|
3320
3320
|
granularity: "days",
|
|
3321
3321
|
chartType: "line",
|
|
3322
3322
|
showDateRange: false,
|
|
@@ -6098,7 +6098,7 @@ class JobHealthView extends View {
|
|
|
6098
6098
|
<i class="bi bi-circle-fill fs-4 {{healthStatusClass}}"></i>
|
|
6099
6099
|
</div>
|
|
6100
6100
|
<div>
|
|
6101
|
-
<h5 class="mb-1">
|
|
6101
|
+
<h5 class="mb-1">Service Health: {{health.overall_status|capitalize}}</h5>
|
|
6102
6102
|
<small class="text-muted d-block">
|
|
6103
6103
|
Workers: {{health.runners.active}}/{{health.runners.total}} active
|
|
6104
6104
|
</small>
|
|
@@ -6574,444 +6574,36 @@ class JobDetailsView extends View {
|
|
|
6574
6574
|
}
|
|
6575
6575
|
}
|
|
6576
6576
|
Job.VIEW_CLASS = JobDetailsView;
|
|
6577
|
-
class
|
|
6577
|
+
class JobMetricsModalView extends View {
|
|
6578
6578
|
constructor(options = {}) {
|
|
6579
6579
|
super({
|
|
6580
|
-
|
|
6581
|
-
collectionParams: {
|
|
6582
|
-
size: 15,
|
|
6583
|
-
sort: "-created"
|
|
6584
|
-
},
|
|
6585
|
-
options: {
|
|
6586
|
-
searchable: true,
|
|
6587
|
-
sortable: true,
|
|
6588
|
-
paginated: true,
|
|
6589
|
-
size: 15,
|
|
6590
|
-
...options.options
|
|
6591
|
-
},
|
|
6592
|
-
columns: [
|
|
6593
|
-
{
|
|
6594
|
-
key: "id",
|
|
6595
|
-
label: "Job ID",
|
|
6596
|
-
formatter: "truncate_middle(12)",
|
|
6597
|
-
sortable: true,
|
|
6598
|
-
filter: { type: "text", placeholder: "Job ID..." }
|
|
6599
|
-
},
|
|
6600
|
-
{
|
|
6601
|
-
key: "status",
|
|
6602
|
-
label: "Status",
|
|
6603
|
-
formatter: (value, context) => {
|
|
6604
|
-
const job = context.row;
|
|
6605
|
-
const badgeClass = job.getStatusBadgeClass ? job.getStatusBadgeClass() : "bg-secondary";
|
|
6606
|
-
const icon = job.getStatusIcon ? job.getStatusIcon() : "bi-question";
|
|
6607
|
-
return `<span class="badge ${badgeClass}"><i class="${icon} me-1"></i>${value.toUpperCase()}</span>`;
|
|
6608
|
-
},
|
|
6609
|
-
sortable: true,
|
|
6610
|
-
filter: {
|
|
6611
|
-
type: "select",
|
|
6612
|
-
options: [
|
|
6613
|
-
{ value: "pending", label: "Pending" },
|
|
6614
|
-
{ value: "running", label: "Running" },
|
|
6615
|
-
{ value: "completed", label: "Completed" },
|
|
6616
|
-
{ value: "failed", label: "Failed" },
|
|
6617
|
-
{ value: "canceled", label: "Canceled" },
|
|
6618
|
-
{ value: "expired", label: "Expired" }
|
|
6619
|
-
]
|
|
6620
|
-
}
|
|
6621
|
-
},
|
|
6622
|
-
{
|
|
6623
|
-
key: "channel",
|
|
6624
|
-
label: "Channel",
|
|
6625
|
-
formatter: "badge",
|
|
6626
|
-
sortable: true,
|
|
6627
|
-
filter: { type: "text", placeholder: "Channel..." }
|
|
6628
|
-
},
|
|
6629
|
-
{
|
|
6630
|
-
key: "created",
|
|
6631
|
-
label: "Created",
|
|
6632
|
-
formatter: "datetime",
|
|
6633
|
-
sortable: true,
|
|
6634
|
-
filter: {
|
|
6635
|
-
type: "daterange",
|
|
6636
|
-
label: "Created Date"
|
|
6637
|
-
}
|
|
6638
|
-
},
|
|
6639
|
-
{
|
|
6640
|
-
key: "started_at",
|
|
6641
|
-
label: "Started",
|
|
6642
|
-
formatter: "datetime",
|
|
6643
|
-
sortable: true
|
|
6644
|
-
},
|
|
6645
|
-
{
|
|
6646
|
-
key: "finished_at",
|
|
6647
|
-
label: "Finished",
|
|
6648
|
-
formatter: "datetime",
|
|
6649
|
-
sortable: true
|
|
6650
|
-
}
|
|
6651
|
-
],
|
|
6652
|
-
contextMenu: [
|
|
6653
|
-
{
|
|
6654
|
-
label: "View Details",
|
|
6655
|
-
action: "view-job-details",
|
|
6656
|
-
icon: "bi-info-circle"
|
|
6657
|
-
},
|
|
6658
|
-
{
|
|
6659
|
-
label: "View Events",
|
|
6660
|
-
action: "view-job-events",
|
|
6661
|
-
icon: "bi-clock-history"
|
|
6662
|
-
},
|
|
6663
|
-
{ separator: true },
|
|
6664
|
-
{
|
|
6665
|
-
label: "Cancel Job",
|
|
6666
|
-
action: "cancel-job",
|
|
6667
|
-
icon: "bi-x-circle",
|
|
6668
|
-
danger: true,
|
|
6669
|
-
condition: (job) => job.canCancel && job.canCancel()
|
|
6670
|
-
},
|
|
6671
|
-
{
|
|
6672
|
-
label: "Retry Job",
|
|
6673
|
-
action: "retry-job",
|
|
6674
|
-
icon: "bi-arrow-clockwise",
|
|
6675
|
-
condition: (job) => job.canRetry && job.canRetry()
|
|
6676
|
-
},
|
|
6677
|
-
{
|
|
6678
|
-
label: "Clone Job",
|
|
6679
|
-
action: "clone-job",
|
|
6680
|
-
icon: "bi-copy"
|
|
6681
|
-
},
|
|
6682
|
-
{ separator: true },
|
|
6683
|
-
{
|
|
6684
|
-
label: "Export Job",
|
|
6685
|
-
action: "export-job",
|
|
6686
|
-
icon: "bi-download"
|
|
6687
|
-
}
|
|
6688
|
-
],
|
|
6689
|
-
filters: [
|
|
6690
|
-
{
|
|
6691
|
-
key: "func",
|
|
6692
|
-
label: "Function",
|
|
6693
|
-
type: "text"
|
|
6694
|
-
}
|
|
6695
|
-
],
|
|
6696
|
-
batchActions: [
|
|
6697
|
-
{
|
|
6698
|
-
label: "Cancel Selected",
|
|
6699
|
-
action: "batch-cancel",
|
|
6700
|
-
icon: "bi-x-circle"
|
|
6701
|
-
},
|
|
6702
|
-
{
|
|
6703
|
-
label: "Retry Selected",
|
|
6704
|
-
action: "batch-retry",
|
|
6705
|
-
icon: "bi-arrow-clockwise"
|
|
6706
|
-
},
|
|
6707
|
-
{
|
|
6708
|
-
label: "Export Selected",
|
|
6709
|
-
action: "batch-export",
|
|
6710
|
-
icon: "bi-download"
|
|
6711
|
-
}
|
|
6712
|
-
],
|
|
6580
|
+
className: "job-metrics-modal-view",
|
|
6713
6581
|
...options
|
|
6714
6582
|
});
|
|
6583
|
+
this.template = `
|
|
6584
|
+
<div data-container="job-metrics-modal-chart" style="min-height:320px;"></div>
|
|
6585
|
+
`;
|
|
6715
6586
|
}
|
|
6716
|
-
async
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
this.getApp().toast.error(result.data?.error || "Failed to cancel job");
|
|
6731
|
-
}
|
|
6732
|
-
} catch (error) {
|
|
6733
|
-
this.getApp().toast.error("Error cancelling job: " + error.message);
|
|
6734
|
-
}
|
|
6735
|
-
}
|
|
6736
|
-
}
|
|
6737
|
-
async onItemRetryJob(job) {
|
|
6738
|
-
if (job) {
|
|
6739
|
-
const result = await Dialog$1.showForm({
|
|
6740
|
-
title: "Retry Job",
|
|
6741
|
-
formConfig: JobForms.retry
|
|
6742
|
-
});
|
|
6743
|
-
if (result) {
|
|
6744
|
-
try {
|
|
6745
|
-
const retryResult = await job.retry(result.delay || 0);
|
|
6746
|
-
if (retryResult.success) {
|
|
6747
|
-
this.getApp().toast.success("Job queued for retry");
|
|
6748
|
-
await this.collection.fetch();
|
|
6749
|
-
} else {
|
|
6750
|
-
this.getApp().toast.error(retryResult.data?.error || "Failed to retry job");
|
|
6751
|
-
}
|
|
6752
|
-
} catch (error) {
|
|
6753
|
-
this.getApp().toast.error("Error retrying job: " + error.message);
|
|
6754
|
-
}
|
|
6755
|
-
}
|
|
6756
|
-
}
|
|
6757
|
-
}
|
|
6758
|
-
async onItemCloneJob(job) {
|
|
6759
|
-
if (job) {
|
|
6760
|
-
const payload = job.getPayload();
|
|
6761
|
-
const result = await Dialog$1.showForm({
|
|
6762
|
-
title: "Clone Job",
|
|
6763
|
-
formConfig: {
|
|
6764
|
-
...JobForms.clone,
|
|
6765
|
-
fields: JobForms.clone.fields.map((field) => {
|
|
6766
|
-
if (field.name === "payload") {
|
|
6767
|
-
field.value = JSON.stringify(payload, null, 2);
|
|
6768
|
-
} else if (field.name === "channel") {
|
|
6769
|
-
field.value = job.get("channel");
|
|
6770
|
-
}
|
|
6771
|
-
return field;
|
|
6772
|
-
})
|
|
6773
|
-
}
|
|
6774
|
-
});
|
|
6775
|
-
if (result) {
|
|
6776
|
-
try {
|
|
6777
|
-
let newPayload = {};
|
|
6778
|
-
if (result.payload) {
|
|
6779
|
-
newPayload = JSON.parse(result.payload);
|
|
6780
|
-
}
|
|
6781
|
-
const cloneData = {
|
|
6782
|
-
payload: newPayload,
|
|
6783
|
-
channel: result.channel || job.get("channel"),
|
|
6784
|
-
delay: result.delay || 0
|
|
6785
|
-
};
|
|
6786
|
-
const cloneResult = await job.cloneJob(cloneData);
|
|
6787
|
-
if (cloneResult.success) {
|
|
6788
|
-
this.getApp().toast.success("Job cloned successfully");
|
|
6789
|
-
await this.collection.fetch();
|
|
6790
|
-
} else {
|
|
6791
|
-
this.getApp().toast.error(cloneResult.data?.error || "Failed to clone job");
|
|
6792
|
-
}
|
|
6793
|
-
} catch (error) {
|
|
6794
|
-
this.getApp().toast.error("Error cloning job: " + error.message);
|
|
6795
|
-
}
|
|
6796
|
-
}
|
|
6797
|
-
}
|
|
6798
|
-
}
|
|
6799
|
-
}
|
|
6800
|
-
class RunnersTable extends TableView {
|
|
6801
|
-
constructor(options = {}) {
|
|
6802
|
-
super({
|
|
6803
|
-
Collection: JobRunnerList,
|
|
6804
|
-
options: {
|
|
6805
|
-
searchable: true,
|
|
6806
|
-
sortable: true,
|
|
6807
|
-
paginated: true,
|
|
6808
|
-
size: 10,
|
|
6809
|
-
...options.options
|
|
6587
|
+
async onInit() {
|
|
6588
|
+
this.chart = new MetricsChart({
|
|
6589
|
+
containerId: "job-metrics-modal-chart",
|
|
6590
|
+
title: '<i class="bi bi-graph-up me-2"></i> Job Channel Metrics',
|
|
6591
|
+
endpoint: "/api/metrics/fetch",
|
|
6592
|
+
height: 320,
|
|
6593
|
+
granularity: "hours",
|
|
6594
|
+
category: "jobs_channels",
|
|
6595
|
+
account: "global",
|
|
6596
|
+
chartType: "bar",
|
|
6597
|
+
showDateRange: true,
|
|
6598
|
+
yAxis: {
|
|
6599
|
+
label: "Count",
|
|
6600
|
+
beginAtZero: true
|
|
6810
6601
|
},
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
key: "runner_id",
|
|
6814
|
-
label: "Runner ID",
|
|
6815
|
-
formatter: "truncate_middle(16)",
|
|
6816
|
-
sortable: true
|
|
6817
|
-
},
|
|
6818
|
-
{
|
|
6819
|
-
key: "alive",
|
|
6820
|
-
label: "Status",
|
|
6821
|
-
formatter: (value) => {
|
|
6822
|
-
const isAlive = value === true;
|
|
6823
|
-
const badgeClass = isAlive ? "bg-success" : "bg-danger";
|
|
6824
|
-
const icon = isAlive ? "bi-check-circle-fill" : "bi-x-octagon-fill";
|
|
6825
|
-
const text = isAlive ? "ALIVE" : "DEAD";
|
|
6826
|
-
return `<span class="badge ${badgeClass}"><i class="${icon} me-1"></i>${text}</span>`;
|
|
6827
|
-
},
|
|
6828
|
-
sortable: true,
|
|
6829
|
-
filter: {
|
|
6830
|
-
type: "select",
|
|
6831
|
-
options: [
|
|
6832
|
-
{ value: true, label: "Alive" },
|
|
6833
|
-
{ value: false, label: "Dead" }
|
|
6834
|
-
]
|
|
6835
|
-
}
|
|
6836
|
-
},
|
|
6837
|
-
{
|
|
6838
|
-
key: "channels",
|
|
6839
|
-
label: "Channels",
|
|
6840
|
-
formatter: (channels) => {
|
|
6841
|
-
if (!channels || !channels.length) return "None";
|
|
6842
|
-
return channels.map((c2) => `<span class="badge bg-secondary me-1">${c2}</span>`).join("");
|
|
6843
|
-
},
|
|
6844
|
-
sortable: false
|
|
6845
|
-
},
|
|
6846
|
-
{
|
|
6847
|
-
key: "jobs_processed",
|
|
6848
|
-
label: "Processed",
|
|
6849
|
-
sortable: true
|
|
6850
|
-
},
|
|
6851
|
-
{
|
|
6852
|
-
key: "jobs_failed",
|
|
6853
|
-
label: "Failed",
|
|
6854
|
-
formatter: (value) => {
|
|
6855
|
-
const badgeClass = value > 0 ? "bg-danger" : "bg-success";
|
|
6856
|
-
return `<span class="badge ${badgeClass}">${value}</span>`;
|
|
6857
|
-
},
|
|
6858
|
-
sortable: true
|
|
6859
|
-
},
|
|
6860
|
-
{
|
|
6861
|
-
key: "last_heartbeat",
|
|
6862
|
-
label: "Last Heartbeat",
|
|
6863
|
-
formatter: (value) => {
|
|
6864
|
-
if (!value) return "Never";
|
|
6865
|
-
const heartbeatTime = new Date(value);
|
|
6866
|
-
const now = /* @__PURE__ */ new Date();
|
|
6867
|
-
const diffMs = now - heartbeatTime;
|
|
6868
|
-
const diffSeconds = Math.floor(diffMs / 1e3);
|
|
6869
|
-
if (diffSeconds < 60) return `${diffSeconds}s ago`;
|
|
6870
|
-
if (diffSeconds < 3600) return `${Math.floor(diffSeconds / 60)}m ago`;
|
|
6871
|
-
return `${Math.floor(diffSeconds / 3600)}h ago`;
|
|
6872
|
-
},
|
|
6873
|
-
sortable: true
|
|
6874
|
-
},
|
|
6875
|
-
{
|
|
6876
|
-
key: "started",
|
|
6877
|
-
label: "Uptime",
|
|
6878
|
-
formatter: (value) => {
|
|
6879
|
-
if (!value) return "Unknown";
|
|
6880
|
-
const startTime = new Date(value);
|
|
6881
|
-
const now = /* @__PURE__ */ new Date();
|
|
6882
|
-
const diffMs = now - startTime;
|
|
6883
|
-
const diffSeconds = Math.floor(diffMs / 1e3);
|
|
6884
|
-
if (diffSeconds < 60) return `${diffSeconds}s`;
|
|
6885
|
-
if (diffSeconds < 3600) return `${Math.floor(diffSeconds / 60)}m`;
|
|
6886
|
-
if (diffSeconds < 86400) return `${Math.floor(diffSeconds / 3600)}h`;
|
|
6887
|
-
return `${Math.floor(diffSeconds / 86400)}d`;
|
|
6888
|
-
},
|
|
6889
|
-
sortable: true
|
|
6890
|
-
}
|
|
6891
|
-
],
|
|
6892
|
-
contextMenu: [
|
|
6893
|
-
{
|
|
6894
|
-
label: "Ping Runner",
|
|
6895
|
-
action: "ping-runner",
|
|
6896
|
-
icon: "bi-wifi"
|
|
6897
|
-
},
|
|
6898
|
-
{
|
|
6899
|
-
label: "View Details",
|
|
6900
|
-
action: "view-runner-details",
|
|
6901
|
-
icon: "bi-info-circle"
|
|
6902
|
-
},
|
|
6903
|
-
{ separator: true },
|
|
6904
|
-
{
|
|
6905
|
-
label: "Pause Runner",
|
|
6906
|
-
action: "pause-runner",
|
|
6907
|
-
icon: "bi-pause-circle",
|
|
6908
|
-
condition: (runner) => runner.get("alive") === true
|
|
6909
|
-
},
|
|
6910
|
-
{
|
|
6911
|
-
label: "Resume Runner",
|
|
6912
|
-
action: "resume-runner",
|
|
6913
|
-
icon: "bi-play-circle",
|
|
6914
|
-
condition: (runner) => runner.get("alive") !== true
|
|
6915
|
-
},
|
|
6916
|
-
{ separator: true },
|
|
6917
|
-
{
|
|
6918
|
-
label: "Shutdown Runner",
|
|
6919
|
-
action: "shutdown-runner",
|
|
6920
|
-
icon: "bi-power",
|
|
6921
|
-
danger: true,
|
|
6922
|
-
condition: (runner) => runner.get("alive") === true
|
|
6923
|
-
}
|
|
6924
|
-
],
|
|
6925
|
-
...options
|
|
6926
|
-
});
|
|
6927
|
-
}
|
|
6928
|
-
async onActionPingRunner(event, element) {
|
|
6929
|
-
const runnerId = element.getAttribute("data-id");
|
|
6930
|
-
const runner = this.collection.get(runnerId);
|
|
6931
|
-
if (runner) {
|
|
6932
|
-
try {
|
|
6933
|
-
const result = await runner.ping();
|
|
6934
|
-
if (result.success) {
|
|
6935
|
-
this.getApp().toast.success("Runner ping successful");
|
|
6936
|
-
await this.collection.fetch();
|
|
6937
|
-
} else {
|
|
6938
|
-
this.getApp().toast.error(result.data?.error || "Runner ping failed");
|
|
6939
|
-
}
|
|
6940
|
-
} catch (error) {
|
|
6941
|
-
this.getApp().toast.error("Error pinging runner: " + error.message);
|
|
6942
|
-
}
|
|
6943
|
-
}
|
|
6944
|
-
}
|
|
6945
|
-
async onActionShutdownRunner(event, element) {
|
|
6946
|
-
const runnerId = element.getAttribute("data-id");
|
|
6947
|
-
const runner = this.collection.get(runnerId);
|
|
6948
|
-
if (runner && confirm("Are you sure you want to shutdown this runner?")) {
|
|
6949
|
-
try {
|
|
6950
|
-
const result = await runner.shutdown(true);
|
|
6951
|
-
if (result.success) {
|
|
6952
|
-
this.getApp().toast.success("Runner shutdown initiated");
|
|
6953
|
-
await this.collection.fetch();
|
|
6954
|
-
} else {
|
|
6955
|
-
this.getApp().toast.error(result.data?.error || "Failed to shutdown runner");
|
|
6956
|
-
}
|
|
6957
|
-
} catch (error) {
|
|
6958
|
-
this.getApp().toast.error("Error shutting down runner: " + error.message);
|
|
6602
|
+
tooltip: {
|
|
6603
|
+
y: "number:0"
|
|
6959
6604
|
}
|
|
6960
|
-
}
|
|
6961
|
-
}
|
|
6962
|
-
}
|
|
6963
|
-
class ScheduledJobsTable extends TableView {
|
|
6964
|
-
constructor(options = {}) {
|
|
6965
|
-
super({
|
|
6966
|
-
Collection: JobList,
|
|
6967
|
-
collectionParams: { status: "pending" },
|
|
6968
|
-
hideActivePillNames: ["status"],
|
|
6969
|
-
options: {
|
|
6970
|
-
searchable: true,
|
|
6971
|
-
sortable: true,
|
|
6972
|
-
paginated: true,
|
|
6973
|
-
size: 10,
|
|
6974
|
-
...options.options
|
|
6975
|
-
},
|
|
6976
|
-
columns: [
|
|
6977
|
-
{
|
|
6978
|
-
key: "id",
|
|
6979
|
-
label: "Job ID",
|
|
6980
|
-
formatter: "truncate_middle(12)",
|
|
6981
|
-
sortable: true
|
|
6982
|
-
},
|
|
6983
|
-
{
|
|
6984
|
-
key: "func",
|
|
6985
|
-
label: "Function",
|
|
6986
|
-
sortable: true
|
|
6987
|
-
},
|
|
6988
|
-
{
|
|
6989
|
-
key: "channel",
|
|
6990
|
-
label: "Channel",
|
|
6991
|
-
formatter: "badge",
|
|
6992
|
-
sortable: true
|
|
6993
|
-
},
|
|
6994
|
-
{
|
|
6995
|
-
key: "run_at",
|
|
6996
|
-
label: "Scheduled For",
|
|
6997
|
-
formatter: "datetime",
|
|
6998
|
-
sortable: true
|
|
6999
|
-
},
|
|
7000
|
-
{
|
|
7001
|
-
key: "created",
|
|
7002
|
-
label: "Created",
|
|
7003
|
-
formatter: "datetime",
|
|
7004
|
-
sortable: true
|
|
7005
|
-
},
|
|
7006
|
-
{
|
|
7007
|
-
key: "expires_at",
|
|
7008
|
-
label: "Expires At",
|
|
7009
|
-
formatter: "datetime",
|
|
7010
|
-
sortable: true
|
|
7011
|
-
}
|
|
7012
|
-
],
|
|
7013
|
-
...options
|
|
7014
6605
|
});
|
|
6606
|
+
this.addChild(this.chart);
|
|
7015
6607
|
}
|
|
7016
6608
|
}
|
|
7017
6609
|
class JobsAdminPage extends Page {
|
|
@@ -7035,7 +6627,7 @@ class JobsAdminPage extends Page {
|
|
|
7035
6627
|
<p class="text-muted mb-0">{{pageSubtitle}}</p>
|
|
7036
6628
|
<small class="text-info">
|
|
7037
6629
|
<i class="bi bi-arrow-clockwise me-1"></i>
|
|
7038
|
-
Auto-refresh: {{
|
|
6630
|
+
Auto-refresh: {{refreshRateLabel}} | Last updated: {{lastUpdated}}
|
|
7039
6631
|
</small>
|
|
7040
6632
|
</div>
|
|
7041
6633
|
<div class="btn-group" role="group">
|
|
@@ -7080,28 +6672,106 @@ class JobsAdminPage extends Page {
|
|
|
7080
6672
|
</div>
|
|
7081
6673
|
</div>
|
|
7082
6674
|
|
|
7083
|
-
<!-- Job Stats -->
|
|
7084
6675
|
<div data-container="job-stats"></div>
|
|
7085
6676
|
|
|
7086
|
-
|
|
6677
|
+
<div class="row mb-4 g-3 align-items-stretch">
|
|
6678
|
+
<div class="col-lg-6" data-container="jobs-published-chart"></div>
|
|
6679
|
+
<div class="col-lg-6 position-relative">
|
|
6680
|
+
<div data-container="jobs-failed-chart"></div>
|
|
6681
|
+
<button class="btn btn-link btn-sm text-decoration-none position-absolute top-0 end-0"
|
|
6682
|
+
data-action="open-job-metrics-modal">
|
|
6683
|
+
<i class="bi bi-graph-up"></i> Channel Metrics
|
|
6684
|
+
</button>
|
|
6685
|
+
</div>
|
|
6686
|
+
</div>
|
|
6687
|
+
|
|
7087
6688
|
<div class="row">
|
|
7088
|
-
<div class="col-
|
|
6689
|
+
<div class="col-xxl-6 col-lg-6 mb-4">
|
|
7089
6690
|
<div data-container="job-health"></div>
|
|
7090
6691
|
</div>
|
|
7091
|
-
<div class="col-
|
|
7092
|
-
<div class="
|
|
6692
|
+
<div class="col-xxl-6 col-lg-6 mb-4">
|
|
6693
|
+
<div class="card shadow-sm h-100">
|
|
6694
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
6695
|
+
<h5 class="mb-0"><i class="bi bi-cpu me-2"></i>Job Runners</h5>
|
|
6696
|
+
<small class="text-muted">Heartbeat & status</small>
|
|
6697
|
+
</div>
|
|
6698
|
+
<div class="card-body p-0" data-container="runner-table"></div>
|
|
6699
|
+
</div>
|
|
6700
|
+
</div>
|
|
6701
|
+
</div>
|
|
6702
|
+
|
|
6703
|
+
<div class="row">
|
|
6704
|
+
<div class="col-xl-6 mb-4">
|
|
6705
|
+
<div class="card shadow-sm h-100">
|
|
6706
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
6707
|
+
<h5 class="mb-0"><i class="bi bi-play-circle me-2"></i>Running Jobs</h5>
|
|
6708
|
+
<small class="text-muted">Currently executing</small>
|
|
6709
|
+
</div>
|
|
6710
|
+
<div class="card-body p-0" data-container="running-jobs-table"></div>
|
|
6711
|
+
</div>
|
|
6712
|
+
</div>
|
|
6713
|
+
<div class="col-xl-6 mb-4">
|
|
6714
|
+
<div class="card shadow-sm h-100">
|
|
6715
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
6716
|
+
<h5 class="mb-0"><i class="bi bi-hourglass-split me-2"></i>Pending Jobs</h5>
|
|
6717
|
+
<small class="text-muted">Waiting in queue</small>
|
|
6718
|
+
</div>
|
|
6719
|
+
<div class="card-body p-0" data-container="pending-jobs-table"></div>
|
|
6720
|
+
</div>
|
|
6721
|
+
</div>
|
|
6722
|
+
</div>
|
|
6723
|
+
|
|
6724
|
+
<div class="row">
|
|
6725
|
+
<div class="col-xl-6 mb-4">
|
|
6726
|
+
<div class="card shadow-sm h-100">
|
|
6727
|
+
<div class="card-header">
|
|
6728
|
+
<h5 class="mb-0"><i class="bi bi-calendar-event me-2"></i>Scheduled Jobs</h5>
|
|
6729
|
+
</div>
|
|
6730
|
+
<div class="card-body p-0" data-container="scheduled-jobs-table"></div>
|
|
6731
|
+
</div>
|
|
6732
|
+
</div>
|
|
6733
|
+
<div class="col-xl-6 mb-4">
|
|
6734
|
+
<div class="card shadow-sm h-100">
|
|
6735
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
6736
|
+
<h5 class="mb-0"><i class="bi bi-bug me-2"></i>Failed Jobs</h5>
|
|
6737
|
+
<small class="text-muted">Latest errors</small>
|
|
6738
|
+
</div>
|
|
6739
|
+
<div class="card-body p-0" data-container="failed-jobs-table"></div>
|
|
6740
|
+
</div>
|
|
7093
6741
|
</div>
|
|
7094
6742
|
</div>
|
|
7095
6743
|
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
<
|
|
7100
|
-
<i class="bi bi-
|
|
7101
|
-
</
|
|
6744
|
+
<div class="card shadow-sm mb-5">
|
|
6745
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
6746
|
+
<h5 class="mb-0"><i class="bi bi-tools me-2"></i>Operations</h5>
|
|
6747
|
+
<button class="btn btn-outline-secondary btn-sm" data-action="view-all-jobs">
|
|
6748
|
+
<i class="bi bi-table"></i> View All Jobs
|
|
6749
|
+
</button>
|
|
7102
6750
|
</div>
|
|
7103
6751
|
<div class="card-body">
|
|
7104
|
-
<div
|
|
6752
|
+
<div class="d-flex flex-wrap gap-2">
|
|
6753
|
+
<button class="btn btn-outline-primary" data-action="run-simple-job">
|
|
6754
|
+
<i class="bi bi-play-circle me-2"></i>Run Simple Job
|
|
6755
|
+
</button>
|
|
6756
|
+
<button class="btn btn-outline-primary" data-action="run-test-jobs">
|
|
6757
|
+
<i class="bi bi-robot me-2"></i>Run Test Jobs
|
|
6758
|
+
</button>
|
|
6759
|
+
<button class="btn btn-outline-warning" data-action="clear-stuck">
|
|
6760
|
+
<i class="bi bi-wrench me-2"></i>Clear Stuck
|
|
6761
|
+
</button>
|
|
6762
|
+
<button class="btn btn-outline-warning" data-action="clear-channel">
|
|
6763
|
+
<i class="bi bi-eraser me-2"></i>Clear Channel
|
|
6764
|
+
</button>
|
|
6765
|
+
<button class="btn btn-outline-danger" data-action="purge-jobs">
|
|
6766
|
+
<i class="bi bi-trash me-2"></i>Purge Jobs
|
|
6767
|
+
</button>
|
|
6768
|
+
<button class="btn btn-outline-info" data-action="cleanup-consumers">
|
|
6769
|
+
<i class="bi bi-people me-2"></i>Cleanup Consumers
|
|
6770
|
+
</button>
|
|
6771
|
+
<button class="btn btn-outline-secondary" data-action="runner-broadcast">
|
|
6772
|
+
<i class="bi bi-wifi me-2"></i>Broadcast Command
|
|
6773
|
+
</button>
|
|
6774
|
+
</div>
|
|
7105
6775
|
</div>
|
|
7106
6776
|
</div>
|
|
7107
6777
|
</div>
|
|
@@ -7119,36 +6789,308 @@ class JobsAdminPage extends Page {
|
|
|
7119
6789
|
model: this.jobStats
|
|
7120
6790
|
});
|
|
7121
6791
|
this.addChild(this.jobHealthView);
|
|
7122
|
-
this.
|
|
7123
|
-
containerId: "
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
6792
|
+
this.jobsPublishedChart = new MetricsMiniChartWidget({
|
|
6793
|
+
containerId: "jobs-published-chart",
|
|
6794
|
+
icon: "bi bi-upload",
|
|
6795
|
+
title: "Jobs Published",
|
|
6796
|
+
subtitle: "{{now_value}} {{now_label}}",
|
|
6797
|
+
granularity: "days",
|
|
6798
|
+
slugs: ["jobs.published"],
|
|
6799
|
+
account: "global",
|
|
6800
|
+
chartType: "line",
|
|
6801
|
+
height: 90,
|
|
6802
|
+
showSettings: true,
|
|
6803
|
+
showTrending: true,
|
|
6804
|
+
showDateRange: false
|
|
6805
|
+
});
|
|
6806
|
+
this.addChild(this.jobsPublishedChart);
|
|
6807
|
+
this.jobsFailedChart = new MetricsMiniChartWidget({
|
|
6808
|
+
containerId: "jobs-failed-chart",
|
|
6809
|
+
icon: "bi bi-exclamation-octagon",
|
|
6810
|
+
title: "Jobs Failed",
|
|
6811
|
+
subtitle: "{{now_value}} {{now_label}}",
|
|
6812
|
+
granularity: "days",
|
|
6813
|
+
slugs: ["jobs.failed"],
|
|
6814
|
+
account: "global",
|
|
6815
|
+
chartType: "line",
|
|
6816
|
+
height: 90,
|
|
6817
|
+
showSettings: true,
|
|
6818
|
+
showTrending: true,
|
|
6819
|
+
showDateRange: false
|
|
6820
|
+
});
|
|
6821
|
+
this.addChild(this.jobsFailedChart);
|
|
6822
|
+
this.runningJobsTable = new TableView({
|
|
6823
|
+
containerId: "running-jobs-table",
|
|
6824
|
+
Collection: JobList,
|
|
6825
|
+
collectionParams: {
|
|
6826
|
+
size: 5,
|
|
6827
|
+
sort: "-created",
|
|
6828
|
+
status: "running"
|
|
6829
|
+
},
|
|
6830
|
+
searchable: true,
|
|
6831
|
+
filterable: false,
|
|
6832
|
+
paginated: true,
|
|
6833
|
+
itemView: JobDetailsView,
|
|
6834
|
+
hideActivePills: ["status"],
|
|
6835
|
+
viewDialogOptions: {
|
|
6836
|
+
title: "Job Details",
|
|
6837
|
+
size: "xl",
|
|
6838
|
+
scrollable: true
|
|
6839
|
+
},
|
|
6840
|
+
tableOptions: {
|
|
6841
|
+
striped: false,
|
|
6842
|
+
hover: true,
|
|
6843
|
+
size: "sm"
|
|
7128
6844
|
},
|
|
7129
|
-
|
|
6845
|
+
columns: [
|
|
6846
|
+
{
|
|
6847
|
+
key: "id",
|
|
6848
|
+
label: "Job",
|
|
6849
|
+
template: `
|
|
6850
|
+
<div class="fw-semibold font-monospace">{{model.id|truncate_middle(12)}}</div>
|
|
6851
|
+
<div class="text-muted small">{{model.channel}} · {{model.func|truncate_middle(28)|default('n/a')}}</div>
|
|
6852
|
+
`
|
|
6853
|
+
},
|
|
6854
|
+
{
|
|
6855
|
+
key: "runner_id",
|
|
6856
|
+
label: "Runner",
|
|
6857
|
+
template: `
|
|
6858
|
+
<span class="font-monospace">{{model.runner_id|truncate_middle(12)|default('n/a')}}</span>
|
|
6859
|
+
`
|
|
6860
|
+
},
|
|
6861
|
+
{
|
|
6862
|
+
key: "status",
|
|
6863
|
+
label: "State",
|
|
6864
|
+
formatter: (value, context) => {
|
|
6865
|
+
const job = context.row;
|
|
6866
|
+
const badgeClass = job.getStatusBadgeClass ? job.getStatusBadgeClass() : "bg-secondary";
|
|
6867
|
+
const icon = job.getStatusIcon ? job.getStatusIcon() : "bi-question";
|
|
6868
|
+
return `<span class="badge ${badgeClass}"><i class="${icon} me-1"></i>${value.toUpperCase()}</span>`;
|
|
6869
|
+
}
|
|
6870
|
+
},
|
|
6871
|
+
{
|
|
6872
|
+
key: "created",
|
|
6873
|
+
label: "Started",
|
|
6874
|
+
formatter: "datetime"
|
|
6875
|
+
}
|
|
6876
|
+
]
|
|
7130
6877
|
});
|
|
7131
|
-
this.addChild(this.
|
|
7132
|
-
this.
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
showDateRange: false,
|
|
7141
|
-
yAxis: {
|
|
7142
|
-
label: "Count",
|
|
7143
|
-
beginAtZero: true
|
|
6878
|
+
this.addChild(this.runningJobsTable);
|
|
6879
|
+
this.pendingJobsTable = new TableView({
|
|
6880
|
+
containerId: "pending-jobs-table",
|
|
6881
|
+
Collection: JobList,
|
|
6882
|
+
collectionParams: {
|
|
6883
|
+
size: 5,
|
|
6884
|
+
sort: "-created",
|
|
6885
|
+
status: "pending",
|
|
6886
|
+
run_at__isnull: true
|
|
7144
6887
|
},
|
|
7145
|
-
|
|
7146
|
-
|
|
6888
|
+
searchable: true,
|
|
6889
|
+
filterable: false,
|
|
6890
|
+
paginated: true,
|
|
6891
|
+
selectable: true,
|
|
6892
|
+
batchBarLocation: "top",
|
|
6893
|
+
batchActions: [
|
|
6894
|
+
{
|
|
6895
|
+
icon: "bi-x-circle-fill",
|
|
6896
|
+
label: "Cancel Jobs",
|
|
6897
|
+
action: "cancel-jobs"
|
|
6898
|
+
}
|
|
6899
|
+
],
|
|
6900
|
+
itemView: JobDetailsView,
|
|
6901
|
+
hideActivePills: ["status"],
|
|
6902
|
+
viewDialogOptions: {
|
|
6903
|
+
title: "Job Details",
|
|
6904
|
+
size: "xl",
|
|
6905
|
+
scrollable: true
|
|
6906
|
+
},
|
|
6907
|
+
tableOptions: {
|
|
6908
|
+
striped: false,
|
|
6909
|
+
hover: true,
|
|
6910
|
+
size: "sm"
|
|
6911
|
+
},
|
|
6912
|
+
columns: [
|
|
6913
|
+
{
|
|
6914
|
+
key: "id",
|
|
6915
|
+
label: "Job",
|
|
6916
|
+
template: `
|
|
6917
|
+
<div class="fw-semibold font-monospace">{{model.id|truncate_middle(12)}}</div>
|
|
6918
|
+
<div class="text-muted small">{{model.channel}} · {{model.func|truncate_middle(28)|default('n/a')}}</div>
|
|
6919
|
+
`
|
|
6920
|
+
},
|
|
6921
|
+
{
|
|
6922
|
+
key: "priority",
|
|
6923
|
+
label: "Priority",
|
|
6924
|
+
formatter: (value = 0) => {
|
|
6925
|
+
const badge = value >= 8 ? "bg-danger" : value >= 5 ? "bg-warning" : "bg-secondary";
|
|
6926
|
+
return `<span class="badge ${badge}">${value}</span>`;
|
|
6927
|
+
}
|
|
6928
|
+
},
|
|
6929
|
+
{
|
|
6930
|
+
key: "modified",
|
|
6931
|
+
label: "Queued",
|
|
6932
|
+
formatter: "relative"
|
|
6933
|
+
}
|
|
6934
|
+
]
|
|
6935
|
+
});
|
|
6936
|
+
this.pendingJobsTable.on("action:batch-cancel-jobs", async (action, event, element) => {
|
|
6937
|
+
const items = this.pendingJobsTable.getSelectedItems();
|
|
6938
|
+
await Promise.all(items.map((item) => item.model.cancel()));
|
|
6939
|
+
this.getApp().toast.success("Jobs cancelled successfully");
|
|
6940
|
+
this.pendingJobsTable.collection.fetch();
|
|
6941
|
+
});
|
|
6942
|
+
this.addChild(this.pendingJobsTable);
|
|
6943
|
+
this.failedJobsTable = new TableView({
|
|
6944
|
+
containerId: "failed-jobs-table",
|
|
6945
|
+
Collection: JobList,
|
|
6946
|
+
collectionParams: {
|
|
6947
|
+
size: 5,
|
|
6948
|
+
sort: "-finished_at",
|
|
6949
|
+
status: "failed"
|
|
6950
|
+
},
|
|
6951
|
+
searchable: true,
|
|
6952
|
+
filterable: false,
|
|
6953
|
+
paginated: true,
|
|
6954
|
+
itemView: JobDetailsView,
|
|
6955
|
+
viewDialogOptions: {
|
|
6956
|
+
title: "Job Details",
|
|
6957
|
+
size: "xl",
|
|
6958
|
+
scrollable: true
|
|
6959
|
+
},
|
|
6960
|
+
hideActivePills: ["status"],
|
|
6961
|
+
tableOptions: {
|
|
6962
|
+
striped: false,
|
|
6963
|
+
hover: true,
|
|
6964
|
+
size: "sm"
|
|
6965
|
+
},
|
|
6966
|
+
columns: [
|
|
6967
|
+
{
|
|
6968
|
+
key: "id",
|
|
6969
|
+
label: "Job",
|
|
6970
|
+
template: `
|
|
6971
|
+
<div class="fw-semibold font-monospace">{{model.id|truncate_middle(12)}}</div>
|
|
6972
|
+
<div class="text-muted small">{{model.channel}} · {{model.func|truncate_middle(28)|default('n/a')}}</div>
|
|
6973
|
+
`
|
|
6974
|
+
},
|
|
6975
|
+
{
|
|
6976
|
+
key: "last_error",
|
|
6977
|
+
label: "Error",
|
|
6978
|
+
template: `
|
|
6979
|
+
<div class="text-danger small">{{model.last_error|truncate(80)|default('Unknown error')}}</div>
|
|
6980
|
+
`
|
|
6981
|
+
},
|
|
6982
|
+
{
|
|
6983
|
+
key: "modified",
|
|
6984
|
+
label: "Failed",
|
|
6985
|
+
formatter: "relative"
|
|
6986
|
+
}
|
|
6987
|
+
]
|
|
6988
|
+
});
|
|
6989
|
+
this.addChild(this.failedJobsTable);
|
|
6990
|
+
this.scheduledJobsTable = new TableView({
|
|
6991
|
+
containerId: "scheduled-jobs-table",
|
|
6992
|
+
Collection: JobList,
|
|
6993
|
+
collectionParams: {
|
|
6994
|
+
size: 5,
|
|
6995
|
+
sort: "run_at",
|
|
6996
|
+
run_at__isnull: false,
|
|
6997
|
+
status: "pending"
|
|
6998
|
+
},
|
|
6999
|
+
searchable: true,
|
|
7000
|
+
filterable: false,
|
|
7001
|
+
paginated: true,
|
|
7002
|
+
itemView: JobDetailsView,
|
|
7003
|
+
hideActivePills: ["status"],
|
|
7004
|
+
viewDialogOptions: {
|
|
7005
|
+
title: "Job Details",
|
|
7006
|
+
size: "xl",
|
|
7007
|
+
scrollable: true
|
|
7008
|
+
},
|
|
7009
|
+
tableOptions: {
|
|
7010
|
+
striped: false,
|
|
7011
|
+
hover: true,
|
|
7012
|
+
size: "sm"
|
|
7013
|
+
},
|
|
7014
|
+
columns: [
|
|
7015
|
+
{
|
|
7016
|
+
key: "id",
|
|
7017
|
+
label: "Job",
|
|
7018
|
+
formatter: "truncate_middle(12)"
|
|
7019
|
+
},
|
|
7020
|
+
{
|
|
7021
|
+
key: "run_at",
|
|
7022
|
+
label: "Scheduled For",
|
|
7023
|
+
formatter: "datetime"
|
|
7024
|
+
},
|
|
7025
|
+
{
|
|
7026
|
+
key: "channel",
|
|
7027
|
+
label: "Channel",
|
|
7028
|
+
formatter: "badge"
|
|
7029
|
+
}
|
|
7030
|
+
],
|
|
7031
|
+
selectable: true,
|
|
7032
|
+
batchBarLocation: "top",
|
|
7033
|
+
batchActions: [
|
|
7034
|
+
{
|
|
7035
|
+
icon: "bi-x-circle-fill",
|
|
7036
|
+
label: "Cancel Jobs",
|
|
7037
|
+
action: "cancel-jobs"
|
|
7038
|
+
}
|
|
7039
|
+
]
|
|
7040
|
+
});
|
|
7041
|
+
this.scheduledJobsTable.on("action:batch-cancel-jobs", async (action, event, element) => {
|
|
7042
|
+
const items = this.scheduledJobsTable.getSelectedItems();
|
|
7043
|
+
await Promise.all(items.map((item) => item.model.cancel()));
|
|
7044
|
+
this.getApp().toast.success("Jobs cancelled successfully");
|
|
7045
|
+
this.scheduledJobsTable.collection.fetch();
|
|
7046
|
+
});
|
|
7047
|
+
this.addChild(this.scheduledJobsTable);
|
|
7048
|
+
this.runnersTable = new TableView({
|
|
7049
|
+
containerId: "runner-table",
|
|
7050
|
+
Collection: JobRunnerList,
|
|
7051
|
+
searchable: true,
|
|
7052
|
+
filterable: false,
|
|
7053
|
+
paginated: true,
|
|
7054
|
+
tableOptions: {
|
|
7055
|
+
striped: false,
|
|
7056
|
+
hover: true,
|
|
7057
|
+
size: "sm"
|
|
7147
7058
|
},
|
|
7148
|
-
|
|
7059
|
+
columns: [
|
|
7060
|
+
{
|
|
7061
|
+
key: "runner_id",
|
|
7062
|
+
label: "Runner",
|
|
7063
|
+
formatter: "truncate_middle(16)"
|
|
7064
|
+
},
|
|
7065
|
+
{
|
|
7066
|
+
key: "alive",
|
|
7067
|
+
label: "Status",
|
|
7068
|
+
formatter: (value) => {
|
|
7069
|
+
const isAlive = value === true;
|
|
7070
|
+
const badgeClass = isAlive ? "bg-success" : "bg-danger";
|
|
7071
|
+
const icon = isAlive ? "bi-check-circle-fill" : "bi-x-octagon-fill";
|
|
7072
|
+
const text = isAlive ? "ALIVE" : "DEAD";
|
|
7073
|
+
return `<span class="badge ${badgeClass}"><i class="${icon} me-1"></i>${text}</span>`;
|
|
7074
|
+
}
|
|
7075
|
+
},
|
|
7076
|
+
{
|
|
7077
|
+
key: "last_heartbeat",
|
|
7078
|
+
label: "Heartbeat",
|
|
7079
|
+
formatter: (value) => {
|
|
7080
|
+
if (!value) return "Never";
|
|
7081
|
+
const heartbeatTime = new Date(value);
|
|
7082
|
+
const now = /* @__PURE__ */ new Date();
|
|
7083
|
+
const diffMs = now - heartbeatTime;
|
|
7084
|
+
const diffSeconds = Math.floor(diffMs / 1e3);
|
|
7085
|
+
if (diffSeconds < 60) return `${diffSeconds}s ago`;
|
|
7086
|
+
if (diffSeconds < 3600) return `${Math.floor(diffSeconds / 60)}m ago`;
|
|
7087
|
+
return `${Math.floor(diffSeconds / 3600)}h ago`;
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
]
|
|
7149
7091
|
});
|
|
7150
|
-
this.addChild(this.
|
|
7151
|
-
await this.
|
|
7092
|
+
this.addChild(this.runnersTable);
|
|
7093
|
+
await this.refreshData();
|
|
7152
7094
|
}
|
|
7153
7095
|
// Auto-refresh management
|
|
7154
7096
|
startAutoRefresh() {
|
|
@@ -7163,14 +7105,31 @@ class JobsAdminPage extends Page {
|
|
|
7163
7105
|
}
|
|
7164
7106
|
async refreshData() {
|
|
7165
7107
|
try {
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7108
|
+
const tasks = [
|
|
7109
|
+
this.jobStats.fetch()
|
|
7110
|
+
];
|
|
7111
|
+
if (this.jobsPublishedChart) {
|
|
7112
|
+
tasks.push(this.jobsPublishedChart.refresh());
|
|
7113
|
+
}
|
|
7114
|
+
if (this.jobsFailedChart) {
|
|
7115
|
+
tasks.push(this.jobsFailedChart.refresh());
|
|
7116
|
+
}
|
|
7117
|
+
if (this.runningJobsTable?.collection?.fetch) {
|
|
7118
|
+
tasks.push(this.runningJobsTable.collection.fetch());
|
|
7119
|
+
}
|
|
7120
|
+
if (this.pendingJobsTable?.collection?.fetch) {
|
|
7121
|
+
tasks.push(this.pendingJobsTable.collection.fetch());
|
|
7173
7122
|
}
|
|
7123
|
+
if (this.failedJobsTable?.collection?.fetch) {
|
|
7124
|
+
tasks.push(this.failedJobsTable.collection.fetch());
|
|
7125
|
+
}
|
|
7126
|
+
if (this.scheduledJobsTable?.collection?.fetch) {
|
|
7127
|
+
tasks.push(this.scheduledJobsTable.collection.fetch());
|
|
7128
|
+
}
|
|
7129
|
+
if (this.runnersTable?.collection?.fetch) {
|
|
7130
|
+
tasks.push(this.runnersTable.collection.fetch());
|
|
7131
|
+
}
|
|
7132
|
+
await Promise.all(tasks);
|
|
7174
7133
|
this.lastUpdated = (/* @__PURE__ */ new Date()).toLocaleString();
|
|
7175
7134
|
this.updateHeaderTimestamp();
|
|
7176
7135
|
} catch (error) {
|
|
@@ -7180,15 +7139,19 @@ class JobsAdminPage extends Page {
|
|
|
7180
7139
|
updateHeaderTimestamp() {
|
|
7181
7140
|
const timestampElement = this.element?.querySelector(".text-info");
|
|
7182
7141
|
if (timestampElement) {
|
|
7142
|
+
const rateLabel = this.refreshRate === 0 ? "Off" : `${this.refreshRate / 1e3}s`;
|
|
7183
7143
|
timestampElement.innerHTML = `
|
|
7184
7144
|
<i class="bi bi-arrow-clockwise me-1"></i>
|
|
7185
|
-
Auto-refresh: ${
|
|
7145
|
+
Auto-refresh: ${rateLabel} | Last updated: ${this.lastUpdated}
|
|
7186
7146
|
`;
|
|
7187
7147
|
}
|
|
7188
7148
|
}
|
|
7189
7149
|
get refreshRateSeconds() {
|
|
7190
7150
|
return this.refreshRate / 1e3;
|
|
7191
7151
|
}
|
|
7152
|
+
get refreshRateLabel() {
|
|
7153
|
+
return this.refreshRate === 0 ? "Off" : `${this.refreshRateSeconds}s`;
|
|
7154
|
+
}
|
|
7192
7155
|
// Action handlers
|
|
7193
7156
|
async onActionRefreshAll(event, element) {
|
|
7194
7157
|
try {
|
|
@@ -7196,7 +7159,6 @@ class JobsAdminPage extends Page {
|
|
|
7196
7159
|
icon?.classList.add("spinning");
|
|
7197
7160
|
element.disabled = true;
|
|
7198
7161
|
await this.refreshData();
|
|
7199
|
-
await this.render();
|
|
7200
7162
|
} catch (error) {
|
|
7201
7163
|
console.error("Failed to refresh jobs dashboard:", error);
|
|
7202
7164
|
} finally {
|
|
@@ -7412,6 +7374,26 @@ class JobsAdminPage extends Page {
|
|
|
7412
7374
|
getHealth() {
|
|
7413
7375
|
return this.jobHealthView?.health || {};
|
|
7414
7376
|
}
|
|
7377
|
+
async onActionOpenJobMetricsModal() {
|
|
7378
|
+
const modalView = new JobMetricsModalView();
|
|
7379
|
+
await Dialog$1.showDialog({
|
|
7380
|
+
title: '<i class="bi bi-graph-up me-2"></i>Job Channel Metrics',
|
|
7381
|
+
body: modalView,
|
|
7382
|
+
size: "xl",
|
|
7383
|
+
scrollable: true,
|
|
7384
|
+
buttons: [
|
|
7385
|
+
{ text: "Close", class: "btn-secondary", dismiss: true }
|
|
7386
|
+
]
|
|
7387
|
+
});
|
|
7388
|
+
}
|
|
7389
|
+
async onActionViewAllJobs() {
|
|
7390
|
+
const router = this.getApp()?.router;
|
|
7391
|
+
if (router) {
|
|
7392
|
+
router.navigateTo("/admin/jobs/table");
|
|
7393
|
+
} else {
|
|
7394
|
+
this.getApp()?.toast?.info("Router unavailable.");
|
|
7395
|
+
}
|
|
7396
|
+
}
|
|
7415
7397
|
}
|
|
7416
7398
|
class TaskDetailsView extends View {
|
|
7417
7399
|
constructor(options = {}) {
|