From fe82400a992af0500de265639fddc4925494353d Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Fri, 5 Jul 2024 20:02:20 -0700 Subject: [PATCH] 1.10.2 ResourceSync manage repo webhooks --- Cargo.lock | 26 +-- Cargo.toml | 2 +- bin/core/src/api/read/mod.rs | 1 + bin/core/src/api/read/sync.rs | 89 +++++++- bin/core/src/api/write/build.rs | 3 +- bin/core/src/api/write/mod.rs | 2 + bin/core/src/api/write/repo.rs | 3 +- bin/core/src/api/write/sync.rs | 215 +++++++++++++++++- client/core/rs/src/api/read/sync.rs | 28 +++ client/core/rs/src/api/write/sync.rs | 53 ++++- client/core/ts/src/responses.ts | 3 + client/core/ts/src/types.ts | 53 +++++ .../resources/resource-sync/config.tsx | 138 ++++++++++- 13 files changed, 581 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2a2d1f0c..5a5321bb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ dependencies = [ [[package]] name = "alerter" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "axum 0.7.5", @@ -967,7 +967,7 @@ dependencies = [ [[package]] name = "command" -version = "1.10.1" +version = "1.10.2" dependencies = [ "monitor_client", "run_command", @@ -1351,7 +1351,7 @@ dependencies = [ [[package]] name = "formatting" -version = "1.10.1" +version = "1.10.2" dependencies = [ "serror", ] @@ -1482,7 +1482,7 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "command", @@ -2083,7 +2083,7 @@ dependencies = [ [[package]] name = "logger" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "monitor_client", @@ -2152,7 +2152,7 @@ dependencies = [ [[package]] name = "migrator" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "chrono", @@ -2286,7 +2286,7 @@ dependencies = [ [[package]] name = "monitor_cli" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "clap", @@ -2306,7 +2306,7 @@ dependencies = [ [[package]] name = "monitor_client" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "async_timing_util", @@ -2338,7 +2338,7 @@ dependencies = [ [[package]] name = "monitor_core" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "async_timing_util", @@ -2394,7 +2394,7 @@ dependencies = [ [[package]] name = "monitor_periphery" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "async_timing_util", @@ -2838,7 +2838,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "periphery_client" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "monitor_client", @@ -3977,7 +3977,7 @@ dependencies = [ [[package]] name = "tests" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "dotenv", @@ -4579,7 +4579,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "update_logger" -version = "1.10.1" +version = "1.10.2" dependencies = [ "anyhow", "logger", diff --git a/Cargo.toml b/Cargo.toml index c43168d52..77124371f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"] [workspace.package] -version = "1.10.1" +version = "1.10.2" edition = "2021" authors = ["mbecker20 "] license = "GPL-3.0-or-later" diff --git a/bin/core/src/api/read/mod.rs b/bin/core/src/api/read/mod.rs index 5f05fa19f..1c3dc6b07 100644 --- a/bin/core/src/api/read/mod.rs +++ b/bin/core/src/api/read/mod.rs @@ -127,6 +127,7 @@ enum ReadRequest { GetResourceSyncsSummary(GetResourceSyncsSummary), GetResourceSync(GetResourceSync), GetResourceSyncActionState(GetResourceSyncActionState), + GetSyncWebhooksEnabled(GetSyncWebhooksEnabled), ListResourceSyncs(ListResourceSyncs), ListFullResourceSyncs(ListFullResourceSyncs), diff --git a/bin/core/src/api/read/sync.rs b/bin/core/src/api/read/sync.rs index 006b1030f..fb9ea8e84 100644 --- a/bin/core/src/api/read/sync.rs +++ b/bin/core/src/api/read/sync.rs @@ -2,6 +2,7 @@ use anyhow::Context; use monitor_client::{ api::read::*, entities::{ + config::core::CoreConfig, permission::PermissionLevel, sync::{ PendingSyncUpdatesData, ResourceSync, ResourceSyncActionState, @@ -13,8 +14,11 @@ use monitor_client::{ use resolver_api::Resolve; use crate::{ + config::core_config, resource, - state::{action_states, resource_sync_state_cache, State}, + state::{ + action_states, github_client, resource_sync_state_cache, State, + }, }; impl Resolve for State { @@ -137,3 +141,86 @@ impl Resolve for State { Ok(res) } } + +impl Resolve for State { + async fn resolve( + &self, + GetSyncWebhooksEnabled { sync }: GetSyncWebhooksEnabled, + user: User, + ) -> anyhow::Result { + let Some(github) = github_client() else { + return Ok(GetSyncWebhooksEnabledResponse { + managed: false, + refresh_enabled: false, + sync_enabled: false, + }); + }; + + let sync = resource::get_check_permissions::( + &sync, + &user, + PermissionLevel::Read, + ) + .await?; + + if sync.config.repo.is_empty() { + return Ok(GetSyncWebhooksEnabledResponse { + managed: false, + refresh_enabled: false, + sync_enabled: false, + }); + } + + let mut split = sync.config.repo.split('/'); + let owner = split.next().context("Sync repo has no owner")?; + + let CoreConfig { + host, + github_webhook_base_url, + github_webhook_app, + .. + } = core_config(); + + if !github_webhook_app.owners.iter().any(|o| o == owner) { + return Ok(GetSyncWebhooksEnabledResponse { + managed: false, + refresh_enabled: false, + sync_enabled: false, + }); + } + + let repo_name = + split.next().context("Repo repo has no repo after the /")?; + + let github_repos = github.repos(); + let webhooks = github_repos + .list_all_webhooks(owner, repo_name) + .await + .context("failed to list all webhooks on repo")? + .body; + + let host = github_webhook_base_url.as_ref().unwrap_or(host); + let refresh_url = + format!("{host}/listener/github/sync/{}/refresh", sync.id); + let sync_url = + format!("{host}/listener/github/sync/{}/sync", sync.id); + + let mut refresh_enabled = false; + let mut sync_enabled = false; + + for webhook in webhooks { + if webhook.active && webhook.config.url == refresh_url { + refresh_enabled = true + } + if webhook.active && webhook.config.url == sync_url { + sync_enabled = true + } + } + + Ok(GetSyncWebhooksEnabledResponse { + managed: true, + refresh_enabled, + sync_enabled, + }) + } +} diff --git a/bin/core/src/api/write/build.rs b/bin/core/src/api/write/build.rs index f5a1b32a5..a5cbcac66 100644 --- a/bin/core/src/api/write/build.rs +++ b/bin/core/src/api/write/build.rs @@ -240,6 +240,7 @@ impl Resolve for State { } } - Err(anyhow!("Didn't find any webhook to delete")) + // No webhook to delete, all good + Ok(NoData {}) } } diff --git a/bin/core/src/api/write/mod.rs b/bin/core/src/api/write/mod.rs index a9b4cdec4..313bde4eb 100644 --- a/bin/core/src/api/write/mod.rs +++ b/bin/core/src/api/write/mod.rs @@ -116,6 +116,8 @@ pub enum WriteRequest { DeleteResourceSync(DeleteResourceSync), UpdateResourceSync(UpdateResourceSync), RefreshResourceSyncPending(RefreshResourceSyncPending), + CreateSyncWebhook(CreateSyncWebhook), + DeleteSyncWebhook(DeleteSyncWebhook), // ==== TAG ==== CreateTag(CreateTag), diff --git a/bin/core/src/api/write/repo.rs b/bin/core/src/api/write/repo.rs index 02ba4c9a6..66a35cec5 100644 --- a/bin/core/src/api/write/repo.rs +++ b/bin/core/src/api/write/repo.rs @@ -255,6 +255,7 @@ impl Resolve for State { } } - Err(anyhow!("Didn't find any webhook to delete")) + // No webhook to delete, all good + Ok(NoData {}) } } diff --git a/bin/core/src/api/write/sync.rs b/bin/core/src/api/write/sync.rs index 041c79917..d8bbc847e 100644 --- a/bin/core/src/api/write/sync.rs +++ b/bin/core/src/api/write/sync.rs @@ -8,6 +8,7 @@ use monitor_client::{ alerter::Alerter, build::Build, builder::Builder, + config::core::CoreConfig, monitor_timestamp, permission::PermissionLevel, procedure::Procedure, @@ -15,21 +16,26 @@ use monitor_client::{ server::{stats::SeverityLevel, Server}, server_template::ServerTemplate, sync::{ - PendingSyncUpdates, PendingSyncUpdatesData, - PendingSyncUpdatesDataErr, PendingSyncUpdatesDataOk, - ResourceSync, + PartialResourceSyncConfig, PendingSyncUpdates, + PendingSyncUpdatesData, PendingSyncUpdatesDataErr, + PendingSyncUpdatesDataOk, ResourceSync, }, update::ResourceTarget, user::User, + NoData, }, }; use mungos::{ by_id::update_one_by_id, mongodb::bson::{doc, to_document}, }; +use octorust::types::{ + ReposCreateWebhookRequest, ReposCreateWebhookRequestConfig, +}; use resolver_api::Resolve; use crate::{ + config::core_config, helpers::{ alert::send_alerts, query::get_id_to_tags, @@ -39,7 +45,7 @@ use crate::{ }, }, resource, - state::{db_client, State}, + state::{db_client, github_client, State}, }; impl Resolve for State { @@ -262,13 +268,11 @@ impl Resolve for State { let Some(existing) = db_client() .await .alerts - .find_one( - doc! { - "resolved": false, - "target.type": "ResourceSync", - "target.id": &id, - }, - ) + .find_one(doc! { + "resolved": false, + "target.type": "ResourceSync", + "target.id": &id, + }) .await .context("failed to query db for alert") .inspect_err(|e| warn!("{e:#}")) @@ -322,3 +326,192 @@ impl Resolve for State { crate::resource::get::(&sync.id).await } } + +impl Resolve for State { + #[instrument(name = "CreateSyncWebhook", skip(self, user))] + async fn resolve( + &self, + CreateSyncWebhook { sync, action }: CreateSyncWebhook, + user: User, + ) -> anyhow::Result { + let Some(github) = github_client() else { + return Err(anyhow!( + "github_webhook_app is not configured in core config toml" + )); + }; + + let sync = resource::get_check_permissions::( + &sync, + &user, + PermissionLevel::Write, + ) + .await?; + + if sync.config.repo.is_empty() { + return Err(anyhow!( + "No repo configured, can't create webhook" + )); + } + + let mut split = sync.config.repo.split('/'); + let owner = split.next().context("Sync repo has no owner")?; + + let CoreConfig { + host, + github_webhook_base_url, + github_webhook_app, + github_webhook_secret, + .. + } = core_config(); + + if !github_webhook_app.owners.iter().any(|o| o == owner) { + return Err(anyhow!( + "Cannot manage repo webhooks under owner {owner}" + )); + } + + let repo = + split.next().context("Repo repo has no repo after the /")?; + + let github_repos = github.repos(); + + // First make sure the webhook isn't already created (inactive ones are ignored) + let webhooks = github_repos + .list_all_webhooks(owner, repo) + .await + .context("failed to list all webhooks on repo")? + .body; + + let host = github_webhook_base_url.as_ref().unwrap_or(host); + let url = match action { + SyncWebhookAction::Refresh => { + format!("{host}/listener/github/sync/{}/refresh", sync.id) + } + SyncWebhookAction::Sync => { + format!("{host}/listener/github/sync/{}/sync", sync.id) + } + }; + + for webhook in webhooks { + if webhook.active && webhook.config.url == url { + return Ok(NoData {}); + } + } + + // Now good to create the webhook + let request = ReposCreateWebhookRequest { + active: Some(true), + config: Some(ReposCreateWebhookRequestConfig { + url, + secret: github_webhook_secret.to_string(), + content_type: String::from("json"), + insecure_ssl: None, + digest: Default::default(), + token: Default::default(), + }), + events: vec![String::from("push")], + name: String::from("web"), + }; + github_repos + .create_webhook(owner, repo, &request) + .await + .context("failed to create webhook")?; + + if !sync.config.webhook_enabled { + self + .resolve( + UpdateResourceSync { + id: sync.id, + config: PartialResourceSyncConfig { + webhook_enabled: Some(true), + ..Default::default() + }, + }, + user, + ) + .await + .context("failed to update sync to enable webhook")?; + } + + Ok(NoData {}) + } +} + +impl Resolve for State { + #[instrument(name = "DeleteSyncWebhook", skip(self, user))] + async fn resolve( + &self, + DeleteSyncWebhook { sync, action }: DeleteSyncWebhook, + user: User, + ) -> anyhow::Result { + let Some(github) = github_client() else { + return Err(anyhow!( + "github_webhook_app is not configured in core config toml" + )); + }; + + let sync = resource::get_check_permissions::( + &sync, + &user, + PermissionLevel::Write, + ) + .await?; + + if sync.config.repo.is_empty() { + return Err(anyhow!( + "No repo configured, can't create webhook" + )); + } + + let mut split = sync.config.repo.split('/'); + let owner = split.next().context("Sync repo has no owner")?; + + let CoreConfig { + host, + github_webhook_base_url, + github_webhook_app, + .. + } = core_config(); + + if !github_webhook_app.owners.iter().any(|o| o == owner) { + return Err(anyhow!( + "Cannot manage repo webhooks under owner {owner}" + )); + } + + let repo = + split.next().context("Sync repo has no repo after the /")?; + + let github_repos = github.repos(); + + // First make sure the webhook isn't already created (inactive ones are ignored) + let webhooks = github_repos + .list_all_webhooks(owner, repo) + .await + .context("failed to list all webhooks on repo")? + .body; + + let host = github_webhook_base_url.as_ref().unwrap_or(host); + let url = match action { + SyncWebhookAction::Refresh => { + format!("{host}/listener/github/sync/{}/refresh", sync.id) + } + SyncWebhookAction::Sync => { + format!("{host}/listener/github/sync/{}/sync", sync.id) + } + }; + + for webhook in webhooks { + if webhook.active && webhook.config.url == url { + github_repos + .delete_webhook(owner, repo, webhook.id) + .await + .context("failed to delete webhook")?; + return Ok(NoData {}); + } + } + + // No webhook to delete, all good + Ok(NoData {}) + } +} diff --git a/client/core/rs/src/api/read/sync.rs b/client/core/rs/src/api/read/sync.rs index de67757d7..e6e452ef2 100644 --- a/client/core/rs/src/api/read/sync.rs +++ b/client/core/rs/src/api/read/sync.rs @@ -111,3 +111,31 @@ pub struct GetResourceSyncsSummaryResponse { /// The number of syncs with unknown state. pub unknown: u32, } + +// + +/// Get a target Sync's configured webhooks. Response: [GetSyncWebhooksEnabledResponse]. +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, +)] +#[empty_traits(MonitorReadRequest)] +#[response(GetSyncWebhooksEnabledResponse)] +pub struct GetSyncWebhooksEnabled { + /// Id or name + #[serde(alias = "id", alias = "name")] + pub sync: String, +} + +/// Response for [GetSyncWebhooksEnabled] +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetSyncWebhooksEnabledResponse { + /// Whether the repo webhooks can even be managed. + /// The repo owner must be in `github_webhook_app.owners` list to be managed. + pub managed: bool, + /// Whether pushes to branch trigger refresh. Will always be false if managed is false. + pub refresh_enabled: bool, + /// Whether pushes to branch trigger sync execution. Will always be false if managed is false. + pub sync_enabled: bool, +} diff --git a/client/core/rs/src/api/write/sync.rs b/client/core/rs/src/api/write/sync.rs index 29e7956d9..3c0bf6ef6 100644 --- a/client/core/rs/src/api/write/sync.rs +++ b/client/core/rs/src/api/write/sync.rs @@ -3,9 +3,9 @@ use resolver_api::derive::Request; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::sync::{ +use crate::entities::{sync::{ ResourceSync, _PartialResourceSyncConfig, -}; +}, NoData}; use super::MonitorWriteRequest; @@ -96,3 +96,52 @@ pub struct RefreshResourceSyncPending { /// Id or name pub sync: String, } + +// + +#[typeshare] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum SyncWebhookAction { + Refresh, + Sync, +} + +/// Create a webhook on the github repo attached to the sync +/// passed in request. Response: [CreateSyncWebhookResponse] +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, +)] +#[empty_traits(MonitorWriteRequest)] +#[response(CreateSyncWebhookResponse)] +pub struct CreateSyncWebhook { + /// Id or name + #[serde(alias = "id", alias = "name")] + pub sync: String, + /// "Refresh" or "Sync" + pub action: SyncWebhookAction, +} + +#[typeshare] +pub type CreateSyncWebhookResponse = NoData; + +// + +/// Delete the webhook on the github repo attached to the sync +/// passed in request. Response: [DeleteSyncWebhookResponse] +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, +)] +#[empty_traits(MonitorWriteRequest)] +#[response(DeleteSyncWebhookResponse)] +pub struct DeleteSyncWebhook { + /// Id or name + #[serde(alias = "id", alias = "name")] + pub sync: String, + /// "Refresh" or "Sync" + pub action: SyncWebhookAction, +} + +#[typeshare] +pub type DeleteSyncWebhookResponse = NoData; diff --git a/client/core/ts/src/responses.ts b/client/core/ts/src/responses.ts index 8d0832378..12d87d300 100644 --- a/client/core/ts/src/responses.ts +++ b/client/core/ts/src/responses.ts @@ -115,6 +115,7 @@ export type ReadResponses = { GetResourceSyncsSummary: Types.GetResourceSyncsSummaryResponse; GetResourceSync: Types.GetResourceSyncResponse; GetResourceSyncActionState: Types.GetResourceSyncActionStateResponse; + GetSyncWebhooksEnabled: Types.GetSyncWebhooksEnabledResponse; ListResourceSyncs: Types.ListResourceSyncsResponse; ListFullResourceSyncs: Types.ListFullResourceSyncsResponse; @@ -227,6 +228,8 @@ export type WriteResponses = { DeleteResourceSync: Types.ResourceSync; UpdateResourceSync: Types.ResourceSync; RefreshResourceSyncPending: Types.ResourceSync; + CreateSyncWebhook: Types.CreateSyncWebhookResponse; + DeleteSyncWebhook: Types.DeleteSyncWebhookResponse; // ==== TAG ==== CreateTag: Types.Tag; diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index ffad41c34..5c5d27761 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -1542,6 +1542,10 @@ export type CreateRepoWebhookResponse = NoData; export type DeleteRepoWebhookResponse = NoData; +export type CreateSyncWebhookResponse = NoData; + +export type DeleteSyncWebhookResponse = NoData; + export type UpdateTagsOnResourceResponse = NoData; export type CreateServiceUserResponse = User; @@ -2793,6 +2797,25 @@ export interface GetResourceSyncsSummaryResponse { unknown: number; } +/** Get a target Sync's configured webhooks. Response: [GetSyncWebhooksEnabledResponse]. */ +export interface GetSyncWebhooksEnabled { + /** Id or name */ + sync: string; +} + +/** Response for [GetSyncWebhooksEnabled] */ +export interface GetSyncWebhooksEnabledResponse { + /** + * Whether the repo webhooks can even be managed. + * The repo owner must be in `github_webhook_app.owners` list to be managed. + */ + managed: boolean; + /** Whether pushes to branch trigger refresh. Will always be false if managed is false. */ + refresh_enabled: boolean; + /** Whether pushes to branch trigger sync execution. Will always be false if managed is false. */ + sync_enabled: boolean; +} + /** Get data for a specific tag. Response [Tag]. */ export interface GetTag { /** Id or name */ @@ -3592,6 +3615,33 @@ export interface RefreshResourceSyncPending { sync: string; } +export enum SyncWebhookAction { + Refresh = "Refresh", + Sync = "Sync", +} + +/** + * Create a webhook on the github repo attached to the sync + * passed in request. Response: [CreateSyncWebhookResponse] + */ +export interface CreateSyncWebhook { + /** Id or name */ + sync: string; + /** "Refresh" or "Sync" */ + action: SyncWebhookAction; +} + +/** + * Delete the webhook on the github repo attached to the sync + * passed in request. Response: [DeleteSyncWebhookResponse] + */ +export interface DeleteSyncWebhook { + /** Id or name */ + sync: string; + /** "Refresh" or "Sync" */ + action: SyncWebhookAction; +} + /** Create a tag. Response: [Tag]. */ export interface CreateTag { /** The name of the tag. */ @@ -4099,6 +4149,7 @@ export type ReadRequest = | { type: "GetResourceSyncsSummary", params: GetResourceSyncsSummary } | { type: "GetResourceSync", params: GetResourceSync } | { type: "GetResourceSyncActionState", params: GetResourceSyncActionState } + | { type: "GetSyncWebhooksEnabled", params: GetSyncWebhooksEnabled } | { type: "ListResourceSyncs", params: ListResourceSyncs } | { type: "ListFullResourceSyncs", params: ListFullResourceSyncs } | { type: "GetBuildersSummary", params: GetBuildersSummary } @@ -4188,6 +4239,8 @@ export type WriteRequest = | { type: "DeleteResourceSync", params: DeleteResourceSync } | { type: "UpdateResourceSync", params: UpdateResourceSync } | { type: "RefreshResourceSyncPending", params: RefreshResourceSyncPending } + | { type: "CreateSyncWebhook", params: CreateSyncWebhook } + | { type: "DeleteSyncWebhook", params: DeleteSyncWebhook } | { type: "CreateTag", params: CreateTag } | { type: "DeleteTag", params: DeleteTag } | { type: "RenameTag", params: RenameTag } diff --git a/frontend/src/components/resources/resource-sync/config.tsx b/frontend/src/components/resources/resource-sync/config.tsx index 2b43b37ed..f3189dda7 100644 --- a/frontend/src/components/resources/resource-sync/config.tsx +++ b/frontend/src/components/resources/resource-sync/config.tsx @@ -1,9 +1,13 @@ import { Config } from "@components/config"; import { AccountSelectorConfig, ConfigItem } from "@components/config/util"; -import { useRead, useWrite } from "@lib/hooks"; +import { useInvalidate, useRead, useWrite } from "@lib/hooks"; import { Types } from "@monitor/client"; import { ReactNode, useState } from "react"; import { CopyGithubWebhook } from "../common"; +import { useToast } from "@ui/use-toast"; +import { text_color_class_by_intention } from "@lib/color"; +import { ConfirmButton } from "@components/util"; +import { Ban, CirclePlus } from "lucide-react"; export const ResourceSyncConfig = ({ id, @@ -16,6 +20,7 @@ export const ResourceSyncConfig = ({ target: { type: "ResourceSync", id }, }).data; const config = useRead("GetResourceSync", { sync: id }).data?.config; + const webhooks = useRead("GetSyncWebhooksEnabled", { sync: id }).data; const global_disabled = useRead("GetCoreInfo", {}).data?.ui_write_disabled ?? false; const [update, set] = useState>({}); @@ -59,19 +64,142 @@ export const ResourceSyncConfig = ({ }, }, { - label: "Github Webhook", + label: "Github Webhooks", components: { ["refresh" as any]: () => ( ), - ["execute" as any]: () => ( + ["sync" as any]: () => ( - + ), - webhook_enabled: true, + webhook_enabled: webhooks !== undefined && !webhooks.managed, + ["managed" as any]: () => { + const inv = useInvalidate(); + const { toast } = useToast(); + const { mutate: createWebhook, isPending: createPending } = + useWrite("CreateSyncWebhook", { + onSuccess: () => { + toast({ title: "Webhook Created" }); + inv(["GetSyncWebhooksEnabled", { sync: id }]); + }, + }); + const { mutate: deleteWebhook, isPending: deletePending } = + useWrite("DeleteSyncWebhook", { + onSuccess: () => { + toast({ title: "Webhook Deleted" }); + inv(["GetSyncWebhooksEnabled", { sync: id }]); + }, + }); + if (!webhooks || !webhooks.managed) return; + return ( + + {webhooks.sync_enabled && ( +
+
+ Incoming webhook is{" "} +
+ ENABLED +
+ and will trigger +
+ SYNC EXECUTION +
+
+ } + variant="destructive" + onClick={() => + deleteWebhook({ + sync: id, + action: Types.SyncWebhookAction.Sync, + }) + } + loading={deletePending} + disabled={disabled || deletePending} + /> +
+ )} + {!webhooks.sync_enabled && webhooks.refresh_enabled && ( +
+
+ Incoming webhook is{" "} +
+ ENABLED +
+ and will trigger +
+ PENDING REFRESH +
+
+ } + variant="destructive" + onClick={() => + deleteWebhook({ + sync: id, + action: Types.SyncWebhookAction.Refresh, + }) + } + loading={deletePending} + disabled={disabled || deletePending} + /> +
+ )} + {!webhooks.sync_enabled && !webhooks.refresh_enabled && ( +
+
+ Incoming webhook is{" "} +
+ DISABLED +
+
+ } + onClick={() => + createWebhook({ + sync: id, + action: Types.SyncWebhookAction.Refresh, + }) + } + loading={createPending} + disabled={disabled || createPending} + /> + } + onClick={() => + createWebhook({ + sync: id, + action: Types.SyncWebhookAction.Sync, + }) + } + loading={createPending} + disabled={disabled || createPending} + /> +
+ )} +
+ ); + }, }, }, ],