From 89f08372c66ccf8a8fcbb87525d784499fe916f8 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Mon, 6 Oct 2025 02:38:16 -0700 Subject: [PATCH] CloseAlert --- bin/core/src/api/write/alert.rs | 33 +++++ bin/core/src/api/write/mod.rs | 4 + client/core/rs/src/api/write/alert.rs | 22 ++++ client/core/rs/src/api/write/mod.rs | 2 + client/core/ts/src/responses.ts | 3 + client/core/ts/src/types.ts | 12 +- frontend/public/client/responses.d.ts | 1 + frontend/public/client/types.d.ts | 11 ++ frontend/src/components/alert/details.tsx | 147 ++++++++++++---------- 9 files changed, 168 insertions(+), 67 deletions(-) create mode 100644 bin/core/src/api/write/alert.rs create mode 100644 client/core/rs/src/api/write/alert.rs diff --git a/bin/core/src/api/write/alert.rs b/bin/core/src/api/write/alert.rs new file mode 100644 index 000000000..fc284dfc4 --- /dev/null +++ b/bin/core/src/api/write/alert.rs @@ -0,0 +1,33 @@ +use std::str::FromStr; + +use anyhow::{Context, anyhow}; +use database::mungos::mongodb::bson::{doc, oid::ObjectId}; +use komodo_client::{api::write::CloseAlert, entities::NoData}; +use reqwest::StatusCode; +use resolver_api::Resolve; +use serror::AddStatusCodeError; + +use crate::{api::write::WriteArgs, state::db_client}; + +impl Resolve for CloseAlert { + async fn resolve( + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> Result { + if !admin.admin { + return Err( + anyhow!("This call is admin only") + .status_code(StatusCode::FORBIDDEN), + ); + } + db_client() + .alerts + .update_one( + doc! { "_id": ObjectId::from_str(&self.id)? }, + doc! { "$set": { "resolved": true } }, + ) + .await + .context("Failed to close Alert on database")?; + Ok(NoData {}) + } +} diff --git a/bin/core/src/api/write/mod.rs b/bin/core/src/api/write/mod.rs index 9547e666a..fd5e24cd6 100644 --- a/bin/core/src/api/write/mod.rs +++ b/bin/core/src/api/write/mod.rs @@ -19,6 +19,7 @@ use crate::auth::auth_request; use super::Variant; mod action; +mod alert; mod alerter; mod build; mod builder; @@ -200,6 +201,9 @@ pub enum WriteRequest { CreateOnboardingKey(CreateOnboardingKey), UpdateOnboardingKey(UpdateOnboardingKey), DeleteOnboardingKey(DeleteOnboardingKey), + + // ==== ALERT ==== + CloseAlert(CloseAlert), } pub fn router() -> Router { diff --git a/client/core/rs/src/api/write/alert.rs b/client/core/rs/src/api/write/alert.rs new file mode 100644 index 000000000..3fa59bab2 --- /dev/null +++ b/client/core/rs/src/api/write/alert.rs @@ -0,0 +1,22 @@ +use derive_empty_traits::EmptyTraits; +use resolver_api::Resolve; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use crate::{api::write::KomodoWriteRequest, entities::NoData}; + +// + +/// **Admin only.** Close the Alert at the given id. +/// Response: [NoData] +#[typeshare] +#[derive( + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, +)] +#[empty_traits(KomodoWriteRequest)] +#[response(NoData)] +#[error(serror::Error)] +pub struct CloseAlert { + /// The id of the Alert to close. + pub id: String, +} diff --git a/client/core/rs/src/api/write/mod.rs b/client/core/rs/src/api/write/mod.rs index 6b1282533..3e329580b 100644 --- a/client/core/rs/src/api/write/mod.rs +++ b/client/core/rs/src/api/write/mod.rs @@ -1,4 +1,5 @@ mod action; +mod alert; mod alerter; mod api_key; mod build; @@ -19,6 +20,7 @@ mod user_group; mod variable; pub use action::*; +pub use alert::*; pub use alerter::*; pub use api_key::*; pub use build::*; diff --git a/client/core/ts/src/responses.ts b/client/core/ts/src/responses.ts index 224a60136..d73bbed5f 100644 --- a/client/core/ts/src/responses.ts +++ b/client/core/ts/src/responses.ts @@ -331,6 +331,9 @@ export type WriteResponses = { CreateOnboardingKey: Types.CreateOnboardingKeyResponse; UpdateOnboardingKey: Types.UpdateOnboardingKeyResponse; DeleteOnboardingKey: Types.DeleteOnboardingKeyResponse; + + // ==== ALERT ==== + CloseAlert: Types.NoData; }; export type ExecuteResponses = { diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index e150bf74a..7020e0e13 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -4603,6 +4603,15 @@ export interface CloneRepo { repo: string; } +/** + * **Admin only.** Close the Alert at the given id. + * Response: [NoData] + */ +export interface CloseAlert { + /** The id of the Alert to close. */ + id: string; +} + /** * Exports matching resources, and writes to the target sync's resource file. Response: [Update] * @@ -9139,7 +9148,8 @@ export type WriteRequest = | { type: "DeleteDockerRegistryAccount", params: DeleteDockerRegistryAccount } | { type: "CreateOnboardingKey", params: CreateOnboardingKey } | { type: "UpdateOnboardingKey", params: UpdateOnboardingKey } - | { type: "DeleteOnboardingKey", params: DeleteOnboardingKey }; + | { type: "DeleteOnboardingKey", params: DeleteOnboardingKey } + | { type: "CloseAlert", params: CloseAlert }; export type WsLoginMessage = | { type: "Jwt", params: { diff --git a/frontend/public/client/responses.d.ts b/frontend/public/client/responses.d.ts index ea7f40f48..e33f10030 100644 --- a/frontend/public/client/responses.d.ts +++ b/frontend/public/client/responses.d.ts @@ -246,6 +246,7 @@ export type WriteResponses = { CreateOnboardingKey: Types.CreateOnboardingKeyResponse; UpdateOnboardingKey: Types.UpdateOnboardingKeyResponse; DeleteOnboardingKey: Types.DeleteOnboardingKeyResponse; + CloseAlert: Types.NoData; }; export type ExecuteResponses = { StartContainer: Types.Update; diff --git a/frontend/public/client/types.d.ts b/frontend/public/client/types.d.ts index 04c1e5782..9720f0864 100644 --- a/frontend/public/client/types.d.ts +++ b/frontend/public/client/types.d.ts @@ -4484,6 +4484,14 @@ export interface CloneRepo { /** Id or name */ repo: string; } +/** + * **Admin only.** Close the Alert at the given id. + * Response: [NoData] + */ +export interface CloseAlert { + /** The id of the Alert to close. */ + id: string; +} /** * Exports matching resources, and writes to the target sync's resource file. Response: [Update] * @@ -9265,6 +9273,9 @@ export type WriteRequest = { } | { type: "DeleteOnboardingKey"; params: DeleteOnboardingKey; +} | { + type: "CloseAlert"; + params: CloseAlert; }; export type WsLoginMessage = { type: "Jwt"; diff --git a/frontend/src/components/alert/details.tsx b/frontend/src/components/alert/details.tsx index 86c9e2fe7..747367eed 100644 --- a/frontend/src/components/alert/details.tsx +++ b/frontend/src/components/alert/details.tsx @@ -1,13 +1,8 @@ import { ResourceLink } from "@components/resources/common"; -import { useRead } from "@lib/hooks"; +import { useInvalidate, useRead, useUser, useWrite } from "@lib/hooks"; import { UsableResource } from "@types"; import { Button } from "@ui/button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTrigger, -} from "@ui/dialog"; +import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "@ui/dialog"; import { useState } from "react"; import { AlertLevel } from "."; import { fmt_date_with_minutes } from "@lib/formatting"; @@ -18,6 +13,9 @@ import { } from "@lib/color"; import { MonacoEditor } from "@components/monaco"; import { Types } from "komodo_client"; +import { ConfirmButton } from "@components/util"; +import { X } from "lucide-react"; +import { useToast } from "@ui/use-toast"; export const AlertDetailsDialog = ({ id }: { id: string }) => { const [open, set] = useState(false); @@ -40,66 +38,83 @@ export const AlertDetailsDialogContent = ({ }: { alert: Types.Alert | undefined; onClose: () => void; -}) => ( - <> - {alert && ( - - {alert && ( - <> - - {alert && ( -
- -
- {fmt_date_with_minutes(new Date(alert.ts))} -
-
- )} -
- -
-
- {/** Alert type */} -
-
type:
{" "} - {alert.data.type} -
+}) => { + const { toast } = useToast(); + const isAdmin = useUser().data?.admin ?? false; + const inv = useInvalidate(); + const { mutate: close_alert, isPending: closePending } = useWrite( + "CloseAlert", + { + onSuccess: () => { + inv(["ListAlerts"], ["GetAlert"]); + toast({ title: "Closed alert." }); + onClose(); + }, + } + ); - {/** Resolved */} -
-
status:
{" "} -
- {alert.resolved ? "RESOLVED" : "OPEN"} -
-
+ if (!alert) return null; - {/** Level */} -
- level: -
-
+ return ( + + +
+ +
+ {fmt_date_with_minutes(new Date(alert.ts))} +
+
+
+ +
+
+ {/** Alert type */} +
+
type:
{" "} + {alert.data.type} +
- {/** Alert data */} - + {/** Resolved */} +
+
status:
{" "} +
+ {alert.resolved ? "RESOLVED" : "OPEN"}
- - - )} - - )} - -); +
+ + {/** Level */} +
+ level: +
+ + {isAdmin && ( + } + variant="destructive" + className="max-w-[120px]" + onClick={() => close_alert({ id: alert?._id?.$oid! })} + loading={closePending} + /> + )} +
+ + {/** Alert data */} + +
+
+
+ ); +};