portal-mojo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +55 -0
- package/docs/DetailView.md +173 -0
- package/docs/ModelTable.md +128 -0
- package/docs/README.md +98 -0
- package/docs/admin-assistant.md +72 -0
- package/docs/admin-bouncer.md +120 -0
- package/docs/admin-cloudwatch.md +38 -0
- package/docs/admin-credentials.md +116 -0
- package/docs/admin-dashboard.md +15 -0
- package/docs/admin-devices-geoip.md +284 -0
- package/docs/admin-dns-certificates.md +122 -0
- package/docs/admin-dns-records.md +65 -0
- package/docs/admin-dns-registrar.md +33 -0
- package/docs/admin-dns.md +80 -0
- package/docs/admin-identity-users.md +103 -0
- package/docs/admin-incidents.md +71 -0
- package/docs/admin-jobs.md +426 -0
- package/docs/admin-members.md +112 -0
- package/docs/admin-messaging.md +56 -0
- package/docs/admin-metrics-explorer.md +63 -0
- package/docs/admin-monitoring.md +99 -0
- package/docs/admin-network-security.md +393 -0
- package/docs/admin-phonehub.md +27 -0
- package/docs/admin-push.md +36 -0
- package/docs/admin-rules.md +87 -0
- package/docs/admin-security-tickets.md +40 -0
- package/docs/admin-settings.md +78 -0
- package/docs/admin-shortlinks.md +32 -0
- package/docs/admin-storage.md +141 -0
- package/docs/auth-pages.md +212 -0
- package/docs/auth.md +58 -0
- package/docs/calendar.md +122 -0
- package/docs/charts.md +303 -0
- package/docs/client.md +206 -0
- package/docs/collection-multiselect.md +117 -0
- package/docs/collection-select.md +132 -0
- package/docs/combobox.md +105 -0
- package/docs/dataview.md +226 -0
- package/docs/datepicker.md +151 -0
- package/docs/daterange.md +170 -0
- package/docs/datetimepicker.md +249 -0
- package/docs/defineModel.md +72 -0
- package/docs/detail-primitives.md +174 -0
- package/docs/feedback.md +85 -0
- package/docs/file-upload-ux.md +146 -0
- package/docs/forms.md +435 -0
- package/docs/grouping-and-fmt.md +98 -0
- package/docs/idioms.md +112 -0
- package/docs/image-editor.md +101 -0
- package/docs/loading.md +242 -0
- package/docs/location-address.md +76 -0
- package/docs/markdown.md +203 -0
- package/docs/menus-and-access.md +89 -0
- package/docs/multiselect-dropdown.md +153 -0
- package/docs/params.md +73 -0
- package/docs/popover.md +130 -0
- package/docs/realtime.md +61 -0
- package/docs/record-feed.md +238 -0
- package/docs/right-panel.md +42 -0
- package/docs/tabs-and-form-wizard.md +60 -0
- package/docs/taginput.md +120 -0
- package/docs/timepicker.md +173 -0
- package/docs/timezone-select.md +147 -0
- package/docs/worldmap.md +302 -0
- package/package.json +55 -0
- package/src/admin/assistant/AssistantFeed.tsx +29 -0
- package/src/admin/assistant/AssistantPanel.tsx +65 -0
- package/src/admin/assistant/api.ts +17 -0
- package/src/admin/assistant/data.ts +63 -0
- package/src/admin/assistant/index.ts +21 -0
- package/src/admin/assistant/launchers.tsx +33 -0
- package/src/admin/assistant/pages.tsx +78 -0
- package/src/admin/assistant/streaming.ts +238 -0
- package/src/admin/assistant/types.ts +30 -0
- package/src/admin/bouncer/devices.tsx +230 -0
- package/src/admin/bouncer/index.ts +34 -0
- package/src/admin/bouncer/models.ts +199 -0
- package/src/admin/bouncer/signals.tsx +182 -0
- package/src/admin/bouncer/signatures.tsx +220 -0
- package/src/admin/cloudwatch/CloudWatchChart.tsx +61 -0
- package/src/admin/cloudwatch/CloudWatchDashboardPage.tsx +52 -0
- package/src/admin/cloudwatch/CloudWatchResourceDetail.tsx +33 -0
- package/src/admin/cloudwatch/data.ts +163 -0
- package/src/admin/cloudwatch/index.ts +16 -0
- package/src/admin/credentials/group-api-keys.tsx +433 -0
- package/src/admin/credentials/index.ts +4 -0
- package/src/admin/credentials/models.ts +244 -0
- package/src/admin/credentials/secret-dialog.tsx +77 -0
- package/src/admin/credentials/webhook-subscriptions.tsx +423 -0
- package/src/admin/dashboard/AdminDashboardPage.tsx +101 -0
- package/src/admin/dashboard/data.ts +60 -0
- package/src/admin/dashboard/index.ts +2 -0
- package/src/admin/dns/CertificateDetail.tsx +115 -0
- package/src/admin/dns/CertificateLifecyclePoller.tsx +141 -0
- package/src/admin/dns/CertificateRequestDialog.tsx +103 -0
- package/src/admin/dns/CertificatesPage.tsx +85 -0
- package/src/admin/dns/DnsRecordEditor.tsx +110 -0
- package/src/admin/dns/DnsRecordsPage.tsx +36 -0
- package/src/admin/dns/DnsRecordsPanel.tsx +71 -0
- package/src/admin/dns/DomainCertificatesSection.tsx +40 -0
- package/src/admin/dns/DomainDetail.tsx +41 -0
- package/src/admin/dns/DomainOnboardingDialog.tsx +27 -0
- package/src/admin/dns/DomainPurchaseDetail.tsx +11 -0
- package/src/admin/dns/DomainPurchaseWizard.tsx +112 -0
- package/src/admin/dns/DomainPurchasesPage.tsx +16 -0
- package/src/admin/dns/DomainsPage.tsx +27 -0
- package/src/admin/dns/ProviderCredentialsPage.tsx +304 -0
- package/src/admin/dns/RegistrantContactPage.tsx +42 -0
- package/src/admin/dns/api.ts +515 -0
- package/src/admin/dns/certificate-data.ts +221 -0
- package/src/admin/dns/data.ts +32 -0
- package/src/admin/dns/dns-data.ts +266 -0
- package/src/admin/dns/dns-integration.ts +24 -0
- package/src/admin/dns/domain-links.ts +37 -0
- package/src/admin/dns/index.ts +86 -0
- package/src/admin/dns/models.ts +418 -0
- package/src/admin/dns/purchase-data.ts +67 -0
- package/src/admin/dns/registrant-data.ts +52 -0
- package/src/admin/identity/index.ts +5 -0
- package/src/admin/identity/members/GroupMembersPanel.tsx +146 -0
- package/src/admin/identity/members/MemberDetail.tsx +226 -0
- package/src/admin/identity/members/MembersPage.tsx +144 -0
- package/src/admin/identity/members/index.ts +24 -0
- package/src/admin/identity/members/member-flows.tsx +132 -0
- package/src/admin/identity/members/models.ts +204 -0
- package/src/admin/identity/users/UserDetail.tsx +257 -0
- package/src/admin/identity/users/UsersPage.tsx +163 -0
- package/src/admin/identity/users/index.ts +27 -0
- package/src/admin/identity/users/models.ts +232 -0
- package/src/admin/identity/users/sections/ApiKeysSection.tsx +119 -0
- package/src/admin/identity/users/sections/AuditSection.tsx +190 -0
- package/src/admin/identity/users/sections/DevicesSection.tsx +140 -0
- package/src/admin/identity/users/sections/GroupsSection.tsx +64 -0
- package/src/admin/identity/users/sections/LoginsSection.tsx +106 -0
- package/src/admin/identity/users/sections/MetadataPanel.tsx +36 -0
- package/src/admin/identity/users/sections/NotificationsSection.tsx +117 -0
- package/src/admin/identity/users/sections/OAuthSection.tsx +72 -0
- package/src/admin/identity/users/sections/OverviewSection.tsx +109 -0
- package/src/admin/identity/users/sections/PermissionsSection.tsx +35 -0
- package/src/admin/identity/users/sections/PersonalSection.tsx +73 -0
- package/src/admin/identity/users/sections/ProfileSection.tsx +175 -0
- package/src/admin/identity/users/sections/SecuritySection.tsx +123 -0
- package/src/admin/identity/users/sections/actions.tsx +752 -0
- package/src/admin/identity/users/sections/permission-catalog.ts +138 -0
- package/src/admin/identity/users/sections/queries.ts +74 -0
- package/src/admin/identity/users/sections/shared.tsx +299 -0
- package/src/admin/incidents/EventDetail.tsx +43 -0
- package/src/admin/incidents/EventsPage.tsx +52 -0
- package/src/admin/incidents/IncidentDetail.tsx +68 -0
- package/src/admin/incidents/IncidentsPage.tsx +102 -0
- package/src/admin/incidents/forensics.tsx +73 -0
- package/src/admin/incidents/index.ts +16 -0
- package/src/admin/incidents/models.ts +113 -0
- package/src/admin/incidents/sanitize.ts +71 -0
- package/src/admin/index.ts +383 -0
- package/src/admin/jobs/JobDashboardPage.tsx +111 -0
- package/src/admin/jobs/JobDetail.tsx +721 -0
- package/src/admin/jobs/JobRunnersPage.tsx +216 -0
- package/src/admin/jobs/JobsTablePage.tsx +122 -0
- package/src/admin/jobs/RunnerDetail.tsx +576 -0
- package/src/admin/jobs/ScheduledTaskDetail.tsx +421 -0
- package/src/admin/jobs/ScheduledTasksPage.tsx +167 -0
- package/src/admin/jobs/columns.tsx +297 -0
- package/src/admin/jobs/control.ts +488 -0
- package/src/admin/jobs/index.ts +85 -0
- package/src/admin/jobs/models.ts +599 -0
- package/src/admin/jobs/queries.ts +272 -0
- package/src/admin/jobs/scheduled-task-form.tsx +493 -0
- package/src/admin/jobs/sections/JobChannelsPanel.tsx +142 -0
- package/src/admin/jobs/sections/JobOperationsSection.tsx +423 -0
- package/src/admin/jobs/sections/JobRunnersStrip.tsx +82 -0
- package/src/admin/jobs/sections/JobThroughputSection.tsx +118 -0
- package/src/admin/messaging/EmailDomainWizard.tsx +24 -0
- package/src/admin/messaging/EmailDomainsPage.tsx +17 -0
- package/src/admin/messaging/EmailTemplatesPage.tsx +13 -0
- package/src/admin/messaging/MailboxesPage.tsx +15 -0
- package/src/admin/messaging/PublicMessagesPage.tsx +13 -0
- package/src/admin/messaging/SandboxedEmailPreview.tsx +23 -0
- package/src/admin/messaging/SentMessagesPage.tsx +11 -0
- package/src/admin/messaging/api.ts +17 -0
- package/src/admin/messaging/data.ts +10 -0
- package/src/admin/messaging/index.ts +23 -0
- package/src/admin/messaging/models.ts +76 -0
- package/src/admin/messaging/push/PushPage.tsx +44 -0
- package/src/admin/messaging/push/api.ts +17 -0
- package/src/admin/messaging/push/index.ts +9 -0
- package/src/admin/messaging/push/models.ts +43 -0
- package/src/admin/messaging/sanitize.ts +44 -0
- package/src/admin/monitoring/LogInspector.tsx +160 -0
- package/src/admin/monitoring/LogsPage.tsx +135 -0
- package/src/admin/monitoring/MetricsExplorerPage.tsx +360 -0
- package/src/admin/monitoring/MetricsPermissionsPage.tsx +166 -0
- package/src/admin/monitoring/MetricsSourcePicker.tsx +136 -0
- package/src/admin/monitoring/index.ts +55 -0
- package/src/admin/monitoring/metrics-explorer-client.ts +229 -0
- package/src/admin/monitoring/metrics-explorer-data.ts +158 -0
- package/src/admin/monitoring/models.ts +155 -0
- package/src/admin/network/BlockedIPsPage.tsx +194 -0
- package/src/admin/network/BlockedIpDetail.tsx +262 -0
- package/src/admin/network/FirewallLogPage.tsx +115 -0
- package/src/admin/network/IPSetDetail.tsx +280 -0
- package/src/admin/network/IPSetEditor.tsx +191 -0
- package/src/admin/network/IPSetsPage.tsx +177 -0
- package/src/admin/network/geofence/BlocksTab.tsx +289 -0
- package/src/admin/network/geofence/ExemptionsTab.tsx +349 -0
- package/src/admin/network/geofence/GeofencingPage.tsx +119 -0
- package/src/admin/network/geofence/PostureHeader.tsx +184 -0
- package/src/admin/network/geofence/RuleEditor.tsx +176 -0
- package/src/admin/network/geofence/RulesTab.tsx +254 -0
- package/src/admin/network/geofence/SimulatorTab.tsx +281 -0
- package/src/admin/network/geofence/geofence-data.ts +674 -0
- package/src/admin/network/index.ts +68 -0
- package/src/admin/network/models.ts +607 -0
- package/src/admin/phonehub/PhoneHubPage.tsx +57 -0
- package/src/admin/phonehub/api.ts +47 -0
- package/src/admin/phonehub/data.ts +6 -0
- package/src/admin/phonehub/index.ts +10 -0
- package/src/admin/phonehub/models.ts +68 -0
- package/src/admin/rules/HandlerChainBuilder.tsx +51 -0
- package/src/admin/rules/RuleSetDetailPage.tsx +33 -0
- package/src/admin/rules/RuleSetsPage.tsx +33 -0
- package/src/admin/rules/editors.tsx +54 -0
- package/src/admin/rules/handler-dsl.ts +213 -0
- package/src/admin/rules/index.ts +13 -0
- package/src/admin/rules/models.ts +112 -0
- package/src/admin/security/devices/LoginEventDetail.tsx +123 -0
- package/src/admin/security/devices/LoginLocationMap.tsx +321 -0
- package/src/admin/security/devices/LoginLocationsPage.tsx +162 -0
- package/src/admin/security/devices/UserDeviceDetail.tsx +546 -0
- package/src/admin/security/devices/UserDeviceLocationDetail.tsx +272 -0
- package/src/admin/security/devices/UserDevicesPage.tsx +84 -0
- package/src/admin/security/devices/index.ts +40 -0
- package/src/admin/security/devices/models.ts +470 -0
- package/src/admin/security/geoip/GeoIpCachePage.tsx +135 -0
- package/src/admin/security/geoip/GeoIpDossier.tsx +804 -0
- package/src/admin/security/geoip/geoip-forms.ts +102 -0
- package/src/admin/security/geoip/index.ts +27 -0
- package/src/admin/security/geoip/models.ts +397 -0
- package/src/admin/security/index.ts +31 -0
- package/src/admin/security/models.ts +137 -0
- package/src/admin/security/tickets.tsx +562 -0
- package/src/admin/security-permissions.ts +5 -0
- package/src/admin/settings/SettingDetail.tsx +57 -0
- package/src/admin/settings/SettingEditor.tsx +157 -0
- package/src/admin/settings/SettingsPage.tsx +72 -0
- package/src/admin/settings/index.ts +19 -0
- package/src/admin/settings/model.ts +143 -0
- package/src/admin/shortlinks/ShortlinkDetail.tsx +55 -0
- package/src/admin/shortlinks/ShortlinkHistoryPage.tsx +18 -0
- package/src/admin/shortlinks/ShortlinksPage.tsx +59 -0
- package/src/admin/shortlinks/index.ts +18 -0
- package/src/admin/shortlinks/models.ts +246 -0
- package/src/admin/storage/BackendsPage.tsx +43 -0
- package/src/admin/storage/BucketDetail.tsx +77 -0
- package/src/admin/storage/BucketsPage.tsx +64 -0
- package/src/admin/storage/FileManagerDetail.tsx +36 -0
- package/src/admin/storage/FilePreview.tsx +54 -0
- package/src/admin/storage/FileUploadSurface.tsx +250 -0
- package/src/admin/storage/FileView.tsx +109 -0
- package/src/admin/storage/FilesPage.tsx +53 -0
- package/src/admin/storage/file-renditions.ts +99 -0
- package/src/admin/storage/index.ts +31 -0
- package/src/admin/storage/models.ts +585 -0
- package/src/admin/storage/storage-dialogs.tsx +80 -0
- package/src/charts/CircularProgress.tsx +281 -0
- package/src/charts/KPIStrip.tsx +162 -0
- package/src/charts/KPITile.tsx +125 -0
- package/src/charts/MetricsChart.tsx +459 -0
- package/src/charts/MetricsMiniWidget.tsx +588 -0
- package/src/charts/MiniChart.tsx +283 -0
- package/src/charts/PieChart.tsx +354 -0
- package/src/charts/SeriesChart.tsx +378 -0
- package/src/charts/chart-dialogs.tsx +149 -0
- package/src/charts/exportChart.ts +144 -0
- package/src/charts/index.ts +28 -0
- package/src/charts/pie-math.ts +127 -0
- package/src/charts/stats.ts +265 -0
- package/src/charts/worldmap/WorldMap.tsx +649 -0
- package/src/charts/worldmap/countryCentroids.ts +304 -0
- package/src/charts/worldmap/geo.ts +293 -0
- package/src/charts/worldmap/use-world-land.ts +47 -0
- package/src/charts/worldmap/world-land.ts +18 -0
- package/src/charts/worldmap/worldmap-data.ts +285 -0
- package/src/client/auth.ts +737 -0
- package/src/client/client.ts +268 -0
- package/src/client/duid.ts +31 -0
- package/src/client/errors.ts +33 -0
- package/src/client/group-context.ts +28 -0
- package/src/client/group.tsx +126 -0
- package/src/client/hooks.ts +50 -0
- package/src/client/index.ts +37 -0
- package/src/client/jwt.ts +75 -0
- package/src/client/location.ts +285 -0
- package/src/client/lookups.ts +41 -0
- package/src/client/markdown-fallback.tsx +115 -0
- package/src/client/markdown-parse.ts +386 -0
- package/src/client/markdown-sanitize.ts +217 -0
- package/src/client/markdown.ts +122 -0
- package/src/client/me.ts +188 -0
- package/src/client/mock.ts +9984 -0
- package/src/client/model.ts +227 -0
- package/src/client/params.ts +292 -0
- package/src/client/realtime-mock.ts +221 -0
- package/src/client/realtime.ts +561 -0
- package/src/client/record-feed.ts +329 -0
- package/src/client/safe-export.ts +71 -0
- package/src/client/types.ts +278 -0
- package/src/client/upload.ts +421 -0
- package/src/ui/AddressField.tsx +273 -0
- package/src/ui/AttachmentQueue.tsx +92 -0
- package/src/ui/CollectionMultiSelect.tsx +622 -0
- package/src/ui/CollectionSelect.tsx +517 -0
- package/src/ui/ComboBox.tsx +336 -0
- package/src/ui/DetailView.tsx +438 -0
- package/src/ui/FileDrop.tsx +194 -0
- package/src/ui/FileField.tsx +369 -0
- package/src/ui/FilterBar.tsx +269 -0
- package/src/ui/FormFields.tsx +77 -0
- package/src/ui/FormView.tsx +360 -0
- package/src/ui/FormWizard.tsx +336 -0
- package/src/ui/GroupSwitcher.tsx +202 -0
- package/src/ui/Guarded.tsx +16 -0
- package/src/ui/MarkdownView.tsx +89 -0
- package/src/ui/ModelTable.tsx +848 -0
- package/src/ui/MultiSelectDropdown.tsx +390 -0
- package/src/ui/Popover.tsx +204 -0
- package/src/ui/RecordFeed.tsx +627 -0
- package/src/ui/RequiresGroup.tsx +32 -0
- package/src/ui/RightPanel.tsx +107 -0
- package/src/ui/SidebarNav.tsx +330 -0
- package/src/ui/Tabs.tsx +161 -0
- package/src/ui/TagInput.tsx +381 -0
- package/src/ui/ThemeProvider.tsx +52 -0
- package/src/ui/UploadQueue.tsx +603 -0
- package/src/ui/armed-button.tsx +91 -0
- package/src/ui/date/Calendar.tsx +498 -0
- package/src/ui/date/DatePicker.tsx +289 -0
- package/src/ui/date/DateRangePicker.tsx +280 -0
- package/src/ui/date/DateTimePicker.tsx +587 -0
- package/src/ui/date/PresetRail.tsx +165 -0
- package/src/ui/date/TimePicker.tsx +624 -0
- package/src/ui/date/TimezoneSelect.tsx +267 -0
- package/src/ui/date/fns.ts +679 -0
- package/src/ui/detail/DataView.tsx +692 -0
- package/src/ui/detail/FlowStrip.tsx +80 -0
- package/src/ui/detail/KnownFieldsCard.tsx +121 -0
- package/src/ui/detail/MetadataSection.tsx +219 -0
- package/src/ui/detail/StackTraceView.tsx +150 -0
- package/src/ui/detail/StatusPanel.tsx +73 -0
- package/src/ui/detail/Timeline.tsx +52 -0
- package/src/ui/detail/index.ts +12 -0
- package/src/ui/field-registry.tsx +428 -0
- package/src/ui/field-wire.ts +322 -0
- package/src/ui/form-autosave.ts +583 -0
- package/src/ui/format.ts +444 -0
- package/src/ui/grouping.ts +150 -0
- package/src/ui/image-editor/ImageEditor.tsx +484 -0
- package/src/ui/image-editor/canvas.ts +254 -0
- package/src/ui/image-editor/index.ts +3 -0
- package/src/ui/image-editor/math.ts +416 -0
- package/src/ui/index.ts +58 -0
- package/src/ui/loading.tsx +604 -0
- package/src/ui/menu-registry.ts +213 -0
- package/src/ui/modal.tsx +297 -0
- package/src/ui/password.tsx +302 -0
- package/src/ui/schema-form-core.tsx +315 -0
- package/src/ui/toast.tsx +219 -0
- package/src/ui/ui.tsx +45 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// Admin jobs — the jobs table's segments, per-segment column sets, filters and
|
|
2
|
+
// its safe client export. Port of web-mojo sections/JobTableSection.js (its
|
|
3
|
+
// per-status `COLUMNS` map, `ALL_FILTERS` and the cancel batch action),
|
|
4
|
+
// re-wired to the fields django-mojo actually serves.
|
|
5
|
+
//
|
|
6
|
+
// Corrections carried from reading the backend rather than the source:
|
|
7
|
+
// · web-mojo's `pending` column set rendered a `priority` badge. jobs.Job
|
|
8
|
+
// has NO priority field in any graph — the column always read `undefined`
|
|
9
|
+
// and rendered `0`. Replaced with the queue facts that exist: how long the
|
|
10
|
+
// job has waited and when it expires.
|
|
11
|
+
// · its `running` set labelled `created` "Started". `started_at` is a real
|
|
12
|
+
// field and is what "started" means for a running job.
|
|
13
|
+
// · the `all` set hand-rolled epoch-vs-ms coercion inside a formatter; `fmt`
|
|
14
|
+
// already sniffs every django-mojo datetime shape.
|
|
15
|
+
// · every cell was a trusted-HTML template string. These are ReactNode
|
|
16
|
+
// renders — the escaping contract cannot be broken.
|
|
17
|
+
//
|
|
18
|
+
// The SEGMENTS are the other half of the port: web-mojo shipped one page per
|
|
19
|
+
// status (JobsTablePage, plus dashboard sections passing `status`), which meant
|
|
20
|
+
// five tables and five column configs to keep in step. Here one table carries
|
|
21
|
+
// preset param bundles and swaps its column set with the segment, because the
|
|
22
|
+
// segment IS the params — `status` (+ `run_at__isnull` to split Queued from
|
|
23
|
+
// Scheduled) and nothing else.
|
|
24
|
+
import { createSafeExporter } from '../../client';
|
|
25
|
+
import { Badge, fmt, type Column, type Preset } from '../../ui';
|
|
26
|
+
import {
|
|
27
|
+
isOverdueJob,
|
|
28
|
+
jobStatusIcon,
|
|
29
|
+
jobStatusTone,
|
|
30
|
+
JobModel,
|
|
31
|
+
type JobRow,
|
|
32
|
+
} from './models';
|
|
33
|
+
|
|
34
|
+
// ── Segments ──────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
export type JobSegmentKey = 'running' | 'queued' | 'scheduled' | 'failed' | 'all';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Queued vs Scheduled splits on `run_at__isnull`, NOT on comparing a datetime
|
|
40
|
+
* on the wire: `__isnull` is an explicitly supported lookup that needs no date
|
|
41
|
+
* coercion, and a pending job whose `run_at` has passed stays in Scheduled
|
|
42
|
+
* flagged "overdue" — which is its true state, not a queue position.
|
|
43
|
+
*/
|
|
44
|
+
export const JOB_SEGMENTS: (Preset & { key: JobSegmentKey })[] = [
|
|
45
|
+
{ key: 'running', label: 'Running', params: { status: 'running' } },
|
|
46
|
+
{ key: 'queued', label: 'Queued', params: { status: 'pending', run_at__isnull: 'true' } },
|
|
47
|
+
{ key: 'scheduled', label: 'Scheduled', params: { status: 'pending', run_at__isnull: 'false' } },
|
|
48
|
+
{ key: 'failed', label: 'Failed', params: { status: 'failed' } },
|
|
49
|
+
{ key: 'all', label: 'All', params: {} },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Which column set the current params ask for.
|
|
54
|
+
*
|
|
55
|
+
* Deliberately more forgiving than `presetActive`: the chip highlight requires
|
|
56
|
+
* an EXACT filter match (adding a channel filter un-highlights the chip), but
|
|
57
|
+
* the columns should keep following the status the operator is looking at.
|
|
58
|
+
* "Running jobs on the email channel" still wants the Running columns.
|
|
59
|
+
*/
|
|
60
|
+
export function jobSegmentOf(status: string | null, runAtIsNull: string | null): JobSegmentKey {
|
|
61
|
+
if (status === 'running') return 'running';
|
|
62
|
+
if (status === 'failed') return 'failed';
|
|
63
|
+
if (status === 'pending') return runAtIsNull === 'false' ? 'scheduled' : 'queued';
|
|
64
|
+
return 'all';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Shared cells ──────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/** "runs in 2h" / "3m ago" — `fmt.relative` only speaks the past tense. */
|
|
70
|
+
export function relativeWhen(value: number | null | undefined, fallback = '—'): string {
|
|
71
|
+
if (value == null) return fallback;
|
|
72
|
+
const diffSec = Math.round((value * 1000 - Date.now()) / 1000);
|
|
73
|
+
const ago = diffSec <= 0;
|
|
74
|
+
const magnitude = Math.abs(diffSec);
|
|
75
|
+
const text = magnitude < 60 ? `${magnitude}s`
|
|
76
|
+
: magnitude < 3600 ? `${Math.floor(magnitude / 60)}m`
|
|
77
|
+
: magnitude < 86400 ? `${Math.floor(magnitude / 3600)}h`
|
|
78
|
+
: `${Math.floor(magnitude / 86400)}d`;
|
|
79
|
+
return ago ? `${text} ago` : `in ${text}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Job identity: the id kept recognizable at both ends, channel + func under it. */
|
|
83
|
+
export function JobIdentity({ job }: { job: JobRow }) {
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
<div className="jobs-cell-id"><code>{fmt.truncateMiddle(job.id, 12)}</code></div>
|
|
87
|
+
<div className="jobs-cell-sub">
|
|
88
|
+
{job.channel} · {job.func ? fmt.truncateMiddle(job.func, 34, '…') : 'no function'}
|
|
89
|
+
</div>
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function JobStatusBadge({ status }: { status: string }) {
|
|
95
|
+
return (
|
|
96
|
+
<Badge tone={jobStatusTone(status)}>
|
|
97
|
+
<i className={`bi ${jobStatusIcon(status)}`} /> {status.toUpperCase()}
|
|
98
|
+
</Badge>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function RunnerCell({ runnerId }: { runnerId: string | null }) {
|
|
103
|
+
return runnerId
|
|
104
|
+
? <code className="jobs-id">{fmt.truncateMiddle(runnerId, 18)}</code>
|
|
105
|
+
: <span className="dim">—</span>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function AttemptCell({ job }: { job: JobRow }) {
|
|
109
|
+
return <span className="jobs-nums">{job.attempt} / {job.max_retries || '∞'}</span>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Column sets ───────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
const IDENTITY: Column<JobRow> = {
|
|
115
|
+
key: 'id', label: 'Job', sortable: false, hideable: false,
|
|
116
|
+
render: (row) => <JobIdentity job={row} />,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const STATUS: Column<JobRow> = {
|
|
120
|
+
key: 'status', label: 'Status', sortable: true,
|
|
121
|
+
render: (row) => <JobStatusBadge status={row.status} />,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const CHANNEL: Column<JobRow> = {
|
|
125
|
+
key: 'channel', label: 'Channel', sortable: true,
|
|
126
|
+
render: (row) => <Badge tone="muted">{row.channel}</Badge>,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const RUNNER: Column<JobRow> = {
|
|
130
|
+
key: 'runner_id', label: 'Runner', sortable: true,
|
|
131
|
+
render: (row) => <RunnerCell runnerId={row.runner_id} />,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const ATTEMPT: Column<JobRow> = {
|
|
135
|
+
key: 'attempt', label: 'Attempt', sortable: true, align: 'center',
|
|
136
|
+
render: (row) => <AttemptCell job={row} />,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const CREATED: Column<JobRow> = {
|
|
140
|
+
key: 'created', label: 'Created', sortable: true,
|
|
141
|
+
render: (row) => <span title={fmt.datetime(row.created)}>{fmt.relative(row.created)}</span>,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** `duration_ms` is a serializer EXTRA, not a column the server can sort on. */
|
|
145
|
+
const DURATION: Column<JobRow> = {
|
|
146
|
+
key: 'duration_ms', label: 'Duration', sortable: false, align: 'end',
|
|
147
|
+
render: (row) => (row.duration_ms > 0 ? fmt.duration(row.duration_ms) : <span className="dim">—</span>),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const COLUMNS: Record<JobSegmentKey, Column<JobRow>[]> = {
|
|
151
|
+
running: [
|
|
152
|
+
IDENTITY,
|
|
153
|
+
RUNNER,
|
|
154
|
+
STATUS,
|
|
155
|
+
{
|
|
156
|
+
key: 'started_at', label: 'Started', sortable: true,
|
|
157
|
+
render: (row) => (row.started_at == null
|
|
158
|
+
? <span className="dim">not yet claimed</span>
|
|
159
|
+
: <span title={fmt.datetime(row.started_at)}>{fmt.relative(row.started_at)}</span>),
|
|
160
|
+
},
|
|
161
|
+
ATTEMPT,
|
|
162
|
+
{
|
|
163
|
+
key: 'cancel_requested', label: 'Cancel', sortable: false, align: 'center',
|
|
164
|
+
render: (row) => (row.cancel_requested
|
|
165
|
+
? <Badge tone="warning">requested</Badge>
|
|
166
|
+
: <span className="dim">—</span>),
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
|
|
170
|
+
// web-mojo's `pending` set led with a `priority` badge. There is no such
|
|
171
|
+
// field — what a queued job actually has is an age and an expiry.
|
|
172
|
+
queued: [
|
|
173
|
+
IDENTITY,
|
|
174
|
+
CHANNEL,
|
|
175
|
+
{
|
|
176
|
+
key: 'created', label: 'Waiting', sortable: true,
|
|
177
|
+
render: (row) => <span title={fmt.datetime(row.created)}>{fmt.relative(row.created)}</span>,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
key: 'expires_at', label: 'Expires', sortable: true,
|
|
181
|
+
render: (row) => (row.expires_at == null
|
|
182
|
+
? <span className="dim">never</span>
|
|
183
|
+
: <span title={fmt.datetime(row.expires_at)}>{relativeWhen(row.expires_at)}</span>),
|
|
184
|
+
},
|
|
185
|
+
ATTEMPT,
|
|
186
|
+
],
|
|
187
|
+
|
|
188
|
+
scheduled: [
|
|
189
|
+
IDENTITY,
|
|
190
|
+
{
|
|
191
|
+
key: 'run_at', label: 'Runs at', sortable: true,
|
|
192
|
+
render: (row) => (
|
|
193
|
+
<div className="jobs-cell-stack">
|
|
194
|
+
<span>{fmt.datetime(row.run_at)}</span>
|
|
195
|
+
<span className="jobs-cell-sub">{relativeWhen(row.run_at)}</span>
|
|
196
|
+
</div>
|
|
197
|
+
),
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
// A pending job whose run_at has passed is not "late by a little" —
|
|
201
|
+
// nothing has picked it up, which is the point of showing it.
|
|
202
|
+
key: 'overdue', label: 'State', sortable: false, align: 'center',
|
|
203
|
+
render: (row) => (isOverdueJob(row)
|
|
204
|
+
? <Badge tone="warning"><i className="bi bi-exclamation-triangle" /> OVERDUE</Badge>
|
|
205
|
+
: <Badge tone="info">WAITING</Badge>),
|
|
206
|
+
},
|
|
207
|
+
CHANNEL,
|
|
208
|
+
CREATED,
|
|
209
|
+
],
|
|
210
|
+
|
|
211
|
+
failed: [
|
|
212
|
+
IDENTITY,
|
|
213
|
+
{
|
|
214
|
+
key: 'last_error', label: 'Error', sortable: false,
|
|
215
|
+
render: (row) => {
|
|
216
|
+
// Only the FIRST line: a traceback in a table cell is noise,
|
|
217
|
+
// and the full text is one click away in the detail modal.
|
|
218
|
+
const firstLine = (row.last_error || '').split('\n')[0] ?? '';
|
|
219
|
+
return (
|
|
220
|
+
<div className="jobs-cell-error" title={row.last_error || undefined}>
|
|
221
|
+
{firstLine ? fmt.truncate(firstLine, 90, '…') : 'Unknown error'}
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
ATTEMPT,
|
|
227
|
+
{
|
|
228
|
+
key: 'finished_at', label: 'Failed', sortable: true,
|
|
229
|
+
render: (row) => <span title={fmt.datetime(row.finished_at ?? row.modified)}>{fmt.relative(row.finished_at ?? row.modified)}</span>,
|
|
230
|
+
},
|
|
231
|
+
RUNNER,
|
|
232
|
+
],
|
|
233
|
+
|
|
234
|
+
all: [
|
|
235
|
+
IDENTITY,
|
|
236
|
+
CHANNEL,
|
|
237
|
+
STATUS,
|
|
238
|
+
{
|
|
239
|
+
key: 'run_at', label: 'Scheduled', sortable: true,
|
|
240
|
+
render: (row) => (row.run_at == null
|
|
241
|
+
? <span className="dim">—</span>
|
|
242
|
+
: (
|
|
243
|
+
<span className={isOverdueJob(row) ? 'jobs-text-warning' : undefined} title={fmt.datetime(row.run_at)}>
|
|
244
|
+
<i className={`bi ${isOverdueJob(row) ? 'bi-clock-history' : 'bi-clock'}`} /> {relativeWhen(row.run_at)}
|
|
245
|
+
</span>
|
|
246
|
+
)),
|
|
247
|
+
},
|
|
248
|
+
CREATED,
|
|
249
|
+
{
|
|
250
|
+
key: 'finished_at', label: 'Finished', sortable: true,
|
|
251
|
+
render: (row) => (row.finished_at == null
|
|
252
|
+
? <span className="dim">—</span>
|
|
253
|
+
: <span title={fmt.datetime(row.finished_at)}>{fmt.relative(row.finished_at)}</span>),
|
|
254
|
+
},
|
|
255
|
+
DURATION,
|
|
256
|
+
],
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export function jobColumns(segment: JobSegmentKey): Column<JobRow>[] {
|
|
260
|
+
return COLUMNS[segment] ?? COLUMNS.all;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ── Safe client export ────────────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Job payloads carry whatever the caller published (recipients, file paths,
|
|
267
|
+
* credentials someone put in a dict), metadata is equally open-ended, and
|
|
268
|
+
* `last_error` is a raw exception string. None of them belong in a file that
|
|
269
|
+
* leaves the browser, so the export is a CLIENT projection over the same
|
|
270
|
+
* filtered set rather than the server's `download_format`, which would ship
|
|
271
|
+
* every graph field.
|
|
272
|
+
*/
|
|
273
|
+
export function sanitizeJobRowForExport(row: JobRow): JobRow {
|
|
274
|
+
return { ...row, payload: {}, metadata: {}, last_error: '' };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export const JOB_EXPORTER = createSafeExporter<JobRow>({
|
|
278
|
+
endpoint: JobModel.endpoint,
|
|
279
|
+
filename: 'jobs',
|
|
280
|
+
sanitizeRow: sanitizeJobRowForExport,
|
|
281
|
+
fields: [
|
|
282
|
+
{ key: 'id' },
|
|
283
|
+
{ key: 'channel' },
|
|
284
|
+
{ key: 'func' },
|
|
285
|
+
{ key: 'status' },
|
|
286
|
+
{ key: 'runner_id' },
|
|
287
|
+
{ key: 'attempt' },
|
|
288
|
+
{ key: 'max_retries' },
|
|
289
|
+
{ key: 'cancel_requested' },
|
|
290
|
+
{ key: 'created' },
|
|
291
|
+
{ key: 'run_at' },
|
|
292
|
+
{ key: 'started_at' },
|
|
293
|
+
{ key: 'finished_at' },
|
|
294
|
+
{ key: 'expires_at' },
|
|
295
|
+
{ key: 'duration_ms' },
|
|
296
|
+
],
|
|
297
|
+
});
|