feat: add support for uptime kuma 1.23.0 and 1.23.1

This commit is contained in:
lucasheld 2023-08-29 18:37:27 +02:00
parent 0d49e97fe5
commit 7902213ddb
15 changed files with 534 additions and 171 deletions

View file

@ -5,7 +5,7 @@ if [ $version ]
then
versions=("$version")
else
versions=(1.22.1 1.22.0 1.21.3)
versions=(1.23.1 1.23.0 1.22.1 1.22.0 1.21.3)
fi
for version in ${versions[*]}
@ -23,6 +23,7 @@ do
echo "Stopping uptime kuma..."
docker stop uptimekuma > /dev/null
sleep 1
echo ''
done

View file

@ -1,37 +1,57 @@
import glob
import re
from bs4 import BeautifulSoup
from utils import deduplicate_list, parse_vue_template
from utils import deduplicate_list, parse_vue_template, diff, type_html_to_py
ROOT = "uptime-kuma"
input_ignores = {
"settings": [
"$root.styleElapsedTime"
]
}
def parse_model_keys(content, object_name):
match = re.findall(object_name + r"\.[0-9a-zA-Z_$]+", content)
keys = []
for m in match:
key = m.replace(object_name + ".", "")
keys.append(key)
keys = deduplicate_list(keys)
return keys
soup = BeautifulSoup(content, "html.parser")
soup_inputs = soup.find_all(attrs={"v-model": True})
inputs = []
for soup_input in soup_inputs:
key = soup_input["v-model"]
if key in input_ignores.get(object_name, []):
continue
else:
key = re.sub(r'^' + object_name + r'\.', "", key)
type_ = soup_input.get("type", "text")
type_ = type_html_to_py(type_)
if type_ == "bool":
value = True if soup_input.get("checked") else False
else:
value = soup_input.get("value")
inputs.append({
"key": key,
"type": type_,
"default": value,
})
return inputs
def parse_proxy_keys():
content = parse_vue_template(f"{ROOT}/src/components/ProxyDialog.vue")
def parse_proxy_keys(root):
content = parse_vue_template(f"{root}/src/components/ProxyDialog.vue")
keys = parse_model_keys(content, "proxy")
return keys
def parse_notification_keys():
content = parse_vue_template(f"{ROOT}/src/components/NotificationDialog.vue")
def parse_notification_keys(root):
content = parse_vue_template(f"{root}/src/components/NotificationDialog.vue")
keys = parse_model_keys(content, "notification")
return keys
def parse_settings_keys():
def parse_settings_keys(root):
all_keys = []
for path in glob.glob('uptime-kuma/src/components/settings/*'):
for path in glob.glob(f'{root}/src/components/settings/*'):
content = parse_vue_template(path)
keys = parse_model_keys(content, "settings")
all_keys.extend(keys)
@ -39,20 +59,20 @@ def parse_settings_keys():
return all_keys
def parse_monitor_keys():
content = parse_vue_template(f"{ROOT}/src/pages/EditMonitor.vue")
def parse_monitor_keys(root):
content = parse_vue_template(f"{root}/src/pages/EditMonitor.vue")
keys = parse_model_keys(content, "monitor")
return keys
def parse_status_page_keys():
def parse_status_page_keys(root):
all_keys = ["id"]
content = parse_vue_template(f"{ROOT}/src/pages/StatusPage.vue")
content = parse_vue_template(f"{root}/src/pages/StatusPage.vue")
keys = parse_model_keys(content, "config")
all_keys.extend(keys)
content = parse_vue_template(f"{ROOT}/src/pages/ManageStatusPage.vue")
content = parse_vue_template(f"{root}/src/pages/ManageStatusPage.vue")
keys = parse_model_keys(content, "statusPage")
all_keys.extend(keys)
@ -60,30 +80,28 @@ def parse_status_page_keys():
return all_keys
def parse_maintenance_keys():
content = parse_vue_template(f"{ROOT}/src/pages/EditMaintenance.vue")
def parse_maintenance_keys(root):
content = parse_vue_template(f"{root}/src/pages/EditMaintenance.vue")
keys = parse_model_keys(content, "maintenance")
return keys
def main():
proxy_keys = parse_proxy_keys()
print("proxy:", proxy_keys)
root_old = "uptime-kuma-old"
root_new = "uptime-kuma"
notification_keys = parse_notification_keys()
print("notification:", notification_keys)
settings_keys = parse_settings_keys()
print("settings:", settings_keys)
monitor_keys = parse_monitor_keys()
print("monitor:", monitor_keys)
status_page_keys = parse_status_page_keys()
print("status_page:", status_page_keys)
maintenance_keys = parse_maintenance_keys()
print("maintenance:", maintenance_keys)
for name, func in [
["proxy", parse_proxy_keys],
["notification", parse_notification_keys],
["settings", parse_settings_keys],
["monitor", parse_monitor_keys],
["status_page", parse_status_page_keys],
["maintenance", parse_maintenance_keys],
]:
keys_old = func(root_old)
keys_new = func(root_new)
print(f"{name}:")
diff(keys_old, keys_new)
if __name__ == "__main__":

View file

