use std::collections::HashMap; use std::fmt::Display; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; #[cfg(feature = "schemars")] use schemars::JsonSchema; use crate::api::rules::contactgroups::HostContactGroups; use crate::api::rules::snmp::SnmpCommunity; use crate::api::{DomainExtension, domain_bulk_read, domain_bulk_update, domain_client, domain_create, domain_delete, domain_read, domain_update}; use crate::{ApiClient, Client, Result}; pub const ROOT_FOLDER: &str = "/"; domain_client!(FolderConfig, folder_api); #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct FolderConfig { pub path: String, pub attributes: FolderAttributes, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct FolderAttributes { #[serde(default, skip_serializing_if = "Option::is_none")] pub site: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub contactgroups: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub parents: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] pub snmp_community: Option, #[serde(default)] pub labels: HashMap, #[serde(default, skip_serializing_if = "Option::is_none")] /// this attribute is not optional when queried from the api. /// It cannot be filled in when creating a new folder. pub meta_data: Option, // Additional attributes that can be used to categorize hosts. every tag is part of a tag_group // NOTE that checkmk always returns taggroups prefixed with "tag_" #[serde(default, flatten, skip_serializing_if = "HashMap::is_empty")] pub tags: HashMap, // these fields are used to prevent tags from taking them up #[serde(default)] management_snmp_community: Option, #[serde(default, skip_serializing)] management_address: Value, #[serde(default, skip_serializing)] management_protocol: Value, #[serde(default, skip_serializing)] management_ipmi_credentials: Value, #[serde(default, skip_serializing)] network_scan: Value, #[serde(default, skip_serializing)] network_scan_result: Value, #[serde(default, skip_serializing)] waiting_for_discovery: Value, #[serde(default, skip_serializing)] inventory_failed: Value, #[serde(default, skip_serializing)] locked_attributes: Value, #[serde(default, skip_serializing)] locked_by: Value, } impl FolderAttributes { pub fn set_site(&mut self, site: String) { self.site = Some(site.to_string()); } pub fn with_site(mut self, site: String) -> Self { self.set_site(site); self } pub fn set_contactgroups(&mut self, contactgroups: HostContactGroups) { self.contactgroups = Some(contactgroups); } pub fn with_contactgroups(mut self, contactgroups: HostContactGroups) -> Self { self.set_contactgroups(contactgroups); self } pub fn add_parent(&mut self, parent: impl Display) { self.parents.push(parent.to_string()); } pub fn with_parent(mut self, parent: impl Display) -> Self { self.add_parent(parent); self } pub fn set_snmp_community(&mut self, community: SnmpCommunity) { self.snmp_community = Some(community); } pub fn with_snmp_community(mut self, community: SnmpCommunity) -> Self { self.set_snmp_community(community); self } pub fn add_label(&mut self, labelkey: String, labelvalue: String) { self.labels.insert(labelkey, labelvalue); } pub fn with_label(mut self, labelkey: String, labelvalue: String) -> Self { self.add_label(labelkey, labelvalue); self } pub fn add_tag(&mut self, taggroup: impl Display, tagvalue: String) { self.tags .insert(format!("tag_{taggroup}"), tagvalue); } pub fn with_tag(mut self, taggroup: impl Display, tagvalue: String) -> Self { self.add_tag(taggroup, tagvalue); self } } #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct MetaData { pub created_at: DateTime, pub updated_at: DateTime, // can be null when builtin by checkmk, like the root diretory pub created_by: Option, } impl DomainExtension for FolderConfig { const DOMAIN_TYPE: super::DomainType = super::DomainType::FolderConfig; } #[derive(Debug, Default, Clone, Serialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct FolderCreationRequest { #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, pub title: String, pub parent: String, #[serde(skip_serializing_if = "Option::is_none")] pub attributes: Option, } impl FolderCreationRequest { pub fn new(title: String, parent: String) -> Self { Self { title, parent, name: None, attributes: None, } } pub fn set_name(&mut self, name: String) { self.name = Some(name); } pub fn with_name(mut self, name: String) -> Self { self.set_name(name); self } pub fn set_attributes(&mut self, attributes: FolderAttributes) { self.attributes = Some(attributes); } pub fn with_attributes(mut self, attributes: FolderAttributes) -> Self { self.set_attributes(attributes); self } } #[derive(Debug, Serialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct FolderUpdateRequest { #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] pub attributes: Option, #[serde(skip_serializing_if = "Option::is_none")] pub update_attributes: Option, #[serde(skip_serializing_if = "Vec::is_empty")] pub remove_attributes: Vec, } impl FolderUpdateRequest { pub fn replace(attributes: FolderAttributes) -> Self { Self { title: None, attributes: Some(attributes), update_attributes: None, remove_attributes: Vec::new(), } } pub fn update(attributes: FolderAttributes) -> Self { Self { title: None, attributes: None, update_attributes: Some(attributes), remove_attributes: Vec::new(), } } pub fn remove(attributes: Vec) -> Self { Self { title: None, attributes: None, update_attributes: None, remove_attributes: attributes, } } pub fn title(title: String) -> Self { Self { title: Some(title), attributes: None, update_attributes: None, remove_attributes: Vec::new(), } } pub fn set_title(&mut self, title: String) { self.title = Some(title); } pub fn with_title(mut self, title: String) -> Self { self.set_title(title); self } } #[derive(Debug, Default, Serialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct FolderDeleteQuery { pub delete_mode: FolderDeleteMode, } impl FolderDeleteQuery { pub fn new(delete_mode: FolderDeleteMode) -> Self { Self { delete_mode } } } #[derive(Debug, Default, Serialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[serde(rename_all = "snake_case")] pub enum FolderDeleteMode { #[default] Recursive, AbortOnNonEmpty, } #[derive(Serialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct FolderBulkReadQuery { pub parent: String, pub recursive: bool, } impl FolderBulkReadQuery { pub fn new(parent: String, recursive: bool) -> Self { Self { parent, recursive } } } #[derive(Serialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct FolderBulkUpdateRequest { folder: String, #[serde(flatten)] update_request: FolderUpdateRequest, } impl FolderBulkUpdateRequest { pub fn new(folder: String, update_request: FolderUpdateRequest) -> Self { Self { folder, update_request, } } } impl ApiClient { pub async fn move_folder(&self, id: impl Display, destination: &str) -> Result<()> { #[derive(Serialize)] struct MoveFolderRequest<'a> { destination: &'a str, } let request = MoveFolderRequest { destination }; let url = format!( "{}/actions/move/invoke", self.inner.object_url(FolderConfig::DOMAIN_TYPE, id) ); let request = self.inner.http.post(url).json(&request).build().unwrap(); self.inner.invoke_api(request).await } } domain_create!(FolderConfig, FolderCreationRequest); domain_read!(FolderConfig); domain_update!(FolderConfig, FolderUpdateRequest); domain_delete!(FolderConfig, FolderDeleteQuery); domain_bulk_read!(FolderConfig, FolderBulkReadQuery); domain_bulk_update!(FolderConfig, FolderBulkUpdateRequest);