1.10.2 ResourceSync manage repo webhooks

This commit is contained in:
mbecker20
2024-07-05 20:02:20 -07:00
parent e37fc6adde
commit fe82400a99
13 changed files with 581 additions and 35 deletions

26
Cargo.lock generated
View File

@@ -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",

View File

@@ -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 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"

View File

@@ -127,6 +127,7 @@ enum ReadRequest {
GetResourceSyncsSummary(GetResourceSyncsSummary),
GetResourceSync(GetResourceSync),
GetResourceSyncActionState(GetResourceSyncActionState),
GetSyncWebhooksEnabled(GetSyncWebhooksEnabled),
ListResourceSyncs(ListResourceSyncs),
ListFullResourceSyncs(ListFullResourceSyncs),

View File

@@ -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<GetResourceSync, User> for State {
@@ -137,3 +141,86 @@ impl Resolve<GetResourceSyncsSummary, User> for State {
Ok(res)
}
}
impl Resolve<GetSyncWebhooksEnabled, User> for State {
async fn resolve(
&self,
GetSyncWebhooksEnabled { sync }: GetSyncWebhooksEnabled,
user: User,
) -> anyhow::Result<GetSyncWebhooksEnabledResponse> {
let Some(github) = github_client() else {
return Ok(GetSyncWebhooksEnabledResponse {
managed: false,
refresh_enabled: false,
sync_enabled: false,
});
};
let sync = resource::get_check_permissions::<ResourceSync>(
&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,
})
}
}

View File

@@ -240,6 +240,7 @@ impl Resolve<DeleteBuildWebhook, User> for State {
}
}
Err(anyhow!("Didn't find any webhook to delete"))
// No webhook to delete, all good
Ok(NoData {})
}
}

View File

@@ -116,6 +116,8 @@ pub enum WriteRequest {
DeleteResourceSync(DeleteResourceSync),
UpdateResourceSync(UpdateResourceSync),
RefreshResourceSyncPending(RefreshResourceSyncPending),
CreateSyncWebhook(CreateSyncWebhook),
DeleteSyncWebhook(DeleteSyncWebhook),
// ==== TAG ====
CreateTag(CreateTag),

View File

@@ -255,6 +255,7 @@ impl Resolve<DeleteRepoWebhook, User> for State {
}
}
Err(anyhow!("Didn't find any webhook to delete"))
// No webhook to delete, all good
Ok(NoData {})
}
}

View File

@@ -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<CreateResourceSync, User> for State {
@@ -262,13 +268,11 @@ impl Resolve<RefreshResourceSyncPending, User> 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<RefreshResourceSyncPending, User> for State {
crate::resource::get::<ResourceSync>(&sync.id).await
}
}
impl Resolve<CreateSyncWebhook, User> for State {
#[instrument(name = "CreateSyncWebhook", skip(self, user))]
async fn resolve(
&self,
CreateSyncWebhook { sync, action }: CreateSyncWebhook,
user: User,
) -> anyhow::Result<CreateSyncWebhookResponse> {
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::<ResourceSync>(
&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<DeleteSyncWebhook, User> for State {
#[instrument(name = "DeleteSyncWebhook", skip(self, user))]
async fn resolve(
&self,
DeleteSyncWebhook { sync, action }: DeleteSyncWebhook,
user: User,
) -> anyhow::Result<DeleteSyncWebhookResponse> {
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::<ResourceSync>(
&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 {})
}
}

View File

@@ -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,
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 }

View File

@@ -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<Partial<Types.ResourceSyncConfig>>({});
@@ -59,19 +64,142 @@ export const ResourceSyncConfig = ({
},
},
{
label: "Github Webhook",
label: "Github Webhooks",
components: {
["refresh" as any]: () => (
<ConfigItem label="Refresh Pending">
<CopyGithubWebhook path={`/sync/${id}/refresh`} />
</ConfigItem>
),
["execute" as any]: () => (
["sync" as any]: () => (
<ConfigItem label="Execute Sync">
<CopyGithubWebhook path={`/sync/${id}/execute`} />
<CopyGithubWebhook path={`/sync/${id}/sync`} />
</ConfigItem>
),
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 (
<ConfigItem label="Manage Webhook">
{webhooks.sync_enabled && (
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
Incoming webhook is{" "}
<div
className={text_color_class_by_intention("Good")}
>
ENABLED
</div>
and will trigger
<div
className={text_color_class_by_intention("Neutral")}
>
SYNC EXECUTION
</div>
</div>
<ConfirmButton
title="Disable"
icon={<Ban className="w-4 h-4" />}
variant="destructive"
onClick={() =>
deleteWebhook({
sync: id,
action: Types.SyncWebhookAction.Sync,
})
}
loading={deletePending}
disabled={disabled || deletePending}
/>
</div>
)}
{!webhooks.sync_enabled && webhooks.refresh_enabled && (
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
Incoming webhook is{" "}
<div
className={text_color_class_by_intention("Good")}
>
ENABLED
</div>
and will trigger
<div
className={text_color_class_by_intention("Neutral")}
>
PENDING REFRESH
</div>
</div>
<ConfirmButton
title="Disable"
icon={<Ban className="w-4 h-4" />}
variant="destructive"
onClick={() =>
deleteWebhook({
sync: id,
action: Types.SyncWebhookAction.Refresh,
})
}
loading={deletePending}
disabled={disabled || deletePending}
/>
</div>
)}
{!webhooks.sync_enabled && !webhooks.refresh_enabled && (
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
Incoming webhook is{" "}
<div
className={text_color_class_by_intention(
"Critical"
)}
>
DISABLED
</div>
</div>
<ConfirmButton
title="Enable for Refresh"
icon={<CirclePlus className="w-4 h-4" />}
onClick={() =>
createWebhook({
sync: id,
action: Types.SyncWebhookAction.Refresh,
})
}
loading={createPending}
disabled={disabled || createPending}
/>
<ConfirmButton
title="Enable for Sync"
icon={<CirclePlus className="w-4 h-4" />}
onClick={() =>
createWebhook({
sync: id,
action: Types.SyncWebhookAction.Sync,
})
}
loading={createPending}
disabled={disabled || createPending}
/>
</div>
)}
</ConfigItem>
);
},
},
},
],