use std::collections::HashMap; use std::fmt::Display; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::{Client, Result}; use crate::api::{BulkReadDomainExtention, BulkUpdateDomainExtention, DomainExtention}; use crate::api::rules::contactgroups::HostContactGroups; use crate::api::rules::snmp::SnmpCommunity; use crate::client::ApiClient; pub const ROOT_FOLDER: &str = "/"; impl Client { pub fn folder_api(&self) -> ApiClient { self.into() } } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct FolderConfig { pub path: String, pub attributes: FolderAttributes, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] 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, #[serde(flatten, skip_serializing_if = "HashMap::is_empty")] /// 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_" pub tags: HashMap, // TODO: to be implemented // #[serde(flatten, default)] // pub networkscan: NetworkScan, // #[serde(flatten, default)] // pub management: ManagementProtocol, } 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.to_string()); } pub fn with_tag(mut self, taggroup: impl Display, tagvalue: String) -> Self { self.add_tag(taggroup, tagvalue); self } } #[derive(Debug, Clone, Serialize, Deserialize)] 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 DomainExtention for FolderConfig { const DOMAIN_TYPE: super::DomainType = super::DomainType::FolderConfig; type CreationRequest = FolderCreationRequest; type CreationQuery = (); type ReadQuery = (); type UpdateRequest = FolderUpdateRequest; type DeleteQuery = FolderDeleteQuery; } #[derive(Debug, Serialize)] pub struct FolderCreationRequest { pub name: String, pub title: String, pub parent: String, pub attributes: FolderAttributes, } impl FolderCreationRequest { pub fn new(name: String, title: String, parent: String, attributes: FolderAttributes) -> Self { Self { name, title, parent, attributes, } } } #[derive(Debug, Serialize)] pub struct FolderCreationQuery; #[derive(Debug, Serialize)] pub struct FolderReadQuery; #[derive(Debug, Serialize)] pub struct FolderUpdateRequest { #[serde(skip_serializing_if = "String::is_empty")] pub title: String, #[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: String::new(), attributes: Some(attributes), update_attributes: None, remove_attributes: Vec::new(), } } pub fn update(attributes: FolderAttributes) -> Self { Self { title: String::new(), attributes: None, update_attributes: Some(attributes), remove_attributes: Vec::new(), } } pub fn remove(attributes: Vec) -> Self { Self { title: String::new(), attributes: None, update_attributes: None, remove_attributes: attributes, } } pub fn title(title: String) -> Self { Self { title, attributes: None, update_attributes: None, remove_attributes: Vec::new() } } pub fn set_title(&mut self, title: String) { self.title = title; } pub fn with_title(mut self, title: String) -> Self { self.set_title(title); self } } #[derive(Debug, Default, Serialize)] pub struct FolderDeleteQuery { pub delete_mode: FolderDeleteMode, } impl FolderDeleteQuery { pub fn new(delete_mode: FolderDeleteMode) -> Self { Self { delete_mode } } } #[derive(Debug, Default, Serialize)] #[serde(rename_all = "snake_case")] pub enum FolderDeleteMode { #[default] Recursive, AbotyOnNonEmpty, } impl BulkReadDomainExtention for FolderConfig { type BulkReadQuery = FolderBulkReadQuery; } #[derive(Serialize)] pub struct FolderBulkReadQuery { pub parent: String, pub recursive: bool, } impl FolderBulkReadQuery { pub fn new(parent: String, recursive: bool) -> Self { Self { parent, recursive } } } impl BulkUpdateDomainExtention for FolderConfig { type BulkUpdateRequest = FolderBulkUpdateRequest; } #[derive(Serialize)] 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 } } } #[derive(Serialize)] pub struct MoveFolderRequest { destination: String } impl MoveFolderRequest { pub fn new(destination: String) -> Self { Self { destination } } } impl ApiClient { pub async fn move_folder(&self, id: impl Display, request: &MoveFolderRequest) -> Result<()> { 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 } } // TODO: add show hosts api call