forked from github-starred/komodo
1.10.2 ResourceSync manage repo webhooks
This commit is contained in:
26
Cargo.lock
generated
26
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -127,6 +127,7 @@ enum ReadRequest {
|
||||
GetResourceSyncsSummary(GetResourceSyncsSummary),
|
||||
GetResourceSync(GetResourceSync),
|
||||
GetResourceSyncActionState(GetResourceSyncActionState),
|
||||
GetSyncWebhooksEnabled(GetSyncWebhooksEnabled),
|
||||
ListResourceSyncs(ListResourceSyncs),
|
||||
ListFullResourceSyncs(ListFullResourceSyncs),
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,8 @@ pub enum WriteRequest {
|
||||
DeleteResourceSync(DeleteResourceSync),
|
||||
UpdateResourceSync(UpdateResourceSync),
|
||||
RefreshResourceSyncPending(RefreshResourceSyncPending),
|
||||
CreateSyncWebhook(CreateSyncWebhook),
|
||||
DeleteSyncWebhook(DeleteSyncWebhook),
|
||||
|
||||
// ==== TAG ====
|
||||
CreateTag(CreateTag),
|
||||
|
||||
@@ -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 {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user