feat(macos): show cron scheduler status
This commit is contained in:
@@ -10,6 +10,10 @@ final class CronJobsStore: ObservableObject {
|
|||||||
@Published var selectedJobId: String?
|
@Published var selectedJobId: String?
|
||||||
@Published var runEntries: [CronRunLogEntry] = []
|
@Published var runEntries: [CronRunLogEntry] = []
|
||||||
|
|
||||||
|
@Published var schedulerEnabled: Bool?
|
||||||
|
@Published var schedulerStorePath: String?
|
||||||
|
@Published var schedulerNextWakeAtMs: Int?
|
||||||
|
|
||||||
@Published var isLoadingJobs = false
|
@Published var isLoadingJobs = false
|
||||||
@Published var isLoadingRuns = false
|
@Published var isLoadingRuns = false
|
||||||
@Published var lastError: String?
|
@Published var lastError: String?
|
||||||
@@ -61,6 +65,11 @@ final class CronJobsStore: ObservableObject {
|
|||||||
defer { self.isLoadingJobs = false }
|
defer { self.isLoadingJobs = false }
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
if let status = try? await self.fetchCronStatus() {
|
||||||
|
self.schedulerEnabled = status.enabled
|
||||||
|
self.schedulerStorePath = status.storePath
|
||||||
|
self.schedulerNextWakeAtMs = status.nextWakeAtMs
|
||||||
|
}
|
||||||
let data = try await self.request(
|
let data = try await self.request(
|
||||||
method: "cron.list",
|
method: "cron.list",
|
||||||
params: ["includeDisabled": true])
|
params: ["includeDisabled": true])
|
||||||
@@ -205,5 +214,16 @@ final class CronJobsStore: ObservableObject {
|
|||||||
let rawParams = params?.reduce(into: [String: AnyCodable]()) { $0[$1.key] = AnyCodable($1.value) }
|
let rawParams = params?.reduce(into: [String: AnyCodable]()) { $0[$1.key] = AnyCodable($1.value) }
|
||||||
return try await GatewayConnection.shared.request(method: method, params: rawParams, timeoutMs: timeoutMs)
|
return try await GatewayConnection.shared.request(method: method, params: rawParams, timeoutMs: timeoutMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func fetchCronStatus() async throws -> CronStatusResponse {
|
||||||
|
let data = try await self.request(method: "cron.status", params: nil)
|
||||||
|
return try JSONDecoder().decode(CronStatusResponse.self, from: data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct CronStatusResponse: Decodable {
|
||||||
|
let enabled: Bool
|
||||||
|
let storePath: String
|
||||||
|
let jobs: Int
|
||||||
|
let nextWakeAtMs: Int?
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ struct CronSettings: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
self.header
|
self.header
|
||||||
|
self.schedulerBanner
|
||||||
self.content
|
self.content
|
||||||
Spacer(minLength: 0)
|
Spacer(minLength: 0)
|
||||||
}
|
}
|
||||||
@@ -57,6 +58,38 @@ struct CronSettings: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var schedulerBanner: some View {
|
||||||
|
Group {
|
||||||
|
if self.store.schedulerEnabled == false {
|
||||||
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Image(systemName: "exclamationmark.triangle.fill")
|
||||||
|
.foregroundStyle(.orange)
|
||||||
|
Text("Cron scheduler is disabled")
|
||||||
|
.font(.headline)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
Text("Jobs are saved, but they will not run automatically until `cron.enabled` is set to `true` and the Gateway restarts.")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
if let storePath = self.store.schedulerStorePath, !storePath.isEmpty {
|
||||||
|
Text(storePath)
|
||||||
|
.font(.caption.monospaced())
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
.lineLimit(1)
|
||||||
|
.truncationMode(.middle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(10)
|
||||||
|
.background(Color.orange.opacity(0.10))
|
||||||
|
.cornerRadius(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var header: some View {
|
private var header: some View {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
|||||||
Reference in New Issue
Block a user