CloseAlert

This commit is contained in:
mbecker20
2025-10-06 02:38:16 -07:00
parent 6a3ce2d426
commit 89f08372c6
9 changed files with 168 additions and 67 deletions
+33
View File
@@ -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<WriteArgs> for CloseAlert {
async fn resolve(
self,
WriteArgs { user: admin }: &WriteArgs,
) -> Result<Self::Response, Self::Error> {
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 {})
}
}
+4
View File
@@ -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 {
+22
View File
@@ -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,
}
+2
View File
@@ -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::*;
+3
View File
@@ -331,6 +331,9 @@ export type WriteResponses = {
CreateOnboardingKey: Types.CreateOnboardingKeyResponse;
UpdateOnboardingKey: Types.UpdateOnboardingKeyResponse;
DeleteOnboardingKey: Types.DeleteOnboardingKeyResponse;
// ==== ALERT ====
CloseAlert: Types.NoData;
};
export type ExecuteResponses = {
+11 -1
View File
@@ -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: {
+1
View File
@@ -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;
+11
View File
@@ -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";
+81 -66
View File
@@ -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 && (
<DialogContent className="w-[90vw] max-w-[900px]">
{alert && (
<>
<DialogHeader>
{alert && (
<div className="flex items-center gap-4">
<ResourceLink
type={alert.target.type as UsableResource}
id={alert.target.id}
onClick={onClose}
/>
<div className="text-muted-foreground">
{fmt_date_with_minutes(new Date(alert.ts))}
</div>
</div>
)}
</DialogHeader>
<DialogDescription>
<div className="flex flex-col gap-4">
<div className="flex gap-4 items-center">
{/** Alert type */}
<div className="flex gap-2">
<div className="text-muted-foreground">type:</div>{" "}
{alert.data.type}
</div>
}) => {
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 */}
<div className="flex gap-2">
<div className="text-muted-foreground">status:</div>{" "}
<div
className={text_color_class_by_intention(
alert.resolved
? "Good"
: alert_level_intention(alert.level)
)}
>
{alert.resolved ? "RESOLVED" : "OPEN"}
</div>
</div>
if (!alert) return null;
{/** Level */}
<div className="flex gap-2 text-muted-foreground">
level: <AlertLevel level={alert.level} />
</div>
</div>
return (
<DialogContent className="w-[90vw] max-w-[900px]">
<DialogHeader>
<div className="flex items-center gap-4">
<ResourceLink
type={alert.target.type as UsableResource}
id={alert.target.id}
onClick={onClose}
/>
<div className="text-muted-foreground">
{fmt_date_with_minutes(new Date(alert.ts))}
</div>
</div>
</DialogHeader>
<DialogDescription>
<div className="flex flex-col gap-4">
<div className="flex gap-4 items-center">
{/** Alert type */}
<div className="flex gap-2">
<div className="text-muted-foreground">type:</div>{" "}
{alert.data.type}
</div>
{/** Alert data */}
<MonacoEditor
value={JSON.stringify(alert.data.data, undefined, 2)}
language="json"
readOnly
/>
{/** Resolved */}
<div className="flex gap-2">
<div className="text-muted-foreground">status:</div>{" "}
<div
className={text_color_class_by_intention(
alert.resolved ? "Good" : alert_level_intention(alert.level)
)}
>
{alert.resolved ? "RESOLVED" : "OPEN"}
</div>
</DialogDescription>
</>
)}
</DialogContent>
)}
</>
);
</div>
{/** Level */}
<div className="flex gap-2 text-muted-foreground">
level: <AlertLevel level={alert.level} />
</div>
{isAdmin && (
<ConfirmButton
title="Close"
icon={<X className="w-4 h-4" />}
variant="destructive"
className="max-w-[120px]"
onClick={() => close_alert({ id: alert?._id?.$oid! })}
loading={closePending}
/>
)}
</div>
{/** Alert data */}
<MonacoEditor
value={JSON.stringify(alert.data.data, undefined, 2)}
language="json"
readOnly
/>
</div>
</DialogDescription>
</DialogContent>
);
};