@ -1,10 +1,6 @@
import re
from pprint import pprint
from utils import deduplicate_list
ROOT = "uptime-kuma"
from utils import deduplicate_list, diff
def parse_json_keys(data):
@ -22,17 +18,8 @@ def parse_json_keys(data):
return keys
# def parse_object_keys(code, object_name):
# match = re.findall(object_name + r'\.[0-9a-zA-Z_$]+', code)
# keys = []
# for m in match:
# key = m.replace(object_name + ".", "")
# keys.append(key)
# return list(set(keys))
def parse_heartbeat():
with open(f'{ROOT}/server/model/heartbeat.js') as f:
def parse_heartbeat(root):
with open(f'{root}/server/model/heartbeat.js') as f:
content = f.read()
all_keys = []
match = re.search(r'toJSON\(\) {\s+return.*{([^}]+)}', content)
@ -47,8 +34,8 @@ def parse_heartbeat():
return all_keys
def parse_incident():
with open(f'{ROOT}/server/model/incident.js') as f:
def parse_incident(root):
with open(f'{root}/server/model/incident.js') as f:
content = f.read()
match = re.search(r'toPublicJSON\(\) {\s+return.*{([^}]+)}', content)
data = match.group(1)
@ -56,9 +43,9 @@ def parse_incident():
return keys
def parse_monitor():
def parse_monitor(root):
# todo: toPublicJSON ???
with open(f'{ROOT}/server/model/monitor.js') as f:
with open(f'{root}/server/model/monitor.js') as f:
content = f.read()
matches = re.findall(r'data = {([^}]+)}', content)
all_keys = []
@ -70,8 +57,8 @@ def parse_monitor():
return all_keys
def parse_proxy():
with open(f'{ROOT}/server/model/proxy.js') as f:
def parse_proxy(root):
with open(f'{root}/server/model/proxy.js') as f:
content = f.read()
match = re.search(r'toJSON\(\) {\s+return.*{([^}]+)}', content)
data = match.group(1)
@ -102,7 +89,7 @@ def parse_proxy():
# # input (add, edit proxy)
# def parse_proxy2():
# with open(f'{ROOT}/server/proxy.js') as f:
# with open(f'{root}/server/proxy.js') as f:
# content = f.read()
#
# code = parse_function(r'async save\([^)]+\) ', content)
@ -110,8 +97,8 @@ def parse_proxy():
# return keys
def parse_status_page():
with open(f'{ROOT}/server/model/status_page.js') as f:
def parse_status_page(root):
with open(f'{root}/server/model/status_page.js') as f:
content = f.read()
all_keys = []
match = re.search(r'toJSON\(\) {\s+return.*{([^}]+)}', content)
@ -126,8 +113,8 @@ def parse_status_page():
return all_keys
def parse_tag():
with open(f'{ROOT}/server/model/tag.js') as f:
def parse_tag(root):
with open(f'{root}/server/model/tag.js') as f:
content = f.read()
match = re.search(r'toJSON\(\) {\s+return.*{([^}]+)}', content)
data = match.group(1)
@ -135,33 +122,22 @@ def parse_tag():
return keys
print("heartbeat")
pprint(parse_heartbeat())
print("")
if __name__ == "__main__":
root_old = "uptime-kuma-old"
root_new = "uptime-kuma"
print("incident")
pprint(parse_incident())
print("")
print("monitor")
pprint(parse_monitor())
print("")
print("proxy")
pprint(parse_proxy())
print("")
# print("prox2")
# pprint(parse_proxy2())
# print("")
print("status page")
pprint(parse_status_page())
print("")
print("tag")
pprint(parse_tag())
print("")
for name, func in [
["heartbeat", parse_heartbeat],
["incident", parse_incident],
["monitor", parse_monitor],
["proxy", parse_proxy],
["status page", parse_status_page],
["tag", parse_tag],
]:
keys_old = func(root_old)
keys_new = func(root_new)
print(f"{name}:")
diff(keys_old, keys_new)
# TODO:

View file

@ -3,6 +3,32 @@ from bs4 import BeautifulSoup
from utils import parse_vue_template, write_to_file
titles = {
"http": "HTTP(s)",
"port": "TCP Port",
"ping": "Ping",
"keyword": "HTTP(s) - Keyword",
"grpc-keyword": "gRPC(s) - Keyword",
"dns": "DNS",
"docker": "Docker Container",
"push": "Push",
"steam": "Steam Game Server",
"gamedig": "GameDig",
"mqtt": "MQTT",
"sqlserver": "Microsoft SQL Server",
"postgres": "PostgreSQL",
"mysql": "MySQL/MariaDB",
"mongodb": "MongoDB",
"radius": "Radius",
"redis": "Redis",
"group": "Group",
"json-query": "HTTP(s) - Json Query",
"real-browser": "HTTP(s) - Browser Engine (Chrome/Chromium)",
"kafka-producer": "Kafka Producer",
"tailscale-ping": "Tailscale Ping"
}
def parse_monitor_types():
content = parse_vue_template("uptime-kuma/src/pages/EditMonitor.vue")
@ -10,10 +36,13 @@ def parse_monitor_types():
select = soup.find("select", id="type")
options = select.find_all("option")
types = []
types = {}
for o in options:
type_ = o.attrs["value"]
types.append(type_)
types[type_] = {
"value": type_,
"title": titles[type_]
}
return types

View file

@ -3,7 +3,7 @@ import re
from bs4 import BeautifulSoup
from utils import deduplicate_list, write_to_file
from utils import deduplicate_list, write_to_file, type_html_to_py
# deprecated or wrong inputs
@ -26,6 +26,10 @@ ignored_inputs = {
]
}
input_overwrites = {
"showAdditionalHeadersField": "webhookAdditionalHeaders"
}
titles = {
"alerta": "Alerta",
"AlertNow": "AlertNow",
@ -48,6 +52,7 @@ titles = {
"OneBot": "OneBot",
"Opsgenie": "Opsgenie",
"PagerDuty": "PagerDuty",
"PagerTree": "PagerTree",
"pushbullet": "Pushbullet",
"PushByTechulus": "Push by Techulus",
"pushover": "Pushover",
@ -76,10 +81,14 @@ titles = {
"SMSManager": "SmsManager (smsmanager.cz)",
"WeCom": "WeCom",
"ServerChan": "ServerChan",
"nostr": "Nostr",
"FlashDuty": "FlashDuty",
"smsc": "SMSC",
}
def build_notification_providers(root):
def build_notification_providers():
root = "uptime-kuma"
providers = {}
# get providers and input names
@ -96,7 +105,7 @@ def build_notification_providers(root):
inputs = [i.strip() for i in inputs]
providers[name] = {
"title": titles.get(name, name),
"title": titles[name],
"inputs": {},
}
for input_ in inputs:
@ -117,15 +126,15 @@ def build_notification_providers(root):
conditions = {}
attrs = input_.attrs
v_model = attrs.get("v-model")
param_name = re.match(r'\$parent.notification.(.*)$', v_model).group(1)
v_model_overwrite = input_overwrites.get(v_model)
if v_model_overwrite:
param_name = v_model_overwrite
else:
param_name = re.match(r'\$parent.notification.(.*)$', v_model).group(1)
type_ = attrs.get("type")
if type_ == "number":
type_ = "int"
elif type_ == "checkbox":
type_ = "bool"
else:
type_ = "str"
type_ = type_html_to_py(type_)
required_true_values = ['', 'true']
if attrs.get("required") in required_true_values or attrs.get(":required") in required_true_values:
@ -157,17 +166,7 @@ def build_notification_providers(root):
return providers
def diff(old, new):
for i in new:
if i not in old:
print("+", i)
for i in old:
if i not in new:
print("-", i)
print("")
notification_providers = build_notification_providers("uptime-kuma")
notification_providers = build_notification_providers()
notification_provider_conditions = {}
for notification_provider in notification_providers:
@ -181,11 +180,3 @@ write_to_file(
notification_providers=notification_providers,
notification_provider_conditions=notification_provider_conditions
)
# notification_providers_old = build_notification_providers("uptime-kuma-old")
# notification_providers_new = build_notification_providers("uptime-kuma")
# diff(notification_providers_old, notification_providers_new)
#
# notification_provider_conditions_old = build_notification_provider_conditions("uptime-kuma-old")
# notification_provider_conditions_new = build_notification_provider_conditions("uptime-kuma")
# diff(notification_provider_conditions_old, notification_provider_conditions_new)

View file

@ -1,6 +0,0 @@
notification = [
"type",
"isDefault",
"userId",
"applyExisting",
]

View file

@ -2,7 +2,10 @@ from enum import Enum
class MonitorType(str, Enum):
{%- for name in monitor_types %}
{{ name.upper() }} = "{{ name }}"
{%- endfor %}
"""Enumerate monitor types."""
{{""}}
{%- for type_ in monitor_types.values() %}
{{ type_["value"].upper().replace("-", "_") }} = "{{ type_["value"] }}"
"""{{ type_["title"] }}"""
{% endfor -%}

View file

@ -24,3 +24,23 @@ def write_to_file(template, destination, **kwargs):
rendered = template.render(**kwargs)
with open(destination, "w") as f:
f.write(rendered)
def diff(old, new):
for i in new:
if i not in old:
print("+", i)
for i in old:
if i not in new:
print("-", i)
print("")
def type_html_to_py(type_):
if type_ == "number":
type_ = "int"
elif type_ == "checkbox":
type_ = "bool"
else:
type_ = "str"
return type_

View file

@ -1,4 +1,5 @@
import unittest
from packaging.version import parse as parse_version
from uptime_kuma_api import DockerType, UptimeKumaException
from uptime_kuma_test_case import UptimeKumaTestCase
@ -16,8 +17,10 @@ class TestDockerHost(UptimeKumaTestCase):
}
# test docker host
with self.assertRaisesRegex(UptimeKumaException, r'connect ENOENT /var/run/docker.sock'):
self.api.test_docker_host(**expected_docker_host)
if parse_version(self.api.version) != parse_version("1.23.0"):
# test_docker_host does not work in 1.23.0 (https://github.com/louislam/uptime-kuma/issues/3605)
with self.assertRaisesRegex(UptimeKumaException, r'connect ENOENT /var/run/docker.sock'):
self.api.test_docker_host(**expected_docker_host)
# add docker host
r = self.api.add_docker_host(**expected_docker_host)

View file

@ -324,6 +324,53 @@ class TestMonitor(UptimeKumaTestCase):
}
self.do_test_monitor_type(expected_monitor)
def test_monitor_type_json_query(self):
if parse_version(self.api.version) < parse_version("1.23"):
self.skipTest("Unsupported in this Uptime Kuma version")
expected_monitor = {
"type": MonitorType.JSON_QUERY,
"name": "monitor 1",
"url": "http://127.0.0.1",
"jsonPath": "address.country",
"expectedValue": "germany",
}
self.do_test_monitor_type(expected_monitor)
def test_monitor_type_real_browser(self):
if parse_version(self.api.version) < parse_version("1.23"):
self.skipTest("Unsupported in this Uptime Kuma version")
expected_monitor = {
"type": MonitorType.REAL_BROWSER,
"name": "monitor 1",
"url": "http://127.0.0.1",
}
self.do_test_monitor_type(expected_monitor)
def test_monitor_type_kafka_producer(self):
if parse_version(self.api.version) < parse_version("1.23"):
self.skipTest("Unsupported in this Uptime Kuma version")
expected_monitor = {
"type": MonitorType.KAFKA_PRODUCER,
"name": "monitor 1",
"kafkaProducerTopic": "topic",
"kafkaProducerMessage": "message",
}
self.do_test_monitor_type(expected_monitor)
def test_monitor_type_tailscale_ping(self):
if parse_version(self.api.version) < parse_version("1.23"):
self.skipTest("Unsupported in this Uptime Kuma version")
expected_monitor = {
"type": MonitorType.TAILSCALE_PING,
"name": "monitor 1",
"hostname": "127.0.0.1"
}
self.do_test_monitor_type(expected_monitor)
def test_delete_not_existing_monitor(self):
with self.assertRaises(UptimeKumaException):
self.api.delete_monitor(42)

View file

@ -278,7 +278,11 @@ def _check_arguments_monitor(kwargs) -> None:
MonitorType.MONGODB: [],
MonitorType.RADIUS: ["radiusUsername", "radiusPassword", "radiusSecret", "radiusCalledStationId", "radiusCallingStationId"],
MonitorType.REDIS: [],
MonitorType.GROUP: []
MonitorType.GROUP: [],
MonitorType.JSON_QUERY: ["url", "jsonPath", "expectedValue"],
MonitorType.REAL_BROWSER: ["url"],
MonitorType.KAFKA_PRODUCER: ["kafkaProducerTopic", "kafkaProducerMessage"],
MonitorType.TAILSCALE_PING: ["hostname"],
}
type_ = kwargs["type"]
required_args = required_args_by_type[type_]
@ -304,8 +308,45 @@ def _check_arguments_monitor(kwargs) -> None:
)
_check_argument_conditions(conditions, kwargs)
if kwargs["accepted_statuscodes"] and not all([type(i) == str for i in kwargs["accepted_statuscodes"]]):
raise TypeError("Accepted status codes are not all strings")
allowed_accepted_statuscodes = [
"100-199",
"200-299",
"300-399",
"400-499",
"500-599",
] + [
str(i) for i in range(100, 999 + 1)
]
accepted_statuscodes = kwargs["accepted_statuscodes"]
for accepted_statuscode in accepted_statuscodes:
if accepted_statuscode not in allowed_accepted_statuscodes:
raise ValueError(f"Unknown accepted_statuscodes value: {allowed_accepted_statuscodes}")
dns_resolve_type = kwargs["dns_resolve_type"]
if dns_resolve_type not in [
"A",
"AAAA",
"CAA",
"CNAME",
"MX",
"NS",
"PTR",
"SOA",
"SRV",
"TXT",
]:
raise ValueError(f"Unknown dns_resolve_type value: {dns_resolve_type}")
if type_ == MonitorType.KAFKA_PRODUCER:
kafkaProducerSaslOptions_mechanism = kwargs["kafkaProducerSaslOptions"]["mechanism"]
if kafkaProducerSaslOptions_mechanism not in [
"None",
"plain",
"scram-sha-256",
"scram-sha-512",
"aws",
]:
raise ValueError(f"Unknown kafkaProducerSaslOptions[\"mechanism\"] value: {kafkaProducerSaslOptions_mechanism}")
def _check_arguments_notification(kwargs) -> None:
@ -653,12 +694,16 @@ class UptimeKumaApi(object):
notificationIDList: list = None,
httpBodyEncoding: str = "json",
# HTTP, KEYWORD
# HTTP, KEYWORD, JSON_QUERY, REAL_BROWSER
url: str = None,
# HTTP, KEYWORD, GRPC_KEYWORD
maxredirects: int = 10,
accepted_statuscodes: list[str] = None,
# HTTP, KEYWORD, JSON_QUERY
expiryNotification: bool = False,
ignoreTls: bool = False,
maxredirects: int = 10,
accepted_statuscodes: list = None,
proxyId: int = None,
method: str = "GET",
body: str = None,
@ -671,9 +716,16 @@ class UptimeKumaApi(object):
basic_auth_pass: str = None,
authDomain: str = None,
authWorkstation: str = None,
oauth_auth_method: str = "client_secret_basic",
oauth_token_url: str = None,
oauth_client_id: str = None,
oauth_client_secret: str = None,
oauth_scopes: str = None,
timeout: int = 48,
# KEYWORD
keyword: str = None,
invertKeyword: bool = False,
# GRPC_KEYWORD
grpcUrl: str = None,
@ -684,13 +736,13 @@ class UptimeKumaApi(object):
grpcBody: str = None,
grpcMetadata: str = None,
# DNS, PING, STEAM, MQTT
# PORT, PING, DNS, STEAM, MQTT, RADIUS, TAILSCALE_PING
hostname: str = None,
# PING
packetSize: int = 56,
# DNS, STEAM, MQTT, RADIUS
# PORT, DNS, STEAM, MQTT, RADIUS
port: int = None,
# DNS
@ -698,10 +750,10 @@ class UptimeKumaApi(object):
dns_resolve_type: str = "A",
# MQTT
mqttUsername: str = None,
mqttPassword: str = None,
mqttTopic: str = None,
mqttSuccessMessage: str = None,
mqttUsername: str = "",
mqttPassword: str = "",
mqttTopic: str = "",
mqttSuccessMessage: str = "",
# SQLSERVER, POSTGRES, MYSQL, MONGODB, REDIS
databaseConnectionString: str = None,
@ -721,8 +773,27 @@ class UptimeKumaApi(object):
radiusCallingStationId: str = None,
# GAMEDIG
game: str = None
game: str = None,
gamedigGivenPortOnly: bool = True,
# JSON_QUERY
jsonPath: str = None,
expectedValue: str = None,
# KAFKA_PRODUCER
kafkaProducerBrokers: list[str] = None,
kafkaProducerTopic: str = None,
kafkaProducerMessage: str = None,
kafkaProducerSsl: bool = False,
kafkaProducerAllowAutoTopicCreation: bool = False,
kafkaProducerSaslOptions: dict = None,
) -> dict:
if accepted_statuscodes is None:
accepted_statuscodes = ["200-299"]
if notificationIDList is None:
notificationIDList = {}
data = {
"type": type,
"name": name,
@ -733,26 +804,37 @@ class UptimeKumaApi(object):
"upsideDown": upsideDown,
"resendInterval": resendInterval,
"description": description,
"httpBodyEncoding": httpBodyEncoding
"httpBodyEncoding": httpBodyEncoding,
}
if parse_version(self.version) >= parse_version("1.22"):
data.update({
"parent": parent
"parent": parent,
})
if type in [MonitorType.KEYWORD, MonitorType.GRPC_KEYWORD]:
data.update({
"keyword": keyword,
})
if parse_version(self.version) >= parse_version("1.23"):
data.update({
"invertKeyword": invertKeyword,
})
# HTTP, KEYWORD
# HTTP, KEYWORD, JSON_QUERY, REAL_BROWSER
data.update({
"url": url,
"expiryNotification": expiryNotification,
"ignoreTls": ignoreTls,
})
# HTTP, KEYWORD, GRPC_KEYWORD
data.update({
"maxredirects": maxredirects,
"accepted_statuscodes": accepted_statuscodes,
})
data.update({
"expiryNotification": expiryNotification,
"ignoreTls": ignoreTls,
"proxyId": proxyId,
"method": method,
"body": body,
@ -760,6 +842,11 @@ class UptimeKumaApi(object):
"authMethod": authMethod,
})
if parse_version(self.version) >= parse_version("1.23"):
data.update({
"timeout": timeout,
})
if authMethod in [AuthMethod.HTTP_BASIC, AuthMethod.NTLM]:
data.update({
"basic_auth_user": basic_auth_user,
@ -779,6 +866,15 @@ class UptimeKumaApi(object):
"tlsCa": tlsCa,
})
if authMethod == AuthMethod.OAUTH2_CC:
data.update({
"oauth_auth_method": oauth_auth_method,
"oauth_token_url": oauth_token_url,
"oauth_client_id": oauth_client_id,
"oauth_client_secret": oauth_client_secret,
"oauth_scopes": oauth_scopes,
})
# GRPC_KEYWORD
if type == MonitorType.GRPC_KEYWORD:
data.update({
@ -791,7 +887,7 @@ class UptimeKumaApi(object):
"grpcMetadata": grpcMetadata,
})
# PORT, PING, DNS, STEAM, MQTT
# PORT, PING, DNS, STEAM, MQTT, RADIUS, TAILSCALE_PING
data.update({
"hostname": hostname,
})
@ -840,7 +936,7 @@ class UptimeKumaApi(object):
if type == MonitorType.DOCKER:
data.update({
"docker_container": docker_container,
"docker_host": docker_host
"docker_host": docker_host,
})
# RADIUS
@ -850,15 +946,42 @@ class UptimeKumaApi(object):
"radiusPassword": radiusPassword,
"radiusSecret": radiusSecret,
"radiusCalledStationId": radiusCalledStationId,
"radiusCallingStationId": radiusCallingStationId
"radiusCallingStationId": radiusCallingStationId,
})
# GAMEDIG
if type == MonitorType.GAMEDIG:
data.update({
"game": game
"game": game,
})
if parse_version(self.version) >= parse_version("1.23"):
data.update({
"gamedigGivenPortOnly": gamedigGivenPortOnly,
})
# JSON_QUERY
if type == MonitorType.JSON_QUERY:
data.update({
"jsonPath": jsonPath,
"expectedValue": expectedValue,
})
# KAFKA_PRODUCER
if type == MonitorType.KAFKA_PRODUCER:
if kafkaProducerBrokers is None:
kafkaProducerBrokers = []
if not kafkaProducerSaslOptions:
kafkaProducerSaslOptions = {
"mechanism": "None",
}
data.update({
"kafkaProducerBrokers": kafkaProducerBrokers,
"kafkaProducerTopic": kafkaProducerTopic,
"kafkaProducerMessage": kafkaProducerMessage,
"kafkaProducerSsl": kafkaProducerSsl,
"kafkaProducerAllowAutoTopicCreation": kafkaProducerAllowAutoTopicCreation,
"kafkaProducerSaslOptions": kafkaProducerSaslOptions,
})
return data
def _build_maintenance_data(
@ -926,6 +1049,7 @@ class UptimeKumaApi(object):
customCSS: str = "",
footerText: str = None,
showPoweredBy: bool = True,
showCertificateExpiry: bool = False,
icon: str = "/icon.svg",
publicGroupList: list = None
@ -954,8 +1078,12 @@ class UptimeKumaApi(object):
"googleAnalyticsId": googleAnalyticsId,
"customCSS": customCSS,
"footerText": footerText,
"showPoweredBy": showPoweredBy
"showPoweredBy": showPoweredBy,
}
if parse_version(self.version) >= parse_version("1.23"):
config.update({
"showCertificateExpiry": showCertificateExpiry,
})
return slug, config, icon, publicGroupList
# monitor
@ -1082,9 +1210,11 @@ class UptimeKumaApi(object):
'dns_resolve_type': 'A',
'docker_container': None,
'docker_host': None,
'expectedValue': None,
'expiryNotification': False,
'forceInactive': False,
'game': None,
'gamedigGivenPortOnly': True,
'grpcBody': None,
'grpcEnableTls': False,
'grpcMetadata': None,
@ -1099,17 +1229,30 @@ class UptimeKumaApi(object):
'ignoreTls': False,
'includeSensitiveData': True,
'interval': 60,
'invertKeyword': False,
'jsonPath': None,
'kafkaProducerAllowAutoTopicCreation': False,
'kafkaProducerBrokers': None,
'kafkaProducerMessage': None,
'kafkaProducerSaslOptions': None,
'kafkaProducerSsl': False,
'kafkaProducerTopic': None,
'keyword': None,
'maintenance': False,
'maxredirects': 10,
'maxretries': 0,
'method': 'GET',
'mqttPassword': None,
'mqttSuccessMessage': None,
'mqttTopic': None,
'mqttUsername': None,
'mqttPassword': '',
'mqttSuccessMessage': '',
'mqttTopic': '',
'mqttUsername': '',
'name': 'monitor 1',
'notificationIDList': [1, 2],
'oauth_auth_method': None,
'oauth_client_id': None,
'oauth_client_secret': None,
'oauth_scopes': None,
'oauth_token_url': None,
'packetSize': 56,
'parent': None,
'pathName': 'monitor 1',
@ -1123,7 +1266,9 @@ class UptimeKumaApi(object):
'radiusUsername': None,
'resendInterval': 0,
'retryInterval': 60,
'screenshot': None,
'tags': [],
'timeout': 48,
'tlsCa': None,
'tlsCert': None,
'tlsKey': None,
@ -1279,6 +1424,23 @@ class UptimeKumaApi(object):
r = self._call('getGameList')
return r.get("gameList")
def test_chrome(self, executable) -> dict:
"""
Test if the chrome executable is valid and return the version.
:return: The server response.
:rtype: dict
:raises UptimeKumaException: If the server returns an error.
Example::
>>> api.test_chrome("/usr/bin/chromium")
{
'msg': 'Found Chromium/Chrome. Version: 90.0.4430.212'
}
"""
return self._call('testChrome', executable)
@append_docstring(monitor_docstring("add"))
def add_monitor(self, **kwargs) -> dict:
"""
@ -1799,8 +1961,8 @@ class UptimeKumaApi(object):
'description': 'description 1',
'domainNameList': [],
'footerText': None,
'icon': '/icon.svg',
'googleAnalyticsId': '',
'icon': '/icon.svg',
'id': 1,
'incident': {
'content': 'content 1',
@ -1819,7 +1981,8 @@ class UptimeKumaApi(object):
{
'id': 1,
'name': 'monitor 1',
'sendUrl': False
'sendUrl': False,
'type': 'http'
}
],
'name': 'Services',
@ -1827,6 +1990,7 @@ class UptimeKumaApi(object):
}
],
'published': True,
'showCertificateExpiry': False,
'showPoweredBy': False,
'showTags': False,
'slug': 'slug1',
@ -1919,6 +2083,7 @@ class UptimeKumaApi(object):
:param str, optional customCSS: Custom CSS, defaults to ""
:param str, optional footerText: Custom Footer, defaults to None
:param bool, optional showPoweredBy: Show Powered By, defaults to True
:param bool, optional showCertificateExpiry: Show Certificate Expiry, defaults to False
:param str, optional icon: Icon, defaults to "/icon.svg"
:param list, optional publicGroupList: Public Group List, defaults to None
:return: The server response.
@ -2327,11 +2492,12 @@ class UptimeKumaApi(object):
>>> api.info()
{
'version': '1.19.2',
'latestVersion': '1.19.2',
'primaryBaseURL': None,
'isContainer': True,
'latestVersion': '1.23.1',
'primaryBaseURL': '',
'serverTimezone': 'Europe/Berlin',
'serverTimezoneOffset': '+01:00'
'serverTimezoneOffset': '+02:00',
'version': '1.23.1'
}
"""
r = self._get_event_data(Event.INFO)
@ -2525,10 +2691,12 @@ class UptimeKumaApi(object):
{
'checkBeta': False,
'checkUpdate': False,
'chromeExecutable': '',
'disableAuth': False,
'dnsCache': True,
'entryPage': 'dashboard',
'keepDataPeriodDays': 180,
'nscd': False,
'primaryBaseURL': '',
'searchEngineIndex': False,
'serverTimezone': 'Europe/Berlin',
@ -2561,7 +2729,9 @@ class UptimeKumaApi(object):
searchEngineIndex: bool = False,
primaryBaseURL: str = "",
steamAPIKey: str = "",
nscd: bool = False,
dnsCache: bool = False,
chromeExecutable: str = "",
# notifications
tlsExpiryNotifyDays: list = None,
@ -2584,7 +2754,9 @@ class UptimeKumaApi(object):
:param bool, optional searchEngineIndex: Search Engine Visibility, defaults to False
:param str, optional primaryBaseURL: Primary Base URL, defaults to ""
:param str, optional steamAPIKey: Steam API Key. For monitoring a Steam Game Server you need a Steam Web-API key., defaults to ""
:param bool, optional nscd: Enable NSCD (Name Service Cache Daemon) for caching all DNS requests, defaults to False
:param bool, optional dnsCache: True to enable DNS Cache. It may be not working in some IPv6 environments, disable it if you encounter any issues., defaults to False
:param str, optional chromeExecutable: Chrome/Chromium Executable, defaults to ""
:param list, optional tlsExpiryNotifyDays: TLS Certificate Expiry. HTTPS Monitors trigger notification when TLS certificate expires in., defaults to None
:param bool, optional disableAuth: Disable Authentication, defaults to False
:param bool, optional trustProxy: Trust Proxy. Trust 'X-Forwarded-\*' headers. If you want to get the correct client IP and your Uptime Kuma is behind such as Nginx or Apache, you should enable this., defaults to False
@ -2634,6 +2806,16 @@ class UptimeKumaApi(object):
"disableAuth": disableAuth,
"trustProxy": trustProxy
}
if parse_version(self.version) >= parse_version("1.23"):
data.update({
"chromeExecutable": chromeExecutable,
})
if parse_version(self.version) >= parse_version("1.23.1"):
data.update({
"nscd": nscd,
})
return self._call('setSettings', (data, password))
def change_password(self, old_password: str, new_password: str) -> dict:
@ -2688,7 +2870,7 @@ class UptimeKumaApi(object):
}
"""
if import_handle not in ["overwrite", "skip", "keep"]:
raise ValueError()
raise ValueError(f"Unknown import_handle value: {import_handle}")
return self._call('uploadBackup', (json_data, import_handle))
# 2FA

View file

@ -15,3 +15,6 @@ class AuthMethod(str, Enum):
MTLS = "mtls"
"""mTLS Authentication."""
OAUTH2_CC = "oauth2-cc"
"""OAuth2: Client Credentials"""

View file

@ -44,12 +44,30 @@ def monitor_docstring(mode) -> str:
:param str, optional basic_auth_pass: Password for ``authMethod`` :attr:`~.AuthMethod.HTTP_BASIC` and :attr:`~.AuthMethod.NTLM`, defaults to None
:param str, optional authDomain: Domain for ``authMethod`` :attr:`~.AuthMethod.NTLM`, defaults to None
:param str, optional authWorkstation: Workstation for ``authMethod`` :attr:`~.AuthMethod.NTLM`, defaults to None
:param str, optional oauth_auth_method: Authentication Method, defaults to None
:param str, optional oauth_token_url: OAuth Token URL, defaults to None
:param str, optional oauth_client_id: Client ID, defaults to None
:param str, optional oauth_client_secret: Client Secret, defaults to None
:param str, optional oauth_scopes: OAuth Scope, defaults to None
:param int, optional timeout: Request Timeout, defaults to None
:param str, optional keyword: Keyword. Search keyword in plain HTML or JSON response. The search is case-sensitive., defaults to None
:param bool, optional invertKeyword: Invert Keyword. Look for the keyword to be absent rather than present., defaults to False
:param str, optional hostname: Hostname, defaults to None
:param int, optional packetSize: Packet Size, defaults to None
:param int, optional port: Port, ``type`` :attr:`~.MonitorType.DNS` defaults to ``53`` and ``type`` :attr:`~.MonitorType.RADIUS` defaults to ``1812``
:param str, optional dns_resolve_server: Resolver Server, defaults to "1.1.1.1"
:param str, optional dns_resolve_type: Resource Record Type, defaults to "A"
:param str, optional dns_resolve_type: Resource Record Type, defaults to "A". Available values are:
- "A"
- "AAAA"
- "CAA"
- "CNAME"
- "MX"
- "NS"
- "PTR"
- "SOA"
- "SRV"
- "TXT"
:param str, optional mqttUsername: MQTT Username, defaults to None
:param str, optional mqttPassword: MQTT Password, defaults to None
:param str, optional mqttTopic: MQTT Topic, defaults to None
@ -64,6 +82,29 @@ def monitor_docstring(mode) -> str:
:param str, optional radiusCalledStationId: Called Station Id. Identifier of the called device., defaults to None
:param str, optional radiusCallingStationId: Calling Station Id. Identifier of the calling device., defaults to None
:param str, optional game: Game, defaults to None
:param bool, optional gamedigGivenPortOnly: Gamedig: Guess Port. The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server., defaults to False
:param str, optional jsonPath: Json Query, defaults to None
:param str, optional expectedValue: Expected Value, defaults to None
:param str, optional kafkaProducerBrokers: Kafka Broker list, defaults to None
:param str, optional kafkaProducerTopic: Kafka Topic Name, defaults to None
:param str, optional kafkaProducerMessage: Kafka Producer Message, defaults to None
:param bool, optional kafkaProducerSsl: Enable Kafka SSL, defaults to False
:param bool, optional kafkaProducerAllowAutoTopicCreation: Enable Kafka Producer Auto Topic Creation, defaults to False
:param dict, optional kafkaProducerSaslOptions: Kafka SASL Options
- **mechanism** (*str*, *optional*): Mechanism, defaults to "None". Available values are:
- "None"
- "plain"
- "scram-sha-256"
- "scram-sha-512"
- "aws"
- **username** (*str*, *optional*): Username, defaults to None
- **password** (*str*, *optional*): Password, defaults to None
- **authorizationIdentity** (*str*, *optional*): Authorization Identity, defaults to None
- **accessKeyId** (*str*, *optional*): AccessKey Id, defaults to None
- **secretAccessKey** (*str*, *optional*): Secret AccessKey, defaults to None
- **sessionToken** (*str*, *optional*): Session Token, defaults to None
"""
@ -100,6 +141,8 @@ def notification_docstring(mode) -> str:
:param str, optional discordWebhookUrl: Notification option for ``type`` :attr:`~.NotificationType.DISCORD`.
:param str discordPrefixMessage: Notification option for ``type`` :attr:`~.NotificationType.DISCORD`.
:param str, optional feishuWebHookUrl: Notification option for ``type`` :attr:`~.NotificationType.FEISHU`.
:param str, optional flashdutySeverity: Notification option for ``type`` :attr:`~.NotificationType.FLASHDUTY`.
:param str flashdutyIntegrationKey: Notification option for ``type`` :attr:`~.NotificationType.FLASHDUTY`.
:param str, optional freemobileUser: Notification option for ``type`` :attr:`~.NotificationType.FREEMOBILE`.
:param str, optional freemobilePass: Notification option for ``type`` :attr:`~.NotificationType.FREEMOBILE`.
:param str, optional goAlertBaseURL: Notification option for ``type`` :attr:`~.NotificationType.GOALERT`.
@ -134,6 +177,9 @@ def notification_docstring(mode) -> str:
:param str mattermostchannel: Notification option for ``type`` :attr:`~.NotificationType.MATTERMOST`.
:param str mattermosticonemo: Notification option for ``type`` :attr:`~.NotificationType.MATTERMOST`.
:param str mattermosticonurl: Notification option for ``type`` :attr:`~.NotificationType.MATTERMOST`.
:param str, optional sender: Notification option for ``type`` :attr:`~.NotificationType.NOSTR`.
:param str, optional recipients: Notification option for ``type`` :attr:`~.NotificationType.NOSTR`.
:param str, optional relays: Notification option for ``type`` :attr:`~.NotificationType.NOSTR`.
:param str ntfyAuthenticationMethod: Notification option for ``type`` :attr:`~.NotificationType.NTFY`. Authentication Method.
:param str ntfyusername: Notification option for ``type`` :attr:`~.NotificationType.NTFY`.
:param str ntfypassword: Notification option for ``type`` :attr:`~.NotificationType.NTFY`.
@ -192,6 +238,7 @@ def notification_docstring(mode) -> str:
- ``4``: SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).
:param str promosmsSenderName: Notification option for ``type`` :attr:`~.NotificationType.PROMOSMS`.
:param str, optional pushbulletAccessToken: Notification option for ``type`` :attr:`~.NotificationType.PUSHBULLET`.
:param str pushdeerServer: Notification option for ``type`` :attr:`~.NotificationType.PUSHDEER`.
:param str, optional pushdeerKey: Notification option for ``type`` :attr:`~.NotificationType.PUSHDEER`.
:param str, optional pushoveruserkey: Notification option for ``type`` :attr:`~.NotificationType.PUSHOVER`.
:param str, optional pushoverapptoken: Notification option for ``type`` :attr:`~.NotificationType.PUSHOVER`.
@ -214,10 +261,16 @@ def notification_docstring(mode) -> str:
:param str, optional signalNumber: Notification option for ``type`` :attr:`~.NotificationType.SIGNAL`.
:param str, optional signalRecipients: Notification option for ``type`` :attr:`~.NotificationType.SIGNAL`.
:param str, optional signalURL: Notification option for ``type`` :attr:`~.NotificationType.SIGNAL`.
:param bool slackchannelnotify: Notification option for ``type`` :attr:`~.NotificationType.SLACK`.
:param str slackchannel: Notification option for ``type`` :attr:`~.NotificationType.SLACK`.
:param str slackusername: Notification option for ``type`` :attr:`~.NotificationType.SLACK`.
:param str slackiconemo: Notification option for ``type`` :attr:`~.NotificationType.SLACK`.
:param str, optional slackwebhookURL: Notification option for ``type`` :attr:`~.NotificationType.SLACK`.
:param str smscTranslit: Notification option for ``type`` :attr:`~.NotificationType.SMSC`.
:param str, optional smscLogin: Notification option for ``type`` :attr:`~.NotificationType.SMSC`.
:param str, optional smscPassword: Notification option for ``type`` :attr:`~.NotificationType.SMSC`.
:param str, optional smscToNumber: Notification option for ``type`` :attr:`~.NotificationType.SMSC`.
:param str smscSenderName: Notification option for ``type`` :attr:`~.NotificationType.SMSC`.
:param bool smseagleEncoding: Notification option for ``type`` :attr:`~.NotificationType.SMSEAGLE`. True to send messages in unicode.
:param int smseaglePriority: Notification option for ``type`` :attr:`~.NotificationType.SMSEAGLE`. Message priority (0-9, default = 0).
:param str smseagleRecipientType: Notification option for ``type`` :attr:`~.NotificationType.SMSEAGLE`. Recipient type.
@ -275,10 +328,12 @@ def notification_docstring(mode) -> str:
:param str telegramMessageThreadID: Notification option for ``type`` :attr:`~.NotificationType.TELEGRAM`.
:param str, optional telegramBotToken: Notification option for ``type`` :attr:`~.NotificationType.TELEGRAM`.
:param str, optional twilioAccountSID: Notification option for ``type`` :attr:`~.NotificationType.TWILIO`. Account SID.
:param str twilioApiKey: Notification option for ``type`` :attr:`~.NotificationType.TWILIO`.
:param str, optional twilioAuthToken: Notification option for ``type`` :attr:`~.NotificationType.TWILIO`. Auth Token.
:param str, optional twilioToNumber: Notification option for ``type`` :attr:`~.NotificationType.TWILIO`. To Number.
:param str, optional twilioFromNumber: Notification option for ``type`` :attr:`~.NotificationType.TWILIO`. From Number.
:param str, optional webhookContentType: Notification option for ``type`` :attr:`~.NotificationType.WEBHOOK`.
:param str webhookCustomBody: Notification option for ``type`` :attr:`~.NotificationType.WEBHOOK`.
:param str webhookAdditionalHeaders: Notification option for ``type`` :attr:`~.NotificationType.WEBHOOK`.
:param str, optional webhookURL: Notification option for ``type`` :attr:`~.NotificationType.WEBHOOK`.
:param str, optional weComBotKey: Notification option for ``type`` :attr:`~.NotificationType.WECOM`.

View file

@ -4,6 +4,9 @@ from enum import Enum
class MonitorType(str, Enum):
"""Enumerate monitor types."""
GROUP = "group"
"""Group"""
HTTP = "http"
"""HTTP(s)"""
@ -16,6 +19,9 @@ class MonitorType(str, Enum):
KEYWORD = "keyword"
"""HTTP(s) - Keyword"""
JSON_QUERY = "json-query"
"""HTTP(s) - Json Query"""
GRPC_KEYWORD = "grpc-keyword"
"""gRPC(s) - Keyword"""
@ -25,6 +31,9 @@ class MonitorType(str, Enum):
DOCKER = "docker"
"""Docker Container"""
REAL_BROWSER = "real-browser"
"""HTTP(s) - Browser Engine (Chrome/Chromium)"""
PUSH = "push"
"""Push"""
@ -37,6 +46,9 @@ class MonitorType(str, Enum):
MQTT = "mqtt"
"""MQTT"""
KAFKA_PRODUCER = "kafka-producer"
"""Kafka Producer"""
SQLSERVER = "sqlserver"
"""Microsoft SQL Server"""
@ -55,5 +67,5 @@ class MonitorType(str, Enum):
REDIS = "redis"
"""Redis"""
GROUP = "group"
"""Group"""
TAILSCALE_PING = "tailscale-ping"
"""Tailscale Ping"""

View file

@ -31,6 +31,9 @@ class NotificationType(str, Enum):
FEISHU = "Feishu"
"""Feishu"""
FLASHDUTY = "FlashDuty"
"""FlashDuty"""
FREEMOBILE = "FreeMobile"
"""FreeMobile (mobile.free.fr)"""
@ -67,6 +70,9 @@ class NotificationType(str, Enum):
MATTERMOST = "mattermost"
"""Mattermost"""
NOSTR = "nostr"
"""Nostr"""
NTFY = "ntfy"
"""Ntfy"""
@ -115,6 +121,9 @@ class NotificationType(str, Enum):
SLACK = "slack"
"""Slack"""
SMSC = "smsc"
"""SMSC"""
SMSEAGLE = "SMSEagle"
"""SMSEagle"""
@ -200,6 +209,10 @@ notification_provider_options = {
NotificationType.FEISHU: dict(
feishuWebHookUrl=dict(type="str", required=True),
),
NotificationType.FLASHDUTY: dict(
flashdutySeverity=dict(type="str", required=True),
flashdutyIntegrationKey=dict(type="str", required=False),
),
NotificationType.FREEMOBILE: dict(
freemobileUser=dict(type="str", required=True),
freemobilePass=dict(type="str", required=True),
@ -258,6 +271,11 @@ notification_provider_options = {
mattermosticonemo=dict(type="str", required=False),
mattermosticonurl=dict(type="str", required=False),
),
NotificationType.NOSTR: dict(
sender=dict(type="str", required=True),
recipients=dict(type="str", required=True),
relays=dict(type="str", required=True),
),
NotificationType.NTFY: dict(
ntfyAuthenticationMethod=dict(type="str", required=False),
ntfyusername=dict(type="str", required=False),
@ -310,6 +328,7 @@ notification_provider_options = {
pushbulletAccessToken=dict(type="str", required=True),
),
NotificationType.PUSHDEER: dict(
pushdeerServer=dict(type="str", required=False),
pushdeerKey=dict(type="str", required=True),
),
NotificationType.PUSHOVER: dict(
@ -346,11 +365,19 @@ notification_provider_options = {
signalURL=dict(type="str", required=True),
),
NotificationType.SLACK: dict(
slackchannelnotify=dict(type="bool", required=False),
slackchannel=dict(type="str", required=False),
slackusername=dict(type="str", required=False),
slackiconemo=dict(type="str", required=False),
slackwebhookURL=dict(type="str", required=True),
),
NotificationType.SMSC: dict(
smscTranslit=dict(type="str", required=False),
smscLogin=dict(type="str", required=True),
smscPassword=dict(type="str", required=True),
smscToNumber=dict(type="str", required=True),
smscSenderName=dict(type="str", required=False),
),
NotificationType.SMSEAGLE: dict(
smseagleEncoding=dict(type="bool", required=False),
smseaglePriority=dict(type="int", required=False),
@ -409,12 +436,14 @@ notification_provider_options = {
),
NotificationType.TWILIO: dict(
twilioAccountSID=dict(type="str", required=True),
twilioApiKey=dict(type="str", required=False),
twilioAuthToken=dict(type="str", required=True),
twilioToNumber=dict(type="str", required=True),
twilioFromNumber=dict(type="str", required=True),
),
NotificationType.WEBHOOK: dict(
webhookContentType=dict(type="str", required=True),
webhookCustomBody=dict(type="str", required=False),
webhookAdditionalHeaders=dict(type="str", required=False),
webhookURL=dict(type="str", required=True),
),