feat(cron): blueprint start-from + delivery target picker in job editor (issue #780) (#822)

- New-job dialog gains a Start-from dropdown: pick a catalog blueprint, fill its typed slots (time/enum/weekdays/text), save instantiates it via POST /api/cron/blueprints/instantiate
- Delivery field is now an editable picker fed by GET /api/cron/delivery-targets (origin + local + connected platforms)
- Blueprints and targets fetched on editor open; 422 slot errors surface the backend detail

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-05 22:44:13 +03:00
1 parent 80be168801
commit 442e868ce2
6 files changed
+740 -118

No files matched your search

@@ -0,0 +1,58 @@
package com.m57.hermescontrol.data.model
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
@Serializable
data class CronBlueprintListResponse(
val blueprints: List<CronBlueprint> = emptyList(),
)
@Serializable
data class CronBlueprint(
val key: String,
val title: String = "",
val description: String = "",
val category: String = "",
val tags: List<String> = emptyList(),
val fields: List<CronBlueprintField> = emptyList(),
val schedule: String = "",
val scheduleHuman: String = "",
val command: String = "",
val appUrl: String = "",
)
@Serializable
data class CronBlueprintField(
val name: String,
val type: String = "text",
val label: String = "",
val default: JsonElement? = null,
val options: List<String> = emptyList(),
val optional: Boolean = false,
val strict: Boolean = true,
val help: String = "",
) {
val defaultText: String
get() = (default as? JsonPrimitive)?.content.orEmpty()
}
@Serializable
data class DeliveryTargetsResponse(
val targets: List<DeliveryTarget> = emptyList(),
)
@Serializable
data class DeliveryTarget(
val id: String,
val name: String = "",
val home_target_set: Boolean = false,
val home_env_var: String? = null,
)
@Serializable
data class InstantiateBlueprintRequest(
val blueprint: String,
val values: Map<String, String>,
)
@@ -19,10 +19,12 @@ import com.m57.hermescontrol.data.model.CreateProfileRequest
import com.m57.hermescontrol.data.model.CreateTaskBody
import com.m57.hermescontrol.data.model.CreateWebhookRequest
import com.m57.hermescontrol.data.model.CredentialPoolResponse
import com.m57.hermescontrol.data.model.CronBlueprintListResponse
import com.m57.hermescontrol.data.model.CronJob
import com.m57.hermescontrol.data.model.CuratorResponse
import com.m57.hermescontrol.data.model.DebugShareResponse
import com.m57.hermescontrol.data.model.DeleteWebhookResponse
import com.m57.hermescontrol.data.model.DeliveryTargetsResponse
import com.m57.hermescontrol.data.model.DoctorResponse
import com.m57.hermescontrol.data.model.EnvVarConfig
import com.m57.hermescontrol.data.model.EnvVarDeleteRequest
@@ -31,6 +33,7 @@ import com.m57.hermescontrol.data.model.EnvVarRevealResponse
import com.m57.hermescontrol.data.model.EnvVarUpdate
import com.m57.hermescontrol.data.model.HealthStatus
import com.m57.hermescontrol.data.model.HookResponse
import com.m57.hermescontrol.data.model.InstantiateBlueprintRequest
import com.m57.hermescontrol.data.model.KanbanBoardResponse
import com.m57.hermescontrol.data.model.KanbanBoardsResponse
import com.m57.hermescontrol.data.model.LearningGraphResponse
@@ -308,6 +311,17 @@ interface HermesApiService {
@Path("id") id: String,
): Response<Unit>
@GET("api/cron/blueprints")
suspend fun getCronBlueprints(): Response<CronBlueprintListResponse>
@POST("api/cron/blueprints/instantiate")
suspend fun instantiateBlueprint(
@Body body: InstantiateBlueprintRequest,
): Response<CronJob>
@GET("api/cron/delivery-targets")
suspend fun getCronDeliveryTargets(): Response<DeliveryTargetsResponse>
@POST("api/gateway/start")
suspend fun startGateway(): Response<Unit>