Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion ENTITY_TYPES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ AutoKuma adds a few special properties which are handled internally and aren't s
| `tag_names` | `[{"name": "mytag", "value": "A value" }]` | List of structs containing the id and optionally a values for labels, |
| `docker_host_name` | `local_socket` | The autokuma id of the docker socket for a docker monitor |
| `create_paused` | false | If true new monitors will be added in paused state, does not effect existing monitors |
| `entity_id` | `my-app-kuma` | Inside a `status_page` `public_group_list` monitor entry, the autokuma id of the monitor to include. Resolved to the Uptime Kuma monitor id before saving. |

# `docker_host`
| Property | Example Value |
Expand All @@ -22,10 +23,11 @@ AutoKuma adds a few special properties which are handled internally and aren't s
| `is_default` | `true` (Note: this is only used by the WebUI, AutoKuma does not respect this setting for technical reasons) |
| `config` | nested provider specific settings. Too many to list here. I suggest creating a notification with your provider in the WebUI and then using the `kuma` CLI to inspect the options |

# Monitor Types
# Entity Types
- [AutoKuma specific properties:](#autokuma-specific-properties)
- [`docker_host`](#docker_host)
- [`notification`](#notification)
- [`status_page`](#status_page)
- [Monitor Types](#monitor-types)
- [`dns`](#dns)
- [`docker`](#docker)
Expand Down Expand Up @@ -55,6 +57,30 @@ AutoKuma adds a few special properties which are handled internally and aren't s
- [`rabbitmq`](#rabbitmq)


## `status_page`
| Property | Example Value |
|-------------------|------------------------------------------------------------|
| `slug` | `production` |
| `title` | `Production Status` |
| `description` | `Status page for the production environment` |
| `published` | `true` |
| `icon` | `/icon.svg` |
| `theme` | `auto` |
| `showTags` | `false` |
| `domainNameList` | `["status.example.com"]` |
| `customCSS` | `body {\n \n}\n` |
| `footerText` | `Managed by AutoKuma` |
| `showPoweredBy` | `true` |
| `analyticsType` | `google` |
| `analyticsId` | `G-XXXXXXXXXX` |
| `analyticsScriptUrl` | `https://analytics.example.com/script.js` |
| `showCertificateExpiry` | `true` |
| `publicGroupList` | `[{"name": "Services", "monitorList": [{"entity_id": "my-app-kuma"}, {"entity_id": "my-db-kuma"}]}]` |

Inside `publicGroupList` each monitor entry supports either:
- `id`: the numeric Uptime Kuma monitor id.
- `entity_id`: the autokuma id of the monitor. AutoKuma resolves this to the numeric `id` using the internal mapping before saving the status page. The referenced monitor must already be known to AutoKuma (created in a previous sync or an earlier resource in the same source); otherwise the status page will be skipped until the monitor exists.

## `dns`
| Property | Example Value |
|------------------------|---------------|
Expand Down
16 changes: 16 additions & 0 deletions autokuma/kubernetes/status-page-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: autokuma.bigboot.dev/v1
kind: KumaEntity
metadata:
name: production-status
namespace: default
spec:
config:
type: status_page
slug: production
title: Production Status
published: true
public_group_list:
- name: Services
monitorList:
- entity_id: my-app-kuma
- entity_id: my-db-kuma
37 changes: 36 additions & 1 deletion autokuma/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,37 @@ pub fn get_entities_from_labels(
.collect()
}

fn resolve_status_page_names(state: Arc<AppState>, status_page: &mut StatusPage) -> Result<()> {
if let Some(groups) = status_page.public_group_list.as_mut() {
for group in groups.iter_mut() {
for monitor in group.monitor_list.iter_mut() {
if monitor.id.is_some() {
continue;
}

let entity_id = monitor
.entity_id
.clone()
.ok_or_else(|| Error::DeserializeError(
"Status page monitor entry must have either `id` or `entity_id`".to_owned(),
))?;

let name = Name::Monitor(entity_id);
let id = state
.db
.get_id::<i32>(name.clone())
.ok()
.flatten()
.ok_or_else(|| Error::NameNotFound(name))?;

monitor.id = Some(id);
}
}
}

Ok(())
}

fn resolve_names(state: Arc<AppState>, monitor: &mut Monitor) -> Result<()> {
if let Some(group_name) = monitor.common().parent_name().clone() {
let name = Name::Monitor(group_name.clone());
Expand Down Expand Up @@ -516,7 +547,11 @@ pub fn get_entity_from_settings(

if let Entity::Monitor(monitor) = &mut entity {
monitor.validate(id)?;
resolve_names(state, monitor)?;
resolve_names(state.clone(), monitor)?;
}

if let Entity::StatusPage(status_page) = &mut entity {
resolve_status_page_names(state, status_page)?;
}

Ok(entity)
Expand Down
3 changes: 3 additions & 0 deletions autokuma/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ impl Sync {
(Entity::Tag(merge), Entity::Tag(_)) => {
kuma.edit_tag(merge).await?;
}
(Entity::StatusPage(merge), Entity::StatusPage(_)) => {
kuma.edit_status_page(merge).await?;
}
_ => {}
}
}
Expand Down
54 changes: 52 additions & 2 deletions kuma-client/src/models/status_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ pub struct PublicGroupMonitor {
#[derivative(Hash = "ignore")]
pub name: Option<String>,

#[serde(skip_serializing)]
#[derivative(PartialEq = "ignore")]
#[derivative(Hash = "ignore")]
pub entity_id: Option<String>,

#[serde(rename = "weight")]
#[serde_as(as = "Option<DeserializeBoolLenient>")]
pub weight: Option<bool>,
Expand Down Expand Up @@ -55,7 +60,7 @@ pub struct PublicGroup {
#[serde_as(as = "Option<DeserializeNumberLenient>")]
pub weight: Option<i32>,

#[serde(rename = "monitorList", default)]
#[serde(rename = "monitorList", alias = "monitor_list", default)]
pub monitor_list: PublicGroupMonitorList,
}
crate::default_from_serde!(PublicGroup);
Expand Down Expand Up @@ -145,7 +150,7 @@ pub struct StatusPage {
#[serde_as(as = "Option<DeserializeBoolLenient>")]
pub show_certificate_expiry: Option<bool>,

#[serde(rename = "publicGroupList")]
#[serde(rename = "publicGroupList", alias = "public_group_list")]
#[serde_as(as = "Option<DeserializeVecLenient<PublicGroup>>")]
pub public_group_list: Option<PublicGroupList>,
}
Expand All @@ -154,3 +159,48 @@ crate::default_from_serde!(StatusPage);
pub type StatusPageList = HashMap<String, StatusPage>;
pub type PublicGroupList = Vec<PublicGroup>;
pub type PublicGroupMonitorList = Vec<PublicGroupMonitor>;

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn status_page_deserializes_snake_case_aliases_and_entity_id() {
let json = serde_json::json!({
"type": "status_page",
"slug": "production",
"title": "Production Status",
"public_group_list": [
{
"name": "Services",
"monitor_list": [
{ "entity_id": "my-app-kuma" },
{ "entity_id": "my-db-kuma" },
]
}
]
});

let page: StatusPage = serde_json::from_value(json).expect("should parse status page");
let group = page.public_group_list.expect("should have groups").into_iter().next().expect("should have one group");
assert_eq!(group.name.as_deref(), Some("Services"));
assert_eq!(group.monitor_list.len(), 2);
assert_eq!(group.monitor_list[0].entity_id.as_deref(), Some("my-app-kuma"));
assert!(group.monitor_list[0].id.is_none());
}

#[test]
fn public_group_monitor_skips_entity_id_on_serialization() {
let monitor = PublicGroupMonitor {
id: Some(42),
name: None,
entity_id: Some("my-app-kuma".to_owned()),
weight: None,
monitor_type: None,
};

let json = serde_json::to_value(&monitor).expect("should serialize");
assert!(json.get("entity_id").is_none());
assert_eq!(json.get("id").and_then(|v| v.as_i64()), Some(42));
}
}