├── .coveragerc ├── .gitignore ├── .readthedocs.yaml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dev-requirements.txt ├── docs ├── Makefile ├── api.rst ├── conf.py ├── index.rst ├── install.rst └── make.bat ├── requirements.txt ├── run_tests.sh ├── scripts ├── .gitignore ├── build_inputs.py ├── build_models.py ├── build_monitor_types.py ├── build_notification_docstring.py ├── build_notifications.py ├── monitor_type.py.j2 ├── notification_providers.py.j2 └── utils.py ├── setup.py ├── tests ├── test_2fa.py ├── test_api_key.py ├── test_avg_ping.py ├── test_clear.py ├── test_database.py ├── test_docker_host.py ├── test_game_list.py ├── test_heartbeat.py ├── test_helper_methods.py ├── test_info.py ├── test_login.py ├── test_maintenance.py ├── test_monitor.py ├── test_monitor_tag.py ├── test_notification.py ├── test_proxy.py ├── test_settings.py ├── test_status_page.py ├── test_tag.py ├── test_uptime.py └── uptime_kuma_test_case.py └── uptime_kuma_api ├── __init__.py ├── __version__.py ├── api.py ├── auth_method.py ├── docker_type.py ├── docstrings.py ├── event.py ├── exceptions.py ├── incident_style.py ├── maintenance_strategy.py ├── monitor_status.py ├── monitor_type.py ├── notification_providers.py └── proxy_protocol.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | *tests* 4 | *scripts* 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | venv 3 | .envrc 4 | __pycache__ 5 | *.egg-info 6 | dist 7 | docs/_build 8 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | python: 4 | install: 5 | - method: pip 6 | path: . 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### Release 1.2.1 4 | 5 | #### Bugfixes 6 | - drop first info event without a version 7 | 8 | ### Release 1.2.0 9 | 10 | #### Features 11 | - add support for uptime kuma 1.23.0 and 1.23.1 12 | 13 | #### Bugfixes 14 | - remove `name` from maintenance monitors and status pages 15 | - rstip url globally 16 | - convert sendUrl from bool to int 17 | - validate accepted status codes types 18 | 19 | ### Release 1.1.0 20 | 21 | #### Features 22 | - add support for uptime kuma 1.22.0 and 1.22.1 23 | 24 | ### Release 1.0.1 25 | 26 | #### Bugfixes 27 | - fix ValueError if monitor authMethod is None 28 | 29 | ### Release 1.0.0 30 | 31 | #### Features 32 | - add `ssl_verify` parameter 33 | - add `wait_events` parameter 34 | - implement context manager for UptimeKumaApi class 35 | - drop Python 3.6 support 36 | - implement `get_monitor_status` helper method 37 | - implement timeouts for all methods (`timeout` parameter) 38 | - add support for uptime kuma 1.21.3 39 | - drop support for Uptime Kuma versions < 1.21.3 40 | - check for required notification arguments 41 | - raise exception when deleting an element that does not exist 42 | - replace raw return values with enum values 43 | 44 | #### Bugfixes 45 | - adjust monitor `status` type to allow all used values 46 | - fix memory leak 47 | 48 | #### BREAKING CHANGES 49 | - Python 3.7+ required 50 | - maintenance parameter `timezone` renamed to `timezoneOption` 51 | - Removed the `wait_timeout` parameter. Use the new `timeout` parameter instead. The `timeout` parameter specifies how many seconds the client should wait for the connection, an expected event or a server response. 52 | - changed return values of methods `get_heartbeats`, `get_important_heartbeats`, `avg_ping`, `uptime`, `cert_info` 53 | - Uptime Kuma versions < 1.21.3 are not supported in uptime-kuma-api 1.0.0+ 54 | - Removed the `get_heartbeat` method. This method was never intended to retrieve information. Use `get_heartbeats` or `get_important_heartbeats` instead. 55 | - Types of return values changed to enum values: 56 | - monitor: `type` (str -> MonitorType), `status` (bool -> MonitorStatus), `authMethod` (str -> AuthMethod) 57 | - notification: `type` (str -> NotificationType) 58 | - docker host: `dockerType` (str -> DockerType) 59 | - status page: `style` (str -> IncidentStyle) 60 | - maintenance: `strategy` (str -> MaintenanceStrategy) 61 | - proxy: `protocol` (str -> ProxyProtocol) 62 | 63 | ### Release 0.13.0 64 | 65 | #### Feature 66 | - add support for uptime kuma 1.21.2 67 | - implement custom socketio headers 68 | 69 | #### Bugfix 70 | - do not wait for events that have already arrived 71 | 72 | ### Release 0.12.0 73 | 74 | #### Feature 75 | - add support for uptime kuma 1.21.1 76 | 77 | ### Release 0.11.0 78 | 79 | #### Feature 80 | - add support for uptime kuma 1.21.0 81 | 82 | ### Release 0.10.0 83 | 84 | #### Feature 85 | - add support for uptime kuma 1.20.0 86 | 87 | ### Release 0.9.0 88 | 89 | #### Feature 90 | - add support for uptime kuma 1.19.5 91 | 92 | ### Release 0.8.0 93 | 94 | #### Feature 95 | - add support for uptime kuma 1.19.3 96 | 97 | ### Release 0.7.1 98 | 99 | #### Bugfix 100 | - remove unsupported type hints on old python versions 101 | 102 | ### Release 0.7.0 103 | 104 | #### Feature 105 | - add support for uptime kuma 1.19.2 106 | 107 | #### Bugfix 108 | - skip condition check for None values 109 | 110 | ### Release 0.6.0 111 | 112 | #### Feature 113 | - add parameter `wait_timeout` to adjust connection timeout 114 | 115 | ### Release 0.5.2 116 | 117 | #### Bugfix 118 | - add type to notification provider options 119 | 120 | ### Release 0.5.1 121 | 122 | #### Bugfix 123 | - remove required notification provider args check 124 | 125 | ### Release 0.5.0 126 | 127 | #### Feature 128 | - support for uptime kuma 1.18.3 129 | 130 | ### Release 0.4.0 131 | 132 | #### Feature 133 | - support for uptime kuma 1.18.1 / 1.18.2 134 | 135 | #### Bugfix 136 | - update event list data after changes 137 | 138 | ### Release 0.3.0 139 | 140 | #### Feature 141 | - support autoLogin for enabled disableAuth 142 | 143 | #### Bugfix 144 | - set_settings password is only required if disableAuth is enabled 145 | - increase event wait time to receive the slow statusPageList event 146 | 147 | ### Release 0.2.2 148 | 149 | #### Bugfix 150 | - remove `tags` from monitor input 151 | - convert monitor notificationIDList only once 152 | 153 | ### Release 0.2.1 154 | 155 | #### Bugfix 156 | - generate pushToken on push monitor save 157 | - convert monitor notificationIDList return value 158 | 159 | ### Release 0.2.0 160 | 161 | #### Feature 162 | - support for uptime kuma 1.18.0 163 | 164 | #### Bugfix 165 | - convert values on monitor edit 166 | 167 | ### Release 0.1.1 168 | 169 | #### Bugfix 170 | - implement 2FA login 171 | - allow to add monitors to status pages 172 | - do not block certain methods 173 | 174 | ### Release 0.1.0 175 | 176 | - initial release 177 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lucas Held 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uptime-kuma-api 2 | 3 | A wrapper for the Uptime Kuma Socket.IO API 4 | --- 5 | uptime-kuma-api is a Python wrapper for the [Uptime Kuma](https://github.com/louislam/uptime-kuma) Socket.IO API. 6 | 7 | This package was developed to configure Uptime Kuma with Ansible. The Ansible collection can be found at https://github.com/lucasheld/ansible-uptime-kuma. 8 | 9 | Python version 3.7+ is required. 10 | 11 | Supported Uptime Kuma versions: 12 | 13 | | Uptime Kuma | uptime-kuma-api | 14 | |-----------------|-----------------| 15 | | 1.21.3 - 1.23.2 | 1.0.0 - 1.2.1 | 16 | | 1.17.0 - 1.21.2 | 0.1.0 - 0.13.0 | 17 | 18 | Installation 19 | --- 20 | uptime-kuma-api is available on the [Python Package Index (PyPI)](https://pypi.org/project/uptime-kuma-api/). 21 | 22 | You can install it using pip: 23 | 24 | ``` 25 | pip install uptime-kuma-api 26 | ``` 27 | 28 | Documentation 29 | --- 30 | The API Reference is available on [Read the Docs](https://uptime-kuma-api.readthedocs.io). 31 | 32 | Example 33 | --- 34 | Once you have installed the python package, you can use it to communicate with an Uptime Kuma instance. 35 | 36 | To do so, import `UptimeKumaApi` from the library and specify the Uptime Kuma server url (e.g. 'http://127.0.0.1:3001'), username and password to initialize the connection. 37 | 38 | ```python 39 | >>> from uptime_kuma_api import UptimeKumaApi, MonitorType 40 | >>> api = UptimeKumaApi('INSERT_URL') 41 | >>> api.login('INSERT_USERNAME', 'INSERT_PASSWORD') 42 | ``` 43 | 44 | Now you can call one of the existing methods of the instance. For example create a new monitor: 45 | 46 | ```python 47 | >>> result = api.add_monitor(type=MonitorType.HTTP, name="Google", url="https://google.com") 48 | >>> print(result) 49 | {'msg': 'Added Successfully.', 'monitorId': 1} 50 | ``` 51 | 52 | At the end, the connection to the API must be disconnected so that the program does not block. 53 | 54 | ```python 55 | >>> api.disconnect() 56 | ``` 57 | 58 | With a context manager, the disconnect method is called automatically: 59 | 60 | ```python 61 | from uptime_kuma_api import UptimeKumaApi 62 | 63 | with UptimeKumaApi('INSERT_URL') as api: 64 | api.login('INSERT_USERNAME', 'INSERT_PASSWORD') 65 | api.add_monitor( 66 | type=MonitorType.HTTP, 67 | name="Google", 68 | url="https://google.com" 69 | ) 70 | ``` 71 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==5.3.0 2 | pyotp==2.8.0 3 | Jinja2==3.1.2 4 | BeautifulSoup4==4.12.2 5 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | 4 | Main Interface 5 | -------------- 6 | 7 | .. module:: uptime_kuma_api 8 | 9 | .. autoclass:: UptimeKumaApi 10 | :inherited-members: 11 | 12 | 13 | Enums 14 | ----- 15 | 16 | .. autoclass:: AuthMethod 17 | :members: 18 | 19 | .. autoclass:: MonitorType 20 | :members: 21 | 22 | .. autoclass:: MonitorStatus 23 | :members: 24 | 25 | .. autoclass:: NotificationType 26 | :members: 27 | 28 | .. autoclass:: ProxyProtocol 29 | :members: 30 | 31 | .. autoclass:: IncidentStyle 32 | :members: 33 | 34 | .. autoclass:: DockerType 35 | :members: 36 | 37 | .. autoclass:: MaintenanceStrategy 38 | :members: 39 | 40 | 41 | Exceptions 42 | ---------- 43 | 44 | .. autoexception:: UptimeKumaException 45 | 46 | .. autoexception:: Timeout 47 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | import os 7 | import sys 8 | 9 | sys.path.insert(0, os.path.abspath("..")) 10 | import uptime_kuma_api 11 | 12 | # -- Project information ----------------------------------------------------- 13 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 14 | 15 | project = 'uptime-kuma-api' 16 | copyright = '2023, Lucas Held' 17 | author = 'Lucas Held' 18 | 19 | version = uptime_kuma_api.__version__ 20 | release = uptime_kuma_api.__version__ 21 | 22 | 23 | # -- General configuration --------------------------------------------------- 24 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 25 | 26 | extensions = [ 27 | "sphinx.ext.autodoc" 28 | ] 29 | 30 | templates_path = ['_templates'] 31 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 32 | 33 | toc_object_entries_show_parents = "hide" 34 | autodoc_member_order = "bysource" 35 | add_module_names = False 36 | 37 | 38 | # -- Options for HTML output ------------------------------------------------- 39 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 40 | 41 | html_theme = 'alabaster' 42 | html_static_path = ['_static'] 43 | 44 | html_theme_options = { 45 | "show_powered_by": False, 46 | "github_user": "lucasheld", 47 | "github_repo": "uptime-kuma-api", 48 | "github_banner": True, 49 | "show_related": False, 50 | "note_bg": "#FFF59C", 51 | "github_button": True, 52 | "github_type": "star" 53 | } 54 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. uptime-kuma-api documentation master file, created by 2 | sphinx-quickstart on Thu Dec 15 11:58:11 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | uptime-kuma-api 7 | =============== 8 | 9 | Release v\ |version|. (:ref:`Installation `) 10 | 11 | A Python wrapper for the Uptime Kuma Socket.IO API 12 | 13 | ------------------- 14 | 15 | 16 | Indices and tables 17 | ------------------ 18 | 19 | * :ref:`genindex` 20 | * :ref:`search` 21 | 22 | 23 | API Reference 24 | ------------- 25 | 26 | If you are looking for information on a specific function, class, or method, 27 | this part of the documentation is for you. 28 | 29 | .. toctree:: 30 | :maxdepth: 3 31 | 32 | api 33 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installation 4 | ------------ 5 | 6 | To install uptime-kuma-api, run this command in your terminal:: 7 | 8 | $ pip install uptime-kuma-api 9 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-socketio[client]>=5.0.0 2 | packaging 3 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | version="$1" 4 | if [ $version ] 5 | then 6 | versions=("$version") 7 | else 8 | versions=(1.23.2 1.23.0 1.22.1 1.22.0 1.21.3) 9 | fi 10 | 11 | for version in ${versions[*]} 12 | do 13 | echo "Starting uptime kuma $version..." 14 | docker run -d -it --rm -p 3001:3001 --name uptimekuma "louislam/uptime-kuma:$version" > /dev/null 15 | 16 | while [[ "$(curl -s -L -o /dev/null -w ''%{http_code}'' 127.0.0.1:3001)" != "200" ]] 17 | do 18 | sleep 0.5 19 | done 20 | 21 | echo "Running tests..." 22 | python -m unittest discover -s tests 23 | 24 | echo "Stopping uptime kuma..." 25 | docker stop uptimekuma > /dev/null 26 | sleep 1 27 | 28 | echo '' 29 | done 30 | -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | uptime-kuma 2 | uptime-kuma-old 3 | -------------------------------------------------------------------------------- /scripts/build_inputs.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import re 3 | from bs4 import BeautifulSoup 4 | 5 | from utils import deduplicate_list, parse_vue_template, diff, type_html_to_py 6 | 7 | 8 | input_ignores = { 9 | "settings": [ 10 | "$root.styleElapsedTime" 11 | ] 12 | } 13 | 14 | 15 | def parse_model_keys(content, object_name): 16 | soup = BeautifulSoup(content, "html.parser") 17 | 18 | soup_inputs = soup.find_all(attrs={"v-model": True}) 19 | inputs = [] 20 | for soup_input in soup_inputs: 21 | key = soup_input["v-model"] 22 | if key in input_ignores.get(object_name, []): 23 | continue 24 | else: 25 | key = re.sub(r'^' + object_name + r'\.', "", key) 26 | type_ = soup_input.get("type", "text") 27 | type_ = type_html_to_py(type_) 28 | if type_ == "bool": 29 | value = True if soup_input.get("checked") else False 30 | else: 31 | value = soup_input.get("value") 32 | inputs.append({ 33 | "key": key, 34 | "type": type_, 35 | "default": value, 36 | }) 37 | return inputs 38 | 39 | 40 | def parse_proxy_keys(root): 41 | content = parse_vue_template(f"{root}/src/components/ProxyDialog.vue") 42 | keys = parse_model_keys(content, "proxy") 43 | return keys 44 | 45 | 46 | def parse_notification_keys(root): 47 | content = parse_vue_template(f"{root}/src/components/NotificationDialog.vue") 48 | keys = parse_model_keys(content, "notification") 49 | return keys 50 | 51 | 52 | def parse_settings_keys(root): 53 | all_keys = [] 54 | for path in glob.glob(f'{root}/src/components/settings/*'): 55 | content = parse_vue_template(path) 56 | keys = parse_model_keys(content, "settings") 57 | all_keys.extend(keys) 58 | all_keys = deduplicate_list(all_keys) 59 | return all_keys 60 | 61 | 62 | def parse_monitor_keys(root): 63 | content = parse_vue_template(f"{root}/src/pages/EditMonitor.vue") 64 | keys = parse_model_keys(content, "monitor") 65 | return keys 66 | 67 | 68 | def parse_status_page_keys(root): 69 | all_keys = ["id"] 70 | 71 | content = parse_vue_template(f"{root}/src/pages/StatusPage.vue") 72 | keys = parse_model_keys(content, "config") 73 | all_keys.extend(keys) 74 | 75 | content = parse_vue_template(f"{root}/src/pages/ManageStatusPage.vue") 76 | keys = parse_model_keys(content, "statusPage") 77 | all_keys.extend(keys) 78 | 79 | all_keys = deduplicate_list(all_keys) 80 | return all_keys 81 | 82 | 83 | def parse_maintenance_keys(root): 84 | content = parse_vue_template(f"{root}/src/pages/EditMaintenance.vue") 85 | keys = parse_model_keys(content, "maintenance") 86 | return keys 87 | 88 | 89 | def main(): 90 | root_old = "uptime-kuma-old" 91 | root_new = "uptime-kuma" 92 | 93 | for name, func in [ 94 | ["proxy", parse_proxy_keys], 95 | ["notification", parse_notification_keys], 96 | ["settings", parse_settings_keys], 97 | ["monitor", parse_monitor_keys], 98 | ["status_page", parse_status_page_keys], 99 | ["maintenance", parse_maintenance_keys], 100 | ]: 101 | keys_old = func(root_old) 102 | keys_new = func(root_new) 103 | print(f"{name}:") 104 | diff(keys_old, keys_new) 105 | 106 | 107 | if __name__ == "__main__": 108 | main() 109 | -------------------------------------------------------------------------------- /scripts/build_models.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from utils import deduplicate_list, diff 4 | 5 | 6 | def parse_json_keys(data): 7 | keys = [] 8 | for line in data.split("\n"): 9 | line = line.strip() 10 | if not line: 11 | continue 12 | match = re.match(r'^([^:]+):', line) # example: "type: this.type," 13 | if match: 14 | key = match.group(1) 15 | else: 16 | key = line.rstrip(",") # example: "notificationIDList," 17 | keys.append(key) 18 | return keys 19 | 20 | 21 | def parse_heartbeat(root): 22 | with open(f'{root}/server/model/heartbeat.js') as f: 23 | content = f.read() 24 | all_keys = [] 25 | match = re.search(r'toJSON\(\) {\s+return.*{([^}]+)}', content) 26 | data = match.group(1) 27 | keys = parse_json_keys(data) 28 | all_keys.extend(keys) 29 | match = re.search(r'toPublicJSON\(\) {\s+return.*{([^}]+)}', content) 30 | data = match.group(1) 31 | keys = parse_json_keys(data) 32 | all_keys.extend(keys) 33 | all_keys = deduplicate_list(all_keys) 34 | return all_keys 35 | 36 | 37 | def parse_incident(root): 38 | with open(f'{root}/server/model/incident.js') as f: 39 | content = f.read() 40 | match = re.search(r'toPublicJSON\(\) {\s+return.*{([^}]+)}', content) 41 | data = match.group(1) 42 | keys = parse_json_keys(data) 43 | return keys 44 | 45 | 46 | def parse_monitor(root): 47 | # todo: toPublicJSON ??? 48 | with open(f'{root}/server/model/monitor.js') as f: 49 | content = f.read() 50 | matches = re.findall(r'data = {([^}]+)}', content) 51 | all_keys = [] 52 | for match in matches: 53 | keys = parse_json_keys(match) 54 | keys = [i for i in keys if i != "...data"] 55 | all_keys.extend(keys) 56 | all_keys = deduplicate_list(all_keys) 57 | return all_keys 58 | 59 | 60 | def parse_proxy(root): 61 | with open(f'{root}/server/model/proxy.js') as f: 62 | content = f.read() 63 | match = re.search(r'toJSON\(\) {\s+return.*{([^}]+)}', content) 64 | data = match.group(1) 65 | keys = parse_json_keys(data) 66 | return keys 67 | 68 | 69 | # def parse_function(regex_name, content): 70 | # match = re.search(regex_name, content) 71 | # name = match.group(0) 72 | # rest = "".join(content.split(name)[1:]) 73 | # 74 | # brackets = 0 75 | # opening_bracket_found = False 76 | # code = "" 77 | # for i in rest: 78 | # code += i 79 | # if i == "{": 80 | # opening_bracket_found = True 81 | # brackets += 1 82 | # if i == "}": 83 | # opening_bracket_found = True 84 | # brackets -= 1 85 | # if opening_bracket_found and brackets == 0: 86 | # break 87 | # return code 88 | 89 | 90 | # # input (add, edit proxy) 91 | # def parse_proxy2(): 92 | # with open(f'{root}/server/proxy.js') as f: 93 | # content = f.read() 94 | # 95 | # code = parse_function(r'async save\([^)]+\) ', content) 96 | # keys = parse_object_keys(code, "proxy") 97 | # return keys 98 | 99 | 100 | def parse_status_page(root): 101 | with open(f'{root}/server/model/status_page.js') as f: 102 | content = f.read() 103 | all_keys = [] 104 | match = re.search(r'toJSON\(\) {\s+return.*{([^}]+)}', content) 105 | data = match.group(1) 106 | keys = parse_json_keys(data) 107 | all_keys.extend(keys) 108 | match = re.search(r'toPublicJSON\(\) {\s+return.*{([^}]+)}', content) 109 | data = match.group(1) 110 | keys = parse_json_keys(data) 111 | all_keys.extend(keys) 112 | all_keys = deduplicate_list(all_keys) 113 | return all_keys 114 | 115 | 116 | def parse_tag(root): 117 | with open(f'{root}/server/model/tag.js') as f: 118 | content = f.read() 119 | match = re.search(r'toJSON\(\) {\s+return.*{([^}]+)}', content) 120 | data = match.group(1) 121 | keys = parse_json_keys(data) 122 | return keys 123 | 124 | 125 | if __name__ == "__main__": 126 | root_old = "uptime-kuma-old" 127 | root_new = "uptime-kuma" 128 | 129 | for name, func in [ 130 | ["heartbeat", parse_heartbeat], 131 | ["incident", parse_incident], 132 | ["monitor", parse_monitor], 133 | ["proxy", parse_proxy], 134 | ["status page", parse_status_page], 135 | ["tag", parse_tag], 136 | ]: 137 | keys_old = func(root_old) 138 | keys_new = func(root_new) 139 | print(f"{name}:") 140 | diff(keys_old, keys_new) 141 | 142 | 143 | # TODO: 144 | # https://github.com/louislam/uptime-kuma/blob/2adb142ae25984ecebfa4b51c739fec5e492763a/server/proxy.js#L20 145 | # https://github.com/louislam/uptime-kuma/blob/239611a016a85712305100818d4c7b88a14664a9/server/socket-handlers/status-page-socket-handler.js#L118 146 | -------------------------------------------------------------------------------- /scripts/build_monitor_types.py: -------------------------------------------------------------------------------- 1 | from bs4 import BeautifulSoup 2 | 3 | from utils import parse_vue_template, write_to_file 4 | 5 | 6 | titles = { 7 | "http": "HTTP(s)", 8 | "port": "TCP Port", 9 | "ping": "Ping", 10 | "keyword": "HTTP(s) - Keyword", 11 | "grpc-keyword": "gRPC(s) - Keyword", 12 | "dns": "DNS", 13 | "docker": "Docker Container", 14 | "push": "Push", 15 | "steam": "Steam Game Server", 16 | "gamedig": "GameDig", 17 | "mqtt": "MQTT", 18 | "sqlserver": "Microsoft SQL Server", 19 | "postgres": "PostgreSQL", 20 | "mysql": "MySQL/MariaDB", 21 | "mongodb": "MongoDB", 22 | "radius": "Radius", 23 | "redis": "Redis", 24 | "group": "Group", 25 | "json-query": "HTTP(s) - Json Query", 26 | "real-browser": "HTTP(s) - Browser Engine (Chrome/Chromium)", 27 | "kafka-producer": "Kafka Producer", 28 | "tailscale-ping": "Tailscale Ping" 29 | } 30 | 31 | 32 | def parse_monitor_types(): 33 | content = parse_vue_template("uptime-kuma/src/pages/EditMonitor.vue") 34 | 35 | soup = BeautifulSoup(content, "html.parser") 36 | select = soup.find("select", id="type") 37 | options = select.find_all("option") 38 | 39 | types = {} 40 | for o in options: 41 | type_ = o.attrs["value"] 42 | types[type_] = { 43 | "value": type_, 44 | "title": titles[type_] 45 | } 46 | return types 47 | 48 | 49 | monitor_types = parse_monitor_types() 50 | 51 | write_to_file( 52 | "monitor_type.py.j2", "./../uptime_kuma_api/monitor_type.py", 53 | monitor_types=monitor_types 54 | ) 55 | -------------------------------------------------------------------------------- /scripts/build_notification_docstring.py: -------------------------------------------------------------------------------- 1 | from uptime_kuma_api import notification_provider_options 2 | 3 | data = { 4 | "lunaseaTarget": """Allowed values: "device", "user".""", 5 | 6 | "lunaseaUserID": """User ID.""", 7 | 8 | "lunaseaDevice": """Device ID.""", 9 | 10 | "pagertreeAutoResolve": """ 11 | 12 | Available values are: 13 | 14 | - ``0``: Do Nothing 15 | - ``resolve``: Auto Resolve""", 16 | 17 | "pagertreeUrgency": """ 18 | 19 | Available values are: 20 | 21 | - ``silent``: Silent 22 | - ``low``: Low 23 | - ``medium``: Medium 24 | - ``high``: High 25 | - ``critical``: Critical""", 26 | 27 | "promosmsAllowLongSMS": "Allow long SMS.", 28 | 29 | "promosmsPhoneNumber": "Phone number (for Polish recipient You can skip area codes).", 30 | 31 | "promosmsSMSType": """ 32 | 33 | Available values are: 34 | 35 | - ``0``: SMS FLASH - Message will automatically show on recipient device. Limited only to Polish recipients. 36 | - ``1``: SMS ECO - cheap but slow and often overloaded. Limited only to Polish recipients. 37 | - ``3``: SMS FULL - Premium tier of SMS, You can use your Sender Name (You need to register name first). Reliable for alerts. 38 | - ``4``: SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).""", 39 | 40 | "smseagleEncoding": "True to send messages in unicode.", 41 | 42 | "smseaglePriority": "Message priority (0-9, default = 0).", 43 | 44 | "smseagleRecipientType": """Recipient type. 45 | 46 | Available values are: 47 | 48 | - ``smseagle-to``: Phone number(s) 49 | - ``smseagle-group``: Phonebook group name(s) 50 | - ``smseagle-contact``: Phonebook contact name(s)""", 51 | 52 | "smseagleToken": "API Access token.", 53 | 54 | "smseagleRecipient": "Recipient(s) (multiple must be separated with comma).", 55 | 56 | "smseagleUrl": "Your SMSEagle device URL.", 57 | 58 | "splunkAutoResolve": """Auto resolve or acknowledged. 59 | 60 | Available values are: 61 | 62 | - ``0``: do nothing 63 | - ``ACKNOWLEDGEMENT``: auto acknowledged 64 | - ``RECOVERY``: auto resolve""", 65 | 66 | "splunkSeverity": """Severity. 67 | 68 | Available values are: 69 | 70 | - ``INFO`` 71 | - ``WARNING`` 72 | - ``CRITICAL``""", 73 | 74 | "splunkRestURL": "Splunk Rest URL.", 75 | 76 | "opsgeniePriority": "Priority. Available values are numbers between ``1`` and ``5``.", 77 | 78 | "opsgenieRegion": """Region. Available values are: 79 | 80 | - ``us``: US (Default) 81 | - ``eu``: EU""", 82 | 83 | "opsgenieApiKey": "API Key.", 84 | 85 | "twilioAccountSID": "Account SID.", 86 | 87 | "twilioAuthToken": "Auth Token.", 88 | 89 | "twilioToNumber": "To Number.", 90 | 91 | "twilioFromNumber": "From Number.", 92 | 93 | "pushoverttl": "Message TTL (Seconds).", 94 | 95 | "ntfyaccesstoken": "Access Token.", 96 | 97 | "ntfyAuthenticationMethod": "Authentication Method.", 98 | } 99 | 100 | for provider in notification_provider_options: 101 | provider_options = notification_provider_options[provider] 102 | for option in provider_options: 103 | type_ = provider_options[option]["type"] 104 | required = provider_options[option]["required"] 105 | text = data.get(option) 106 | line = f":param {type_}{', optional' if required else ''} {option}: Notification option for ``type`` :attr:`~.NotificationType.{provider.name}`." 107 | if text: 108 | line += f" {text}" 109 | print(line) 110 | -------------------------------------------------------------------------------- /scripts/build_notifications.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import re 3 | 4 | from bs4 import BeautifulSoup 5 | 6 | from utils import deduplicate_list, write_to_file, type_html_to_py 7 | 8 | 9 | # deprecated or wrong inputs 10 | ignored_inputs = { 11 | "slack": [ 12 | "slackbutton" 13 | ], 14 | "rocket.chat": [ 15 | "rocketbutton" 16 | ], 17 | "octopush": [ 18 | "octopushDMLogin", 19 | "octopushDMAPIKey", 20 | "octopushDMPhoneNumber", 21 | "octopushDMSenderName", 22 | "octopushDMSMSType" 23 | ], 24 | "Splunk": [ 25 | "pagerdutyIntegrationKey" 26 | ] 27 | } 28 | 29 | input_overwrites = { 30 | "showAdditionalHeadersField": "webhookAdditionalHeaders" 31 | } 32 | 33 | titles = { 34 | "alerta": "Alerta", 35 | "AlertNow": "AlertNow", 36 | "apprise": "Apprise (Support 50+ Notification services)", 37 | "Bark": "Bark", 38 | "clicksendsms": "ClickSend SMS", 39 | "discord": "Discord", 40 | "GoogleChat": "Google Chat (Google Workspace)", 41 | "gorush": "Gorush", 42 | "gotify": "Gotify", 43 | "HomeAssistant": "Home Assistant", 44 | "Kook": "Kook", 45 | "line": "LINE Messenger", 46 | "LineNotify": "LINE Notify", 47 | "lunasea": "LunaSea", 48 | "matrix": "Matrix", 49 | "mattermost": "Mattermost", 50 | "ntfy": "Ntfy", 51 | "octopush": "Octopush", 52 | "OneBot": "OneBot", 53 | "Opsgenie": "Opsgenie", 54 | "PagerDuty": "PagerDuty", 55 | "PagerTree": "PagerTree", 56 | "pushbullet": "Pushbullet", 57 | "PushByTechulus": "Push by Techulus", 58 | "pushover": "Pushover", 59 | "pushy": "Pushy", 60 | "rocket.chat": "Rocket.Chat", 61 | "signal": "Signal", 62 | "slack": "Slack", 63 | "squadcast": "SquadCast", 64 | "SMSEagle": "SMSEagle", 65 | "smtp": "Email (SMTP)", 66 | "stackfield": "Stackfield", 67 | "teams": "Microsoft Teams", 68 | "telegram": "Telegram", 69 | "twilio": "Twilio", 70 | "Splunk": "Splunk", 71 | "webhook": "Webhook", 72 | "GoAlert": "GoAlert", 73 | "ZohoCliq": "ZohoCliq", 74 | "AliyunSMS": "AliyunSMS", 75 | "DingDing": "DingDing", 76 | "Feishu": "Feishu", 77 | "FreeMobile": "FreeMobile (mobile.free.fr)", 78 | "PushDeer": "PushDeer", 79 | "promosms": "PromoSMS", 80 | "serwersms": "SerwerSMS.pl", 81 | "SMSManager": "SmsManager (smsmanager.cz)", 82 | "WeCom": "WeCom", 83 | "ServerChan": "ServerChan", 84 | "nostr": "Nostr", 85 | "FlashDuty": "FlashDuty", 86 | "smsc": "SMSC", 87 | } 88 | 89 | 90 | def build_notification_providers(): 91 | root = "uptime-kuma" 92 | providers = {} 93 | 94 | # get providers and input names 95 | for path in sorted(glob.glob(f'{root}/server/notification-providers/*')): 96 | with open(path) as f: 97 | content = f.read() 98 | match = re.search(r'class [^ ]+ extends NotificationProvider {', content) 99 | if match: 100 | match = re.search(r'name = "([^"]+)";', content) 101 | name = match.group(1) 102 | 103 | inputs = re.findall(r'notification\??\.([^ ,.;})\]]+)', content) 104 | inputs = deduplicate_list(inputs) 105 | inputs = [i.strip() for i in inputs] 106 | 107 | providers[name] = { 108 | "title": titles[name], 109 | "inputs": {}, 110 | } 111 | for input_ in inputs: 112 | if input_ not in ignored_inputs.get(name, []): 113 | providers[name]["inputs"][input_] = {} 114 | 115 | # get inputs 116 | for path in glob.glob(f'{root}/src/components/notifications/*'): 117 | if path.endswith("index.js"): 118 | continue 119 | with open(path) as f: 120 | content = f.read() 121 | match = re.search(r'', content, re.MULTILINE) 122 | html = match.group(0) 123 | soup = BeautifulSoup(html, "html.parser") 124 | inputs = soup.find_all(attrs={"v-model": True}) 125 | for input_ in inputs: 126 | conditions = {} 127 | attrs = input_.attrs 128 | v_model = attrs.get("v-model") 129 | 130 | v_model_overwrite = input_overwrites.get(v_model) 131 | if v_model_overwrite: 132 | param_name = v_model_overwrite 133 | else: 134 | param_name = re.match(r'\$parent.notification.(.*)$', v_model).group(1) 135 | 136 | type_ = attrs.get("type") 137 | type_ = type_html_to_py(type_) 138 | 139 | required_true_values = ['', 'true'] 140 | if attrs.get("required") in required_true_values or attrs.get(":required") in required_true_values: 141 | required = True 142 | else: 143 | required = False 144 | 145 | min_ = attrs.get("min") 146 | if min_: 147 | conditions["min"] = int(min_) 148 | 149 | max_ = attrs.get("max") 150 | if max_: 151 | conditions["max"] = int(max_) 152 | 153 | # find provider inputs dict 154 | input_found = False 155 | for name in list(providers.keys()): 156 | inputs = providers[name]["inputs"] 157 | for provider_input in inputs: 158 | if provider_input == param_name: 159 | input_found = True 160 | providers[name]["inputs"][provider_input] = { 161 | "conditions": conditions, 162 | "type": type_, 163 | "required": required 164 | } 165 | assert input_found 166 | return providers 167 | 168 | 169 | notification_providers = build_notification_providers() 170 | 171 | notification_provider_conditions = {} 172 | for notification_provider in notification_providers: 173 | for notification_provider_input_name in notification_providers[notification_provider]["inputs"]: 174 | notification_provider_input = notification_providers[notification_provider]["inputs"][notification_provider_input_name] 175 | if notification_provider_input["conditions"]: 176 | notification_provider_conditions[notification_provider_input_name] = notification_provider_input["conditions"] 177 | 178 | write_to_file( 179 | "notification_providers.py.j2", "./../uptime_kuma_api/notification_providers.py", 180 | notification_providers=notification_providers, 181 | notification_provider_conditions=notification_provider_conditions 182 | ) 183 | -------------------------------------------------------------------------------- /scripts/monitor_type.py.j2: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class MonitorType(str, Enum): 5 | """Enumerate monitor types.""" 6 | {{""}} 7 | {%- for type_ in monitor_types.values() %} 8 | {{ type_["value"].upper().replace("-", "_") }} = "{{ type_["value"] }}" 9 | """{{ type_["title"] }}""" 10 | {% endfor -%} 11 | 12 | -------------------------------------------------------------------------------- /scripts/notification_providers.py.j2: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class NotificationType(str, Enum): 5 | """Enumerate notification types.""" 6 | {% for provider in notification_providers %} 7 | {{ provider.upper().replace(".", "_") }} = "{{ provider }}" 8 | """{{ notification_providers[provider]["title"] }}""" 9 | {% endfor %} 10 | 11 | notification_provider_options = { 12 | {%- for provider in notification_providers %} 13 | NotificationType.{{ provider.upper().replace(".", "_") }}: dict( 14 | {%- for input_name in notification_providers[provider]["inputs"] %} 15 | {%- set input = notification_providers[provider]["inputs"][input_name] %} 16 | {{ input_name }}=dict(type="{{ input["type"] }}", required={{ input["required"] }}), 17 | {%- endfor %} 18 | ), 19 | {%- endfor %} 20 | } 21 | 22 | notification_provider_conditions = dict( 23 | {%- for provider in notification_provider_conditions %} 24 | {{ provider }}=dict( 25 | {%- for key, value in notification_provider_conditions[provider].items() %} 26 | {{ key }}={{ value }}, 27 | {%- endfor %} 28 | ), 29 | {%- endfor %} 30 | ) 31 | 32 | -------------------------------------------------------------------------------- /scripts/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | import jinja2 3 | 4 | 5 | def deduplicate_list(l): 6 | out = [] 7 | for i in l: 8 | if i not in out: 9 | out.append(i) 10 | return out 11 | 12 | 13 | def parse_vue_template(path): 14 | with open(path) as f: 15 | vue = f.read() 16 | match = re.search(r'', vue, re.MULTILINE) 17 | template = match.group(0) 18 | return template 19 | 20 | 21 | def write_to_file(template, destination, **kwargs): 22 | env = jinja2.Environment(loader=jinja2.FileSystemLoader("./")) 23 | template = env.get_template(template) 24 | rendered = template.render(**kwargs) 25 | with open(destination, "w") as f: 26 | f.write(rendered) 27 | 28 | 29 | def diff(old, new): 30 | for i in new: 31 | if i not in old: 32 | print("+", i) 33 | for i in old: 34 | if i not in new: 35 | print("-", i) 36 | print("") 37 | 38 | 39 | def type_html_to_py(type_): 40 | if type_ == "number": 41 | type_ = "int" 42 | elif type_ == "checkbox": 43 | type_ = "bool" 44 | else: 45 | type_ = "str" 46 | return type_ 47 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from codecs import open 3 | import os 4 | import sys 5 | 6 | # "setup.py publish" shortcut. 7 | if sys.argv[-1] == "publish": 8 | os.system("rm dist/*") 9 | os.system("python setup.py sdist") 10 | os.system("twine upload dist/*") 11 | sys.exit() 12 | 13 | info = {} 14 | here = os.path.abspath(os.path.dirname(__file__)) 15 | with open(os.path.join(here, "uptime_kuma_api", "__version__.py"), "r", "utf-8") as f: 16 | exec(f.read(), info) 17 | 18 | with open("README.md", "r", "utf-8") as f: 19 | readme = f.read() 20 | 21 | setup( 22 | name=info["__title__"], 23 | version=info["__version__"], 24 | description="A python wrapper for the Uptime Kuma WebSocket API", 25 | long_description=readme, 26 | long_description_content_type="text/markdown", 27 | url="https://github.com/lucasheld/uptime-kuma-api", 28 | author=info["__author__"], 29 | author_email="lucasheld@hotmail.de", 30 | license=info["__license__"], 31 | packages=["uptime_kuma_api"], 32 | python_requires=">=3.7, <4", 33 | install_requires=[ 34 | "python-socketio[client]>=5.0.0", 35 | "packaging" 36 | ], 37 | classifiers=[ 38 | "Development Status :: 5 - Production/Stable", 39 | "Environment :: Web Environment", 40 | "Intended Audience :: Developers", 41 | "License :: OSI Approved :: MIT License", 42 | "Natural Language :: English", 43 | "Operating System :: OS Independent", 44 | "Programming Language :: Python", 45 | "Programming Language :: Python :: 3", 46 | "Programming Language :: Python :: 3.7", 47 | "Programming Language :: Python :: 3.8", 48 | "Programming Language :: Python :: 3.9", 49 | "Programming Language :: Python :: 3.10", 50 | "Programming Language :: Python :: 3.11", 51 | "Programming Language :: Python :: 3 :: Only", 52 | "Topic :: Internet :: WWW/HTTP", 53 | "Topic :: Software Development :: Libraries" 54 | ] 55 | ) 56 | -------------------------------------------------------------------------------- /tests/test_2fa.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from urllib import parse 3 | 4 | import pyotp 5 | 6 | from uptime_kuma_test_case import UptimeKumaTestCase 7 | 8 | 9 | def parse_secret(uri): 10 | query = parse.urlsplit(uri).query 11 | params = dict(parse.parse_qsl(query)) 12 | return params["secret"] 13 | 14 | 15 | def generate_token(secret): 16 | totp = pyotp.TOTP(secret) 17 | return totp.now() 18 | 19 | 20 | class Test2FA(UptimeKumaTestCase): 21 | def test_2fa(self): 22 | # check 2fa is disabled 23 | r = self.api.twofa_status() 24 | self.assertEqual(r["status"], False) 25 | 26 | # prepare 2fa 27 | r = self.api.prepare_2fa(self.password) 28 | uri = r["uri"] 29 | self.assertTrue(uri.startswith("otpauth://totp/")) 30 | secret = parse_secret(uri) 31 | 32 | # verify token 33 | token = generate_token(secret) 34 | r = self.api.verify_token(token, self.password) 35 | self.assertEqual(r["valid"], True) 36 | 37 | # save 2fa 38 | r = self.api.save_2fa(self.password) 39 | self.assertEqual(r["msg"], "2FA Enabled.") 40 | 41 | # check 2fa is enabled 42 | r = self.api.twofa_status() 43 | self.assertEqual(r["status"], True) 44 | 45 | # relogin using the totp token 46 | self.api.logout() 47 | token = generate_token(secret) 48 | self.api.login(self.username, self.password, token) 49 | 50 | # disable 2fa 51 | r = self.api.disable_2fa(self.password) 52 | self.assertEqual(r["msg"], "2FA Disabled.") 53 | 54 | 55 | if __name__ == '__main__': 56 | unittest.main() 57 | -------------------------------------------------------------------------------- /tests/test_api_key.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import UptimeKumaException 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestApiKey(UptimeKumaTestCase): 8 | def test_api_key(self): 9 | # get empty list to make sure that future accesses will also work 10 | self.api.get_api_keys() 11 | 12 | expected = { 13 | "name": "name 1", 14 | "expires": "2023-03-30 12:20:00", 15 | "active": True 16 | } 17 | 18 | # add api key 19 | r = self.api.add_api_key(**expected) 20 | self.assertEqual(r["msg"], "Added Successfully.") 21 | api_key_id = r["keyID"] 22 | 23 | # get api key 24 | api_key = self.api.get_api_key(api_key_id) 25 | self.compare(api_key, expected) 26 | 27 | # get api keys 28 | api_keys = self.api.get_api_keys() 29 | api_key = self.find_by_id(api_keys, api_key_id) 30 | self.assertIsNotNone(api_key) 31 | self.compare(api_key, expected) 32 | 33 | # disable api key 34 | r = self.api.disable_api_key(api_key_id) 35 | self.assertEqual(r["msg"], "Disabled Successfully.") 36 | api_key = self.api.get_api_key(api_key_id) 37 | expected["active"] = False 38 | self.compare(api_key, expected) 39 | 40 | # enable api key 41 | r = self.api.enable_api_key(api_key_id) 42 | self.assertEqual(r["msg"], "Enabled Successfully") 43 | api_key = self.api.get_api_key(api_key_id) 44 | expected["active"] = True 45 | self.compare(api_key, expected) 46 | 47 | # delete api key 48 | r = self.api.delete_api_key(api_key_id) 49 | self.assertEqual(r["msg"], "Deleted Successfully.") 50 | with self.assertRaises(UptimeKumaException): 51 | self.api.get_api_key(api_key_id) 52 | 53 | def test_delete_not_existing_api_key(self): 54 | with self.assertRaises(UptimeKumaException): 55 | self.api.delete_api_key(42) 56 | 57 | 58 | if __name__ == '__main__': 59 | unittest.main() 60 | -------------------------------------------------------------------------------- /tests/test_avg_ping.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_test_case import UptimeKumaTestCase 4 | 5 | 6 | class TestAvgPing(UptimeKumaTestCase): 7 | def test_avg_ping(self): 8 | self.add_monitor() 9 | self.api.avg_ping() 10 | 11 | 12 | if __name__ == '__main__': 13 | unittest.main() 14 | -------------------------------------------------------------------------------- /tests/test_clear.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_test_case import UptimeKumaTestCase 4 | 5 | 6 | class TestClear(UptimeKumaTestCase): 7 | def test_clear_events(self): 8 | monitor_id = self.add_monitor() 9 | self.api.clear_events(monitor_id) 10 | 11 | def test_clear_heartbeats(self): 12 | monitor_id = self.add_monitor() 13 | self.api.clear_heartbeats(monitor_id) 14 | 15 | def test_clear_statistics(self): 16 | self.api.clear_statistics() 17 | 18 | 19 | if __name__ == '__main__': 20 | unittest.main() 21 | -------------------------------------------------------------------------------- /tests/test_database.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_test_case import UptimeKumaTestCase 4 | 5 | 6 | class TestDatabase(UptimeKumaTestCase): 7 | def test_get_database_size(self): 8 | r = self.api.get_database_size() 9 | self.assertIn("size", r) 10 | 11 | def test_shrink_database(self): 12 | self.api.shrink_database() 13 | 14 | 15 | if __name__ == '__main__': 16 | unittest.main() 17 | -------------------------------------------------------------------------------- /tests/test_docker_host.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from packaging.version import parse as parse_version 3 | 4 | from uptime_kuma_api import DockerType, UptimeKumaException 5 | from uptime_kuma_test_case import UptimeKumaTestCase 6 | 7 | 8 | class TestDockerHost(UptimeKumaTestCase): 9 | def test_docker_host(self): 10 | # get empty list to make sure that future accesses will also work 11 | self.api.get_docker_hosts() 12 | 13 | expected_docker_host = { 14 | "name": "name 1", 15 | "dockerType": DockerType.SOCKET, 16 | "dockerDaemon": "/var/run/docker.sock" 17 | } 18 | 19 | # test docker host 20 | if parse_version(self.api.version) != parse_version("1.23.0"): 21 | # test_docker_host does not work in 1.23.0 (https://github.com/louislam/uptime-kuma/issues/3605) 22 | with self.assertRaisesRegex(UptimeKumaException, r'connect ENOENT /var/run/docker.sock'): 23 | self.api.test_docker_host(**expected_docker_host) 24 | 25 | # add docker host 26 | r = self.api.add_docker_host(**expected_docker_host) 27 | self.assertEqual(r["msg"], "Saved") 28 | docker_host_id = r["id"] 29 | 30 | # get docker host 31 | docker_host = self.api.get_docker_host(docker_host_id) 32 | self.compare(docker_host, expected_docker_host) 33 | 34 | # get docker hosts 35 | docker_hosts = self.api.get_docker_hosts() 36 | self.assertTrue(type(docker_hosts[0]["dockerType"]) == DockerType) 37 | docker_host = self.find_by_id(docker_hosts, docker_host_id) 38 | self.assertIsNotNone(docker_host) 39 | self.compare(docker_host, expected_docker_host) 40 | 41 | # edit docker host 42 | r = self.api.edit_docker_host(docker_host_id, name="name 2") 43 | self.assertEqual(r["msg"], "Saved") 44 | docker_host = self.api.get_docker_host(docker_host_id) 45 | expected_docker_host["name"] = "name 2" 46 | self.compare(docker_host, expected_docker_host) 47 | 48 | # delete docker host 49 | r = self.api.delete_docker_host(docker_host_id) 50 | self.assertEqual(r["msg"], "Deleted") 51 | with self.assertRaises(UptimeKumaException): 52 | self.api.get_docker_host(docker_host_id) 53 | 54 | def test_delete_not_existing_docker_host(self): 55 | with self.assertRaises(UptimeKumaException): 56 | self.api.delete_docker_host(42) 57 | 58 | 59 | if __name__ == '__main__': 60 | unittest.main() 61 | -------------------------------------------------------------------------------- /tests/test_game_list.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_test_case import UptimeKumaTestCase 4 | 5 | 6 | class TestGameList(UptimeKumaTestCase): 7 | def test_game_list(self): 8 | game_list = self.api.get_game_list() 9 | self.assertTrue("keys" in game_list[0]) 10 | 11 | 12 | if __name__ == '__main__': 13 | unittest.main() 14 | -------------------------------------------------------------------------------- /tests/test_heartbeat.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import MonitorStatus 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestHeartbeat(UptimeKumaTestCase): 8 | def test_get_heartbeats(self): 9 | self.add_monitor() 10 | r = self.api.get_heartbeats() 11 | self.assertTrue(type(list(r.values())[0][0]["status"]) == MonitorStatus) 12 | 13 | def test_get_important_heartbeats(self): 14 | self.add_monitor() 15 | r = self.api.get_important_heartbeats() 16 | self.assertTrue(type(list(r.values())[0][0]["status"]) == MonitorStatus) 17 | 18 | 19 | if __name__ == '__main__': 20 | unittest.main() 21 | -------------------------------------------------------------------------------- /tests/test_helper_methods.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import MonitorStatus 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestHelperMethods(UptimeKumaTestCase): 8 | def test_monitor_status(self): 9 | monitor_id = self.add_monitor() 10 | status = self.api.get_monitor_status(monitor_id) 11 | self.assertTrue(type(status) == MonitorStatus) 12 | 13 | 14 | if __name__ == '__main__': 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /tests/test_info.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import UptimeKumaApi 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestInfo(UptimeKumaTestCase): 8 | def test_info(self): 9 | info = self.api.info() 10 | self.assertIn("version", info) 11 | self.assertIn("latestVersion", info) 12 | 13 | def test_info_with_version(self): 14 | # If wait_events is set to 0, the first info event is normally used. 15 | # The info event handler needs to drop this first event without a version. 16 | self.api.logout() 17 | self.api.disconnect() 18 | self.api = UptimeKumaApi(self.url, wait_events=0) 19 | self.api.login(self.username, self.password) 20 | info = self.api.info() 21 | self.assertIn("version", info) 22 | 23 | 24 | if __name__ == '__main__': 25 | unittest.main() 26 | -------------------------------------------------------------------------------- /tests/test_login.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import UptimeKumaApi 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestLogin(UptimeKumaTestCase): 8 | def test_auto_login(self): 9 | # disable auth 10 | r = self.api.set_settings(self.password, disableAuth=True) 11 | self.assertEqual(r["msg"], "Saved") 12 | 13 | # login again without username and password 14 | self.api.logout() 15 | self.api.disconnect() 16 | self.api = UptimeKumaApi(self.url) 17 | self.api.login() 18 | 19 | r = self.api.get_settings() 20 | self.assertTrue(r["disableAuth"]) 21 | 22 | # enable auth again 23 | r = self.api.set_settings(disableAuth=False) 24 | self.assertEqual(r["msg"], "Saved") 25 | 26 | r = self.api.get_settings() 27 | self.assertFalse(r["disableAuth"]) 28 | 29 | 30 | if __name__ == '__main__': 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /tests/test_maintenance.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import UptimeKumaException, MaintenanceStrategy 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestMaintenance(UptimeKumaTestCase): 8 | def test_maintenance(self): 9 | expected_maintenance = { 10 | "title": "maintenance 1", 11 | "description": "test", 12 | "strategy": MaintenanceStrategy.SINGLE, 13 | "active": True, 14 | "intervalDay": 1, 15 | "dateRange": [ 16 | "2022-12-27 22:36:00", 17 | "2022-12-29 22:36:00" 18 | ], 19 | "weekdays": [], 20 | "daysOfMonth": [], 21 | "timezoneOption": "Europe/Berlin" 22 | } 23 | 24 | # add maintenance 25 | r = self.api.add_maintenance(**expected_maintenance) 26 | self.assertEqual(r["msg"], "Added Successfully.") 27 | maintenance_id = r["maintenanceID"] 28 | 29 | # get maintenance 30 | maintenance = self.api.get_maintenance(maintenance_id) 31 | self.assertTrue(type(maintenance["strategy"]) == MaintenanceStrategy) 32 | self.compare(maintenance, expected_maintenance) 33 | 34 | # get maintenances 35 | maintenances = self.api.get_maintenances() 36 | self.assertTrue(type(maintenances[0]["strategy"]) == MaintenanceStrategy) 37 | maintenance = self.find_by_id(maintenances, maintenance_id) 38 | self.assertIsNotNone(maintenance) 39 | self.compare(maintenance, expected_maintenance) 40 | 41 | # edit maintenance 42 | expected_maintenance["strategy"] = MaintenanceStrategy.RECURRING_INTERVAL 43 | expected_maintenance["title"] = "maintenance 1 new" 44 | r = self.api.edit_maintenance(maintenance_id, **expected_maintenance) 45 | self.assertEqual(r["msg"], "Saved.") 46 | maintenance = self.api.get_maintenance(maintenance_id) 47 | self.compare(maintenance, expected_maintenance) 48 | 49 | # pause maintenance 50 | r = self.api.pause_maintenance(maintenance_id) 51 | self.assertEqual(r["msg"], "Paused Successfully.") 52 | 53 | # resume maintenance 54 | r = self.api.resume_maintenance(maintenance_id) 55 | self.assertEqual(r["msg"], "Resume Successfully") 56 | 57 | # add monitor maintenance 58 | monitor_name = "monitor 1" 59 | monitor_id = self.add_monitor(monitor_name) 60 | monitors = [ 61 | { 62 | "id": monitor_id 63 | }, 64 | ] 65 | r = self.api.add_monitor_maintenance(maintenance_id, monitors) 66 | self.assertEqual(r["msg"], "Added Successfully.") 67 | 68 | # get monitor maintenance 69 | monitors = self.api.get_monitor_maintenance(maintenance_id) 70 | monitor = self.find_by_id(monitors, monitor_id) 71 | self.assertIsNotNone(monitor) 72 | 73 | # add status page maintenance 74 | status_page_title = "status page 1" 75 | status_page_id = self.add_status_page(status_page_title) 76 | status_pages = [ 77 | { 78 | "id": status_page_id 79 | } 80 | ] 81 | r = self.api.add_status_page_maintenance(maintenance_id, status_pages) 82 | self.assertEqual(r["msg"], "Added Successfully.") 83 | 84 | # get status page maintenance 85 | status_pages = self.api.get_status_page_maintenance(maintenance_id) 86 | status_page = self.find_by_id(status_pages, status_page_id) 87 | self.assertIsNotNone(status_page) 88 | 89 | # delete maintenance 90 | r = self.api.delete_maintenance(maintenance_id) 91 | self.assertEqual(r["msg"], "Deleted Successfully.") 92 | with self.assertRaises(UptimeKumaException): 93 | self.api.get_maintenance(maintenance_id) 94 | 95 | def test_maintenance_strategy_manual(self): 96 | expected_maintenance = { 97 | "title": "test", 98 | "description": "test", 99 | "strategy": MaintenanceStrategy.MANUAL, 100 | "active": True, 101 | "intervalDay": 1, 102 | "dateRange": [ 103 | "2022-12-27 00:00:00" 104 | ], 105 | "weekdays": [], 106 | "daysOfMonth": [] 107 | } 108 | self.do_test_maintenance_strategy(expected_maintenance) 109 | 110 | def test_maintenance_strategy_single(self): 111 | expected_maintenance = { 112 | "title": "test", 113 | "description": "test", 114 | "strategy": MaintenanceStrategy.SINGLE, 115 | "active": True, 116 | "intervalDay": 1, 117 | "dateRange": [ 118 | "2022-12-27 22:36:00", 119 | "2022-12-29 22:36:00" 120 | ], 121 | "weekdays": [], 122 | "daysOfMonth": [] 123 | } 124 | self.do_test_maintenance_strategy(expected_maintenance) 125 | 126 | def test_maintenance_strategy_recurring_interval(self): 127 | expected_maintenance = { 128 | "title": "test", 129 | "description": "test", 130 | "strategy": MaintenanceStrategy.RECURRING_INTERVAL, 131 | "active": True, 132 | "intervalDay": 1, 133 | "dateRange": [ 134 | "2022-12-27 22:37:00", 135 | "2022-12-31 22:37:00" 136 | ], 137 | "timeRange": [ 138 | { 139 | "hours": 2, 140 | "minutes": 0 141 | }, 142 | { 143 | "hours": 3, 144 | "minutes": 0 145 | } 146 | ], 147 | "weekdays": [], 148 | "daysOfMonth": [] 149 | } 150 | self.do_test_maintenance_strategy(expected_maintenance) 151 | 152 | def test_maintenance_strategy_recurring_weekday(self): 153 | expected_maintenance = { 154 | "title": "test", 155 | "description": "test", 156 | "strategy": MaintenanceStrategy.RECURRING_WEEKDAY, 157 | "active": True, 158 | "intervalDay": 1, 159 | "dateRange": [ 160 | "2022-12-27 22:38:00", 161 | "2022-12-31 22:38:00" 162 | ], 163 | "timeRange": [ 164 | { 165 | "hours": 2, 166 | "minutes": 0 167 | }, 168 | { 169 | "hours": 3, 170 | "minutes": 0 171 | } 172 | ], 173 | "weekdays": [ 174 | 1, 175 | 3, 176 | 5, 177 | 0 178 | ], 179 | "daysOfMonth": [] 180 | } 181 | self.do_test_maintenance_strategy(expected_maintenance) 182 | 183 | def test_maintenance_strategy_recurring_day_of_month(self): 184 | expected_maintenance = { 185 | "title": "test", 186 | "description": "test", 187 | "strategy": MaintenanceStrategy.RECURRING_DAY_OF_MONTH, 188 | "active": True, 189 | "intervalDay": 1, 190 | "dateRange": [ 191 | "2022-12-27 22:39:00", 192 | "2022-12-31 22:39:00" 193 | ], 194 | "timeRange": [ 195 | { 196 | "hours": 2, 197 | "minutes": 0 198 | }, 199 | { 200 | "hours": 3, 201 | "minutes": 0 202 | } 203 | ], 204 | "weekdays": [], 205 | "daysOfMonth": [ 206 | 1, 207 | 10, 208 | 20, 209 | 30, 210 | "lastDay1" 211 | ] 212 | } 213 | self.do_test_maintenance_strategy(expected_maintenance) 214 | 215 | def test_maintenance_strategy_cron(self): 216 | expected_maintenance = { 217 | "title": "test", 218 | "description": "test", 219 | "strategy": MaintenanceStrategy.CRON, 220 | "active": True, 221 | "intervalDay": 1, 222 | "dateRange": [ 223 | "2022-12-27 22:37:00", 224 | "2022-12-31 22:37:00" 225 | ], 226 | "weekdays": [], 227 | "daysOfMonth": [], 228 | "cron": "50 5 * * *", 229 | "durationMinutes": 120, 230 | "timezoneOption": "Europe/Berlin" 231 | } 232 | self.do_test_maintenance_strategy(expected_maintenance) 233 | 234 | def do_test_maintenance_strategy(self, expected_maintenance): 235 | # add maintenance 236 | r = self.api.add_maintenance(**expected_maintenance) 237 | self.assertEqual(r["msg"], "Added Successfully.") 238 | maintenance_id = r["maintenanceID"] 239 | 240 | # get maintenance 241 | maintenance = self.api.get_maintenance(maintenance_id) 242 | self.compare(maintenance, expected_maintenance) 243 | 244 | # get maintenances 245 | maintenances = self.api.get_maintenances() 246 | maintenance = self.find_by_id(maintenances, maintenance_id) 247 | self.assertIsNotNone(maintenance) 248 | self.compare(maintenance, expected_maintenance) 249 | 250 | # edit maintenance 251 | r = self.api.edit_maintenance(maintenance_id, title="name 2") 252 | self.assertEqual(r["msg"], "Saved.") 253 | maintenance = self.api.get_maintenance(maintenance_id) 254 | expected_maintenance["title"] = "name 2" 255 | self.compare(maintenance, expected_maintenance) 256 | 257 | # delete maintenance 258 | r = self.api.delete_maintenance(maintenance_id) 259 | self.assertEqual(r["msg"], "Deleted Successfully.") 260 | with self.assertRaises(UptimeKumaException): 261 | self.api.get_maintenance(maintenance_id) 262 | 263 | def test_delete_not_existing_maintenance(self): 264 | with self.assertRaises(UptimeKumaException): 265 | self.api.delete_maintenance(42) 266 | 267 | 268 | if __name__ == '__main__': 269 | unittest.main() 270 | -------------------------------------------------------------------------------- /tests/test_monitor.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from packaging.version import parse as parse_version 3 | 4 | from uptime_kuma_api import UptimeKumaException, MonitorType, AuthMethod, MonitorStatus 5 | from uptime_kuma_test_case import UptimeKumaTestCase 6 | 7 | 8 | class TestMonitor(UptimeKumaTestCase): 9 | def test_monitor(self): 10 | # get empty list to make sure that future accesses will also work 11 | self.api.get_monitors() 12 | 13 | notification_id_1 = self.add_notification() 14 | notification_id_2 = self.add_notification() 15 | 16 | expected_monitor = { 17 | "type": MonitorType.HTTP, 18 | "name": "monitor 1", 19 | "interval": 60, 20 | "retryInterval": 60, 21 | "maxretries": 0, 22 | "notificationIDList": [notification_id_1, notification_id_2], 23 | "upsideDown": False, 24 | "url": "http://127.0.0.1", 25 | "resendInterval": 0 26 | } 27 | 28 | # add monitor 29 | r = self.api.add_monitor(**expected_monitor) 30 | self.assertEqual(r["msg"], "Added Successfully.") 31 | monitor_id = r["monitorID"] 32 | 33 | # get monitor 34 | monitor = self.api.get_monitor(monitor_id) 35 | self.compare(monitor, expected_monitor) 36 | 37 | # get monitors 38 | monitors = self.api.get_monitors() 39 | self.assertTrue(type(monitors[0]["type"]) == MonitorType) 40 | self.assertTrue(type(monitors[0]["authMethod"]) == AuthMethod) 41 | monitor = self.find_by_id(monitors, monitor_id) 42 | self.assertTrue(type(monitor["type"]) == MonitorType) 43 | self.assertTrue(type(monitor["authMethod"]) == AuthMethod) 44 | self.assertIsNotNone(monitor) 45 | self.compare(monitor, expected_monitor) 46 | 47 | # edit monitor 48 | expected_monitor["type"] = MonitorType.PING 49 | expected_monitor["name"] = "monitor 1 new" 50 | expected_monitor["hostname"] = "127.0.0.10" 51 | del expected_monitor["url"] 52 | r = self.api.edit_monitor(monitor_id, **expected_monitor) 53 | self.assertEqual(r["msg"], "Saved.") 54 | monitor = self.api.get_monitor(monitor_id) 55 | self.compare(monitor, expected_monitor) 56 | 57 | # pause monitor 58 | r = self.api.pause_monitor(monitor_id) 59 | self.assertEqual(r["msg"], "Paused Successfully.") 60 | 61 | # resume monitor 62 | r = self.api.resume_monitor(monitor_id) 63 | self.assertEqual(r["msg"], "Resumed Successfully.") 64 | 65 | # get monitor beats 66 | r = self.api.get_monitor_beats(monitor_id, 6) 67 | self.assertTrue(type(r[0]["status"]) == MonitorStatus) 68 | 69 | # delete monitor 70 | r = self.api.delete_monitor(monitor_id) 71 | self.assertEqual(r["msg"], "Deleted Successfully.") 72 | with self.assertRaises(UptimeKumaException): 73 | self.api.get_monitor(monitor_id) 74 | 75 | def do_test_monitor_type(self, expected_monitor): 76 | r = self.api.add_monitor(**expected_monitor) 77 | self.assertEqual(r["msg"], "Added Successfully.") 78 | monitor_id = r["monitorID"] 79 | 80 | monitor = self.api.get_monitor(monitor_id) 81 | self.compare(monitor, expected_monitor) 82 | 83 | expected_monitor.update({ 84 | "name": "monitor 2" 85 | }) 86 | r = self.api.edit_monitor(monitor_id, **expected_monitor) 87 | self.assertEqual(r["msg"], "Saved.") 88 | monitor = self.api.get_monitor(monitor_id) 89 | self.compare(monitor, expected_monitor) 90 | 91 | monitor = self.api.get_monitor(monitor_id) 92 | self.compare(monitor, expected_monitor) 93 | return monitor 94 | 95 | def test_monitor_type_http(self): 96 | proxy_id = self.add_proxy() 97 | 98 | json_data = '{"key": "value"}' 99 | expected_monitor = { 100 | "type": MonitorType.HTTP, 101 | "name": "monitor 1", 102 | "url": "http://127.0.0.1", 103 | "expiryNotification": False, 104 | "ignoreTls": False, 105 | "maxredirects": 10, 106 | "accepted_statuscodes": ["200-299"], 107 | "proxyId": proxy_id, 108 | "method": "GET", 109 | "body": json_data, 110 | "headers": json_data, 111 | "authMethod": AuthMethod.NONE 112 | } 113 | self.do_test_monitor_type(expected_monitor) 114 | 115 | def test_monitor_auth_method(self): 116 | for auth_method in [AuthMethod.HTTP_BASIC, AuthMethod.NTLM]: 117 | expected_monitor = { 118 | "type": MonitorType.HTTP, 119 | "name": "monitor 1", 120 | "url": "http://127.0.0.1", 121 | "authMethod": auth_method, 122 | "basic_auth_user": "auth user", 123 | "basic_auth_pass": "auth pass", 124 | } 125 | self.do_test_monitor_type(expected_monitor) 126 | 127 | expected_monitor = { 128 | "type": MonitorType.HTTP, 129 | "name": "monitor 1", 130 | "url": "http://127.0.0.1", 131 | "authMethod": AuthMethod.NTLM, 132 | "authDomain": "auth domain", 133 | "authWorkstation": "auth workstation", 134 | } 135 | self.do_test_monitor_type(expected_monitor) 136 | 137 | expected_monitor = { 138 | "type": MonitorType.HTTP, 139 | "name": "monitor 1", 140 | "url": "http://127.0.0.1", 141 | "authMethod": AuthMethod.MTLS, 142 | "tlsCert": "cert", 143 | "tlsKey": "key", 144 | "tlsCa": "ca", 145 | } 146 | self.do_test_monitor_type(expected_monitor) 147 | 148 | def test_monitor_type_port(self): 149 | expected_monitor = { 150 | "type": MonitorType.PORT, 151 | "name": "monitor 1", 152 | "hostname": "127.0.0.1", 153 | "port": 8888 154 | } 155 | self.do_test_monitor_type(expected_monitor) 156 | 157 | def test_monitor_type_ping(self): 158 | expected_monitor = { 159 | "type": MonitorType.PING, 160 | "name": "monitor 1", 161 | "hostname": "127.0.0.1", 162 | "packetSize": 56 163 | } 164 | self.do_test_monitor_type(expected_monitor) 165 | 166 | def test_monitor_type_keyword(self): 167 | expected_monitor = { 168 | "type": MonitorType.KEYWORD, 169 | "name": "monitor 1", 170 | "url": "http://127.0.0.1", 171 | "keyword": "healthy" 172 | } 173 | self.do_test_monitor_type(expected_monitor) 174 | 175 | def test_monitor_type_grpc_keyword(self): 176 | expected_monitor = { 177 | "type": MonitorType.GRPC_KEYWORD, 178 | "name": "monitor 1", 179 | "grpcUrl": "127.0.0.1", 180 | "keyword": "healthy", 181 | "grpcServiceName": "health", 182 | "grpcMethod": "check", 183 | } 184 | self.do_test_monitor_type(expected_monitor) 185 | 186 | def test_monitor_type_dns(self): 187 | expected_monitor = { 188 | "type": MonitorType.DNS, 189 | "name": "monitor 1", 190 | "hostname": "127.0.0.1", 191 | "port": 8888, 192 | "dns_resolve_server": "1.1.1.1", 193 | "dns_resolve_type": "A" 194 | } 195 | self.do_test_monitor_type(expected_monitor) 196 | 197 | def test_monitor_type_docker(self): 198 | docker_host_id = self.add_docker_host() 199 | expected_monitor = { 200 | "type": MonitorType.DOCKER, 201 | "name": "monitor 1", 202 | "docker_container": "test", 203 | "docker_host": docker_host_id 204 | } 205 | self.do_test_monitor_type(expected_monitor) 206 | 207 | def test_monitor_type_push(self): 208 | expected_monitor = { 209 | "type": MonitorType.PUSH, 210 | "name": "monitor 1" 211 | } 212 | monitor = self.do_test_monitor_type(expected_monitor) 213 | 214 | # https://github.com/lucasheld/ansible-uptime-kuma/issues/5 215 | self.assertIsNotNone(monitor["pushToken"]) 216 | 217 | def test_monitor_type_steam(self): 218 | expected_monitor = { 219 | "type": MonitorType.STEAM, 220 | "name": "monitor 1", 221 | "hostname": "127.0.0.1", 222 | "port": 8888 223 | } 224 | self.do_test_monitor_type(expected_monitor) 225 | 226 | def test_monitor_type_gamedig(self): 227 | game_list = self.api.get_game_list() 228 | game = game_list[0]["keys"][0] 229 | expected_monitor = { 230 | "type": MonitorType.GAMEDIG, 231 | "name": "monitor 1", 232 | "hostname": "127.0.0.1", 233 | "port": 8888, 234 | "game": game 235 | } 236 | self.do_test_monitor_type(expected_monitor) 237 | 238 | def test_monitor_type_mqtt(self): 239 | expected_monitor = { 240 | "type": MonitorType.MQTT, 241 | "name": "monitor 1", 242 | "hostname": "127.0.0.1", 243 | "port": 8888, 244 | "mqttUsername": "mqtt username", 245 | "mqttPassword": "mqtt password", 246 | "mqttTopic": "mqtt topic", 247 | "mqttSuccessMessage": "mqtt success message" 248 | } 249 | self.do_test_monitor_type(expected_monitor) 250 | 251 | def test_monitor_type_sqlserver(self): 252 | expected_monitor = { 253 | "type": MonitorType.SQLSERVER, 254 | "name": "monitor 1", 255 | "databaseConnectionString": "Server=127.0.0.1,8888;Database=test;User Id=1;Password=secret123;Encrypt=true;" 256 | "TrustServerCertificate=Yes;Connection Timeout=5", 257 | "databaseQuery": "select getdate()" 258 | } 259 | self.do_test_monitor_type(expected_monitor) 260 | 261 | def test_monitor_type_postgres(self): 262 | expected_monitor = { 263 | "type": MonitorType.POSTGRES, 264 | "name": "monitor 1", 265 | "databaseConnectionString": "postgres://username:password@host:port/database", 266 | "databaseQuery": "select getdate()" 267 | } 268 | self.do_test_monitor_type(expected_monitor) 269 | 270 | def test_monitor_type_mysql(self): 271 | expected_monitor = { 272 | "type": MonitorType.MYSQL, 273 | "name": "monitor 1", 274 | "databaseConnectionString": "mysql://username:password@host:port/database", 275 | "databaseQuery": "select getdate()" 276 | } 277 | self.do_test_monitor_type(expected_monitor) 278 | 279 | def test_monitor_type_mongodb(self): 280 | expected_monitor = { 281 | "type": MonitorType.MONGODB, 282 | "name": "monitor 1", 283 | "databaseConnectionString": "mongodb://username:password@host:port/database" 284 | } 285 | self.do_test_monitor_type(expected_monitor) 286 | 287 | def test_monitor_type_radius(self): 288 | expected_monitor = { 289 | "type": MonitorType.RADIUS, 290 | "name": "monitor 1", 291 | "radiusUsername": "123", 292 | "radiusPassword": "456", 293 | "radiusSecret": "789", 294 | "radiusCalledStationId": "1", 295 | "radiusCallingStationId": "2" 296 | } 297 | self.do_test_monitor_type(expected_monitor) 298 | 299 | def test_monitor_type_redis(self): 300 | expected_monitor = { 301 | "type": MonitorType.REDIS, 302 | "name": "monitor 1", 303 | "databaseConnectionString": "redis://user:password@host:port" 304 | } 305 | self.do_test_monitor_type(expected_monitor) 306 | 307 | def test_monitor_type_group(self): 308 | if parse_version(self.api.version) < parse_version("1.22"): 309 | self.skipTest("Unsupported in this Uptime Kuma version") 310 | 311 | # create monitor group 312 | expected_monitor = { 313 | "type": MonitorType.GROUP, 314 | "name": "monitor 1" 315 | } 316 | group_monitor = self.do_test_monitor_type(expected_monitor) 317 | group_monitor_id = group_monitor["id"] 318 | 319 | # use monitor group as parent for another monitor 320 | expected_monitor = { 321 | "type": MonitorType.PUSH, 322 | "name": "monitor 1", 323 | "parent": group_monitor_id 324 | } 325 | self.do_test_monitor_type(expected_monitor) 326 | 327 | def test_monitor_type_json_query(self): 328 | if parse_version(self.api.version) < parse_version("1.23"): 329 | self.skipTest("Unsupported in this Uptime Kuma version") 330 | 331 | expected_monitor = { 332 | "type": MonitorType.JSON_QUERY, 333 | "name": "monitor 1", 334 | "url": "http://127.0.0.1", 335 | "jsonPath": "address.country", 336 | "expectedValue": "germany", 337 | } 338 | self.do_test_monitor_type(expected_monitor) 339 | 340 | def test_monitor_type_real_browser(self): 341 | if parse_version(self.api.version) < parse_version("1.23"): 342 | self.skipTest("Unsupported in this Uptime Kuma version") 343 | 344 | expected_monitor = { 345 | "type": MonitorType.REAL_BROWSER, 346 | "name": "monitor 1", 347 | "url": "http://127.0.0.1", 348 | } 349 | self.do_test_monitor_type(expected_monitor) 350 | 351 | def test_monitor_type_kafka_producer(self): 352 | if parse_version(self.api.version) < parse_version("1.23"): 353 | self.skipTest("Unsupported in this Uptime Kuma version") 354 | 355 | expected_monitor = { 356 | "type": MonitorType.KAFKA_PRODUCER, 357 | "name": "monitor 1", 358 | "kafkaProducerTopic": "topic", 359 | "kafkaProducerMessage": "message", 360 | } 361 | self.do_test_monitor_type(expected_monitor) 362 | 363 | def test_monitor_type_tailscale_ping(self): 364 | if parse_version(self.api.version) < parse_version("1.23"): 365 | self.skipTest("Unsupported in this Uptime Kuma version") 366 | 367 | expected_monitor = { 368 | "type": MonitorType.TAILSCALE_PING, 369 | "name": "monitor 1", 370 | "hostname": "127.0.0.1" 371 | } 372 | self.do_test_monitor_type(expected_monitor) 373 | 374 | def test_delete_not_existing_monitor(self): 375 | with self.assertRaises(UptimeKumaException): 376 | self.api.delete_monitor(42) 377 | 378 | 379 | if __name__ == '__main__': 380 | unittest.main() 381 | -------------------------------------------------------------------------------- /tests/test_monitor_tag.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import UptimeKumaException 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestMonitorTag(UptimeKumaTestCase): 8 | def test_monitor_tag(self): 9 | tag_id = self.add_tag() 10 | monitor_id = self.add_monitor() 11 | 12 | expected_monitor_tag = { 13 | "tag_id": tag_id, 14 | "monitor_id": monitor_id, 15 | "value": "value 1" 16 | } 17 | 18 | # add monitor tag 19 | r = self.api.add_monitor_tag(**expected_monitor_tag) 20 | self.assertEqual(r["msg"], "Added Successfully.") 21 | 22 | # check if tag is listed in monitor tags 23 | monitors = self.api.get_monitors() 24 | monitor = self.find_by_id(monitors, monitor_id) 25 | self.assertEqual(monitor["tags"][0]["tag_id"], tag_id) 26 | 27 | # delete monitor tag 28 | r = self.api.delete_monitor_tag(**expected_monitor_tag) 29 | self.assertEqual(r["msg"], "Deleted Successfully.") 30 | 31 | # check if tag is not listed in monitor tags 32 | monitors = self.api.get_monitors() 33 | monitor = self.find_by_id(monitors, monitor_id) 34 | self.assertEqual(monitor["tags"], []) 35 | 36 | def test_delete_not_existing_monitor_tag(self): 37 | with self.assertRaises(UptimeKumaException): 38 | self.api.delete_monitor_tag(42, 42, 42) 39 | 40 | 41 | if __name__ == '__main__': 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /tests/test_notification.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import UptimeKumaException, NotificationType 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestNotification(UptimeKumaTestCase): 8 | def test_notification(self): 9 | # get empty list to make sure that future accesses will also work 10 | self.api.get_notifications() 11 | 12 | expected_notification = { 13 | "name": "notification 1", 14 | "isDefault": True, 15 | "applyExisting": True, 16 | "type": NotificationType.TELEGRAM, 17 | "telegramChatID": "123456789", 18 | "telegramBotToken": "987654321" 19 | } 20 | 21 | # test notification 22 | with self.assertRaisesRegex(UptimeKumaException, r'Not Found'): 23 | self.api.test_notification(**expected_notification) 24 | 25 | # add notification 26 | r = self.api.add_notification(**expected_notification) 27 | self.assertEqual(r["msg"], "Saved") 28 | notification_id = r["id"] 29 | 30 | # get notification 31 | notification = self.api.get_notification(notification_id) 32 | self.compare(notification, expected_notification) 33 | 34 | # get notifications 35 | notifications = self.api.get_notifications() 36 | self.assertTrue(type(notifications[0]["type"]) == NotificationType) 37 | notification = self.find_by_id(notifications, notification_id) 38 | self.assertTrue(type(notification["type"]) == NotificationType) 39 | self.assertIsNotNone(notification) 40 | self.compare(notification, expected_notification) 41 | 42 | # edit notification 43 | expected_notification["name"] = "notification 1 new" 44 | expected_notification["default"] = False 45 | expected_notification["applyExisting"] = False 46 | expected_notification["type"] = NotificationType.PUSHDEER 47 | expected_notification["pushdeerKey"] = "987654321" 48 | del expected_notification["telegramChatID"] 49 | del expected_notification["telegramBotToken"] 50 | r = self.api.edit_notification(notification_id, **expected_notification) 51 | self.assertEqual(r["msg"], "Saved") 52 | notification = self.api.get_notification(notification_id) 53 | self.compare(notification, expected_notification) 54 | self.assertIsNone(notification.get("pushAPIKey")) 55 | 56 | # delete notification 57 | r = self.api.delete_notification(notification_id) 58 | self.assertEqual(r["msg"], "Deleted") 59 | with self.assertRaises(UptimeKumaException): 60 | self.api.delete_notification(notification_id) 61 | 62 | def test_delete_not_existing_notification(self): 63 | with self.assertRaises(UptimeKumaException): 64 | self.api.delete_notification(42) 65 | 66 | 67 | if __name__ == '__main__': 68 | unittest.main() 69 | -------------------------------------------------------------------------------- /tests/test_proxy.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import UptimeKumaException, ProxyProtocol 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestProxy(UptimeKumaTestCase): 8 | def test_proxy(self): 9 | # get empty list to make sure that future accesses will also work 10 | self.api.get_proxies() 11 | 12 | expected_proxy = { 13 | "protocol": ProxyProtocol.HTTP, 14 | "host": "127.0.0.1", 15 | "port": 8080, 16 | "auth": True, 17 | "username": "username", 18 | "password": "password", 19 | "active": True, 20 | "default": False 21 | } 22 | 23 | # add proxy 24 | r = self.api.add_proxy(applyExisting=False, **expected_proxy) 25 | self.assertEqual(r["msg"], "Saved") 26 | proxy_id = r["id"] 27 | 28 | # get proxy 29 | proxy = self.api.get_proxy(proxy_id) 30 | self.compare(proxy, expected_proxy) 31 | 32 | # get proxies 33 | proxies = self.api.get_proxies() 34 | self.assertTrue(type(proxies[0]["protocol"]) == ProxyProtocol) 35 | proxy = self.find_by_id(proxies, proxy_id) 36 | self.assertIsNotNone(proxy) 37 | self.compare(proxy, expected_proxy) 38 | 39 | # edit proxy 40 | expected_proxy["protocol"] = ProxyProtocol.HTTPS 41 | expected_proxy["host"] = "127.0.0.2" 42 | expected_proxy["port"] = 8888 43 | r = self.api.edit_proxy(proxy_id, **expected_proxy) 44 | self.assertEqual(r["msg"], "Saved") 45 | proxy = self.api.get_proxy(proxy_id) 46 | self.compare(proxy, expected_proxy) 47 | 48 | # delete proxy 49 | r = self.api.delete_proxy(proxy_id) 50 | self.assertEqual(r["msg"], "Deleted") 51 | with self.assertRaises(UptimeKumaException): 52 | self.api.get_proxy(proxy_id) 53 | 54 | def test_delete_not_existing_proxy(self): 55 | with self.assertRaises(UptimeKumaException): 56 | self.api.delete_proxy(42) 57 | 58 | 59 | if __name__ == '__main__': 60 | unittest.main() 61 | -------------------------------------------------------------------------------- /tests/test_settings.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestSettings(UptimeKumaTestCase): 8 | def test_settings(self): 9 | expected_settings = { 10 | "checkUpdate": False, 11 | "checkBeta": False, 12 | "keepDataPeriodDays": 180, 13 | "entryPage": "dashboard", 14 | "searchEngineIndex": False, 15 | "primaryBaseURL": "", 16 | "steamAPIKey": "", 17 | "tlsExpiryNotifyDays": [7, 14, 21], 18 | "disableAuth": False, 19 | "trustProxy": False, 20 | "serverTimezone": "Europe/Berlin", 21 | "dnsCache": True 22 | } 23 | 24 | # set settings 25 | r = self.api.set_settings(self.password, **expected_settings) 26 | self.assertEqual(r["msg"], "Saved") 27 | 28 | # set settings without password 29 | r = self.api.set_settings(**expected_settings) 30 | self.assertEqual(r["msg"], "Saved") 31 | 32 | # get settings 33 | settings = self.api.get_settings() 34 | self.compare(settings, expected_settings) 35 | 36 | def test_change_password(self): 37 | new_password = "321terces" 38 | 39 | # change password 40 | r = self.api.change_password(self.password, new_password) 41 | self.assertEqual(r["msg"], "Password has been updated successfully.") 42 | 43 | # check login 44 | r = self.api.login(self.username, new_password) 45 | self.assertIn("token", r) 46 | 47 | # restore password 48 | r = self.api.change_password(new_password, self.password) 49 | self.assertEqual(r["msg"], "Password has been updated successfully.") 50 | 51 | def test_upload_backup(self): 52 | data = { 53 | "version": "1.17.1", 54 | "notificationList": [], 55 | "monitorList": [], 56 | "proxyList": [] 57 | } 58 | data_str = json.dumps(data) 59 | r = self.api.upload_backup(data_str, "overwrite") 60 | self.assertEqual(r["msg"], "Backup successfully restored.") 61 | 62 | 63 | if __name__ == '__main__': 64 | unittest.main() 65 | -------------------------------------------------------------------------------- /tests/test_status_page.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import UptimeKumaException, IncidentStyle 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestStatusPage(UptimeKumaTestCase): 8 | def test_status_page(self): 9 | # get empty list to make sure that future accesses will also work 10 | self.api.get_status_pages() 11 | 12 | monitor_id = self.add_monitor() 13 | 14 | slug = "slug1" 15 | expected_status_page = { 16 | "slug": slug, 17 | "title": "status page 1", 18 | "description": "description 1", 19 | "theme": "light", 20 | "published": True, 21 | "showTags": False, 22 | "domainNameList": [], 23 | "customCSS": "", 24 | "footerText": None, 25 | "showPoweredBy": False, 26 | "icon": "/icon.svg", 27 | "publicGroupList": [ 28 | { 29 | 'name': 'Services', 30 | 'weight': 1, 31 | 'monitorList': [ 32 | { 33 | "id": monitor_id 34 | } 35 | ] 36 | } 37 | ], 38 | "googleAnalyticsId": "" 39 | } 40 | 41 | # add status page 42 | r = self.api.add_status_page(slug, expected_status_page["title"]) 43 | self.assertEqual(r["msg"], "OK!") 44 | 45 | # save status page 46 | self.api.save_status_page(**expected_status_page) 47 | 48 | # get status page 49 | status_page = self.api.get_status_page(slug) 50 | self.compare(status_page, expected_status_page) 51 | 52 | # get status pages 53 | status_pages = self.api.get_status_pages() 54 | status_page = self.find_by_id(status_pages, slug, "slug") 55 | self.assertIsNotNone(status_page) 56 | # publicGroupList and incident is not available in status pages 57 | expected_status_page_config = {i: expected_status_page[i] for i in expected_status_page if i != "publicGroupList"} 58 | self.compare(status_page, expected_status_page_config) 59 | 60 | # edit status page 61 | expected_status_page["title"] = "status page 1 new" 62 | expected_status_page["theme"] = "dark" 63 | self.api.save_status_page(**expected_status_page) 64 | status_page = self.api.get_status_page(slug) 65 | self.compare(status_page, expected_status_page) 66 | 67 | # pin incident 68 | incident_expected = { 69 | "title": "title 1", 70 | "content": "content 1", 71 | "style": IncidentStyle.DANGER 72 | } 73 | incident = self.api.post_incident(slug, **incident_expected) 74 | self.assertTrue(type(incident["style"]) == IncidentStyle) 75 | self.compare(incident, incident_expected) 76 | status_page = self.api.get_status_page(slug) 77 | self.compare(status_page["incident"], incident) 78 | self.assertTrue(type(status_page["incident"]["style"]) == IncidentStyle) 79 | 80 | # unpin incident 81 | self.api.unpin_incident(slug) 82 | status_page = self.api.get_status_page(slug) 83 | self.assertIsNone(status_page["incident"]) 84 | 85 | # delete status page 86 | self.api.delete_status_page(slug) 87 | with self.assertRaises(UptimeKumaException): 88 | self.api.get_status_page(slug) 89 | 90 | status_pages = self.api.get_status_pages() 91 | status_page = self.find_by_id(status_pages, slug, "slug") 92 | self.assertIsNone(status_page) 93 | 94 | def test_delete_not_existing_status_page(self): 95 | with self.assertRaises(UptimeKumaException): 96 | self.api.delete_status_page("slug42") 97 | 98 | 99 | if __name__ == '__main__': 100 | unittest.main() 101 | -------------------------------------------------------------------------------- /tests/test_tag.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_api import UptimeKumaException 4 | from uptime_kuma_test_case import UptimeKumaTestCase 5 | 6 | 7 | class TestTag(UptimeKumaTestCase): 8 | def test_tag(self): 9 | expected_tag = { 10 | "name": "tag 1", 11 | "color": "#ffffff" 12 | } 13 | 14 | # add tag 15 | tag = self.api.add_tag(**expected_tag) 16 | self.compare(tag, expected_tag) 17 | tag_id = tag["id"] 18 | 19 | # get tag 20 | tag = self.api.get_tag(tag_id) 21 | self.compare(tag, expected_tag) 22 | 23 | # get tags 24 | tags = self.api.get_tags() 25 | tag = self.find_by_id(tags, tag_id) 26 | self.assertIsNotNone(tag) 27 | self.compare(tag, expected_tag) 28 | 29 | # edit tag 30 | expected_tag["name"] = "tag 1 new" 31 | expected_tag["color"] = "#000000" 32 | r = self.api.edit_tag(tag_id, **expected_tag) 33 | self.assertEqual(r["msg"], "Saved") 34 | tag = self.api.get_tag(tag_id) 35 | self.compare(tag, expected_tag) 36 | 37 | # delete tag 38 | r = self.api.delete_tag(tag_id) 39 | self.assertEqual(r["msg"], "Deleted Successfully.") 40 | with self.assertRaises(UptimeKumaException): 41 | self.api.get_tag(tag_id) 42 | 43 | def test_delete_not_existing_tag(self): 44 | with self.assertRaises(UptimeKumaException): 45 | self.api.delete_tag(42) 46 | 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /tests/test_uptime.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uptime_kuma_test_case import UptimeKumaTestCase 4 | 5 | 6 | class TestUptime(UptimeKumaTestCase): 7 | def test_uptime_without_monitor(self): 8 | self.api.uptime() 9 | 10 | def test_uptime_with_monitor(self): 11 | self.add_monitor() 12 | self.api.uptime() 13 | 14 | 15 | if __name__ == '__main__': 16 | unittest.main() 17 | -------------------------------------------------------------------------------- /tests/uptime_kuma_test_case.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import warnings 3 | 4 | from uptime_kuma_api import UptimeKumaApi, MonitorType, DockerType 5 | 6 | token = None 7 | 8 | 9 | def compare(subset, superset): 10 | for key, value in subset.items(): 11 | value2 = superset.get(key) 12 | if type(value) == list: 13 | for i in range(len(value)): 14 | if not value2: 15 | return False 16 | elif type(value[i]) == list or type(value[i]) == dict: 17 | if not compare(value[i], value2[i]): 18 | return False 19 | else: 20 | if value[i] != value2[i]: 21 | return False 22 | elif type(value) == dict: 23 | if not compare(value, value2): 24 | return False 25 | else: 26 | if value != value2: 27 | return False 28 | return True 29 | 30 | 31 | class UptimeKumaTestCase(unittest.TestCase): 32 | api = None 33 | url = "http://127.0.0.1:3001" 34 | username = "admin" 35 | password = "secret123" 36 | 37 | def setUp(self): 38 | warnings.simplefilter("ignore", ResourceWarning) 39 | 40 | self.api = UptimeKumaApi(self.url, timeout=1, wait_events=0.01) 41 | 42 | global token 43 | if not token: 44 | if self.api.need_setup(): 45 | self.api.setup(self.username, self.password) 46 | r = self.api.login(self.username, self.password) 47 | token = r["token"] 48 | 49 | self.api.login_by_token(token) 50 | 51 | # delete monitors 52 | monitors = self.api.get_monitors() 53 | for monitor in monitors: 54 | self.api.delete_monitor(monitor["id"]) 55 | 56 | # delete notifications 57 | notifications = self.api.get_notifications() 58 | for notification in notifications: 59 | self.api.delete_notification(notification["id"]) 60 | 61 | # delete proxies 62 | proxies = self.api.get_proxies() 63 | for proxy in proxies: 64 | self.api.delete_proxy(proxy["id"]) 65 | 66 | # delete tags 67 | tags = self.api.get_tags() 68 | for tag in tags: 69 | self.api.delete_tag(tag["id"]) 70 | 71 | # delete status pages 72 | status_pages = self.api.get_status_pages() 73 | for status_page in status_pages: 74 | self.api.delete_status_page(status_page["slug"]) 75 | 76 | # delete docker hosts 77 | docker_hosts = self.api.get_docker_hosts() 78 | for docker_host in docker_hosts: 79 | self.api.delete_docker_host(docker_host["id"]) 80 | 81 | # delete maintenances 82 | maintenances = self.api.get_maintenances() 83 | for maintenance in maintenances: 84 | self.api.delete_maintenance(maintenance["id"]) 85 | 86 | # delete api keys 87 | api_keys = self.api.get_api_keys() 88 | for api_key in api_keys: 89 | self.api.delete_api_key(api_key["id"]) 90 | 91 | # login again to receive initial messages 92 | self.api.disconnect() 93 | self.api = UptimeKumaApi(self.url) 94 | self.api.login_by_token(token) 95 | 96 | def tearDown(self): 97 | self.api.disconnect() 98 | 99 | def compare(self, superset, subset): 100 | self.assertTrue(compare(subset, superset)) 101 | 102 | def find_by_id(self, objects, value, key="id"): 103 | for obj in objects: 104 | if obj[key] == value: 105 | return obj 106 | 107 | def add_monitor(self, name="monitor 1"): 108 | r = self.api.add_monitor( 109 | type=MonitorType.HTTP, 110 | name=name, 111 | url="http://127.0.0.1" 112 | ) 113 | monitor_id = r["monitorID"] 114 | return monitor_id 115 | 116 | def add_tag(self): 117 | r = self.api.add_tag( 118 | name="tag 1", 119 | color="#ffffff" 120 | ) 121 | tag_id = r["id"] 122 | return tag_id 123 | 124 | def add_notification(self): 125 | r = self.api.add_notification( 126 | name="notification 1", 127 | type="PushByTechulus", 128 | pushAPIKey="123456789" 129 | ) 130 | notification_id = r["id"] 131 | return notification_id 132 | 133 | def add_proxy(self): 134 | r = self.api.add_proxy( 135 | protocol="http", 136 | host="127.0.0.1", 137 | port=8080, 138 | active=True 139 | ) 140 | proxy_id = r["id"] 141 | return proxy_id 142 | 143 | def add_docker_host(self): 144 | r = self.api.add_docker_host( 145 | name="docker host 1", 146 | dockerType=DockerType.SOCKET 147 | ) 148 | docker_host_id = r["id"] 149 | return docker_host_id 150 | 151 | def add_status_page(self, title="status page 1"): 152 | slug = "statuspage1" 153 | self.api.add_status_page(slug, title) 154 | r = self.api.get_status_page(slug) 155 | return r["id"] 156 | -------------------------------------------------------------------------------- /uptime_kuma_api/__init__.py: -------------------------------------------------------------------------------- 1 | from .__version__ import __title__, __version__, __author__, __copyright__ 2 | from .auth_method import AuthMethod 3 | from .monitor_status import MonitorStatus 4 | from .monitor_type import MonitorType 5 | from .notification_providers import NotificationType, notification_provider_options, notification_provider_conditions 6 | from .proxy_protocol import ProxyProtocol 7 | from .incident_style import IncidentStyle 8 | from .docker_type import DockerType 9 | from .maintenance_strategy import MaintenanceStrategy 10 | from .exceptions import UptimeKumaException, Timeout 11 | from .event import Event 12 | from .api import UptimeKumaApi 13 | -------------------------------------------------------------------------------- /uptime_kuma_api/__version__.py: -------------------------------------------------------------------------------- 1 | __title__ = "uptime_kuma_api" 2 | __version__ = "1.2.1" 3 | __author__ = "Lucas Held" 4 | __license__ = "MIT" 5 | __copyright__ = "Copyright 2023 Lucas Held" 6 | -------------------------------------------------------------------------------- /uptime_kuma_api/auth_method.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class AuthMethod(str, Enum): 5 | """Enumerate authentication methods for monitors.""" 6 | 7 | NONE = "" 8 | """Authentication is disabled.""" 9 | 10 | HTTP_BASIC = "basic" 11 | """HTTP Basic Authentication.""" 12 | 13 | NTLM = "ntlm" 14 | """NTLM Authentication.""" 15 | 16 | MTLS = "mtls" 17 | """mTLS Authentication.""" 18 | 19 | OAUTH2_CC = "oauth2-cc" 20 | """OAuth2: Client Credentials""" 21 | -------------------------------------------------------------------------------- /uptime_kuma_api/docker_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class DockerType(str, Enum): 5 | """Enumerate docker connection types.""" 6 | 7 | SOCKET = "socket" 8 | """Socket""" 9 | 10 | TCP = "tcp" 11 | """TCP""" 12 | -------------------------------------------------------------------------------- /uptime_kuma_api/docstrings.py: -------------------------------------------------------------------------------- 1 | def append_docstring(value): 2 | def _doc(func): 3 | # inserts the value into the existing docstring before the :return: line 4 | split_value = ":return:" 5 | splitted = func.__doc__.split(split_value) 6 | part1 = splitted[0] 7 | line = [i for i in part1.split("\n") if i][0] 8 | indent = len(line) - len(line.lstrip()) 9 | line_start = " " * indent 10 | part2 = split_value + line_start.join(splitted[1:]) 11 | func.__doc__ = part1 + "\n" + line_start + value + "\n" + line_start + part2 12 | return func 13 | 14 | return _doc 15 | 16 | 17 | def monitor_docstring(mode) -> str: 18 | return f""" 19 | :param MonitorType{", optional" if mode == "edit" else ""} type: Monitor Type 20 | :param str{", optional" if mode == "edit" else ""} name: Friendly Name 21 | :param str, optional parent: Id of the monitor group, defaults to None 22 | :param str, optional description: Description, defaults to None 23 | :param int, optional interval: Heartbeat Interval, defaults to 60 24 | :param int, optional retryInterval: Retry every X seconds, defaults to 60 25 | :param int, optional resendInterval: Resend every X times, defaults to 0 26 | :param int, optional maxretries: Retries. Maximum retries before the service is marked as down and a notification is sent., defaults to 0 27 | :param bool, optional upsideDown: Upside Down Mode. Flip the status upside down. If the service is reachable, it is DOWN., defaults to False 28 | :param list, optional notificationIDList: Notifications, defaults to None 29 | :param str, optional url: URL, defaults to None 30 | :param bool, optional expiryNotification: Certificate Expiry Notification, defaults to False 31 | :param bool, optional ignoreTls: Ignore TLS/SSL error for HTTPS websites, defaults to False 32 | :param int, optional maxredirects: Max. Redirects. Maximum number of redirects to follow. Set to 0 to disable redirects., defaults to 10 33 | :param list, optional accepted_statuscodes: Accepted Status Codes. Select status codes which are considered as a successful response., defaults to None 34 | :param int, optional proxyId: Proxy, defaults to None 35 | :param str, optional method: Method, defaults to "GET" 36 | :param str, optional httpBodyEncoding: Body Encoding, defaults to "json". Allowed values: "json", "xml". 37 | :param str, optional body: Body, defaults to None 38 | :param str, optional headers: Headers, defaults to None 39 | :param AuthMethod, optional authMethod: Method, defaults to :attr:`~.AuthMethod.NONE` 40 | :param str, optional tlsCert: Cert for ``authMethod`` :attr:`~.AuthMethod.MTLS`, defaults to None. 41 | :param str, optional tlsKey: Key for ``authMethod`` :attr:`~.AuthMethod.MTLS`, defaults to None. 42 | :param str, optional tlsCa: Ca for ``authMethod`` :attr:`~.AuthMethod.MTLS`, defaults to None. 43 | :param str, optional basic_auth_user: Username for ``authMethod`` :attr:`~.AuthMethod.HTTP_BASIC` and :attr:`~.AuthMethod.NTLM`, defaults to None 44 | :param str, optional basic_auth_pass: Password for ``authMethod`` :attr:`~.AuthMethod.HTTP_BASIC` and :attr:`~.AuthMethod.NTLM`, defaults to None 45 | :param str, optional authDomain: Domain for ``authMethod`` :attr:`~.AuthMethod.NTLM`, defaults to None 46 | :param str, optional authWorkstation: Workstation for ``authMethod`` :attr:`~.AuthMethod.NTLM`, defaults to None 47 | :param str, optional oauth_auth_method: Authentication Method, defaults to None 48 | :param str, optional oauth_token_url: OAuth Token URL, defaults to None 49 | :param str, optional oauth_client_id: Client ID, defaults to None 50 | :param str, optional oauth_client_secret: Client Secret, defaults to None 51 | :param str, optional oauth_scopes: OAuth Scope, defaults to None 52 | :param int, optional timeout: Request Timeout, defaults to None 53 | :param str, optional keyword: Keyword. Search keyword in plain HTML or JSON response. The search is case-sensitive., defaults to None 54 | :param bool, optional invertKeyword: Invert Keyword. Look for the keyword to be absent rather than present., defaults to False 55 | :param str, optional hostname: Hostname, defaults to None 56 | :param int, optional packetSize: Packet Size, defaults to None 57 | :param int, optional port: Port, ``type`` :attr:`~.MonitorType.DNS` defaults to ``53`` and ``type`` :attr:`~.MonitorType.RADIUS` defaults to ``1812`` 58 | :param str, optional dns_resolve_server: Resolver Server, defaults to "1.1.1.1" 59 | :param str, optional dns_resolve_type: Resource Record Type, defaults to "A". Available values are: 60 | 61 | - "A" 62 | - "AAAA" 63 | - "CAA" 64 | - "CNAME" 65 | - "MX" 66 | - "NS" 67 | - "PTR" 68 | - "SOA" 69 | - "SRV" 70 | - "TXT" 71 | :param str, optional mqttUsername: MQTT Username, defaults to None 72 | :param str, optional mqttPassword: MQTT Password, defaults to None 73 | :param str, optional mqttTopic: MQTT Topic, defaults to None 74 | :param str, optional mqttSuccessMessage: MQTT Success Message, defaults to None 75 | :param str, optional databaseConnectionString: Connection String, defaults to None 76 | :param str, optional databaseQuery: Query, defaults to None 77 | :param str, optional docker_container: Container Name / ID, defaults to "" 78 | :param int, optional docker_host: Docker Host, defaults to None 79 | :param str, optional radiusUsername: Radius Username, defaults to None 80 | :param str, optional radiusPassword: Radius Password, defaults to None 81 | :param str, optional radiusSecret: Radius Secret. Shared Secret between client and server., defaults to None 82 | :param str, optional radiusCalledStationId: Called Station Id. Identifier of the called device., defaults to None 83 | :param str, optional radiusCallingStationId: Calling Station Id. Identifier of the calling device., defaults to None 84 | :param str, optional game: Game, defaults to None 85 | :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 86 | :param str, optional jsonPath: Json Query, defaults to None 87 | :param str, optional expectedValue: Expected Value, defaults to None 88 | :param str, optional kafkaProducerBrokers: Kafka Broker list, defaults to None 89 | :param str, optional kafkaProducerTopic: Kafka Topic Name, defaults to None 90 | :param str, optional kafkaProducerMessage: Kafka Producer Message, defaults to None 91 | :param bool, optional kafkaProducerSsl: Enable Kafka SSL, defaults to False 92 | :param bool, optional kafkaProducerAllowAutoTopicCreation: Enable Kafka Producer Auto Topic Creation, defaults to False 93 | :param dict, optional kafkaProducerSaslOptions: Kafka SASL Options 94 | 95 | - **mechanism** (*str*, *optional*): Mechanism, defaults to "None". Available values are: 96 | 97 | - "None" 98 | - "plain" 99 | - "scram-sha-256" 100 | - "scram-sha-512" 101 | - "aws" 102 | - **username** (*str*, *optional*): Username, defaults to None 103 | - **password** (*str*, *optional*): Password, defaults to None 104 | - **authorizationIdentity** (*str*, *optional*): Authorization Identity, defaults to None 105 | - **accessKeyId** (*str*, *optional*): AccessKey Id, defaults to None 106 | - **secretAccessKey** (*str*, *optional*): Secret AccessKey, defaults to None 107 | - **sessionToken** (*str*, *optional*): Session Token, defaults to None 108 | """ 109 | 110 | 111 | def notification_docstring(mode) -> str: 112 | return f""" 113 | :param str{", optional" if mode == "edit" else ""} name: Friendly Name 114 | :param NotificationType{", optional" if mode == "edit" else ""} type: Notification Type 115 | :param bool, optional isDefault: Default enabled. This notification will be enabled by default for new monitors. You can still disable the notification separately for each monitor., defaults to False 116 | :param bool, optional applyExisting: Apply on all existing monitors, defaults to False 117 | 118 | :param str, optional alertaApiEndpoint: Notification option for ``type`` :attr:`~.NotificationType.ALERTA`. 119 | :param str, optional alertaApiKey: Notification option for ``type`` :attr:`~.NotificationType.ALERTA`. 120 | :param str, optional alertaEnvironment: Notification option for ``type`` :attr:`~.NotificationType.ALERTA`. 121 | :param str, optional alertaAlertState: Notification option for ``type`` :attr:`~.NotificationType.ALERTA`. 122 | :param str, optional alertaRecoverState: Notification option for ``type`` :attr:`~.NotificationType.ALERTA`. 123 | :param str, optional alertNowWebhookURL: Notification option for ``type`` :attr:`~.NotificationType.ALERTNOW`. 124 | :param str, optional phonenumber: Notification option for ``type`` :attr:`~.NotificationType.ALIYUNSMS`. 125 | :param str, optional templateCode: Notification option for ``type`` :attr:`~.NotificationType.ALIYUNSMS`. 126 | :param str, optional signName: Notification option for ``type`` :attr:`~.NotificationType.ALIYUNSMS`. 127 | :param str, optional accessKeyId: Notification option for ``type`` :attr:`~.NotificationType.ALIYUNSMS`. 128 | :param str, optional secretAccessKey: Notification option for ``type`` :attr:`~.NotificationType.ALIYUNSMS`. 129 | :param str, optional appriseURL: Notification option for ``type`` :attr:`~.NotificationType.APPRISE`. 130 | :param str title: Notification option for ``type`` :attr:`~.NotificationType.APPRISE`. 131 | :param str, optional barkEndpoint: Notification option for ``type`` :attr:`~.NotificationType.BARK`. 132 | :param str, optional barkGroup: Notification option for ``type`` :attr:`~.NotificationType.BARK`. 133 | :param str, optional barkSound: Notification option for ``type`` :attr:`~.NotificationType.BARK`. 134 | :param str, optional clicksendsmsLogin: Notification option for ``type`` :attr:`~.NotificationType.CLICKSENDSMS`. 135 | :param str, optional clicksendsmsPassword: Notification option for ``type`` :attr:`~.NotificationType.CLICKSENDSMS`. 136 | :param str, optional clicksendsmsToNumber: Notification option for ``type`` :attr:`~.NotificationType.CLICKSENDSMS`. 137 | :param str clicksendsmsSenderName: Notification option for ``type`` :attr:`~.NotificationType.CLICKSENDSMS`. 138 | :param str, optional webHookUrl: Notification option for ``type`` :attr:`~.NotificationType.DINGDING`. 139 | :param str, optional secretKey: Notification option for ``type`` :attr:`~.NotificationType.DINGDING`. 140 | :param str discordUsername: Notification option for ``type`` :attr:`~.NotificationType.DISCORD`. 141 | :param str, optional discordWebhookUrl: Notification option for ``type`` :attr:`~.NotificationType.DISCORD`. 142 | :param str discordPrefixMessage: Notification option for ``type`` :attr:`~.NotificationType.DISCORD`. 143 | :param str, optional feishuWebHookUrl: Notification option for ``type`` :attr:`~.NotificationType.FEISHU`. 144 | :param str, optional flashdutySeverity: Notification option for ``type`` :attr:`~.NotificationType.FLASHDUTY`. 145 | :param str flashdutyIntegrationKey: Notification option for ``type`` :attr:`~.NotificationType.FLASHDUTY`. 146 | :param str, optional freemobileUser: Notification option for ``type`` :attr:`~.NotificationType.FREEMOBILE`. 147 | :param str, optional freemobilePass: Notification option for ``type`` :attr:`~.NotificationType.FREEMOBILE`. 148 | :param str, optional goAlertBaseURL: Notification option for ``type`` :attr:`~.NotificationType.GOALERT`. 149 | :param str, optional goAlertToken: Notification option for ``type`` :attr:`~.NotificationType.GOALERT`. 150 | :param str, optional googleChatWebhookURL: Notification option for ``type`` :attr:`~.NotificationType.GOOGLECHAT`. 151 | :param str, optional gorushDeviceToken: Notification option for ``type`` :attr:`~.NotificationType.GORUSH`. 152 | :param str gorushPlatform: Notification option for ``type`` :attr:`~.NotificationType.GORUSH`. 153 | :param str gorushTitle: Notification option for ``type`` :attr:`~.NotificationType.GORUSH`. 154 | :param str gorushPriority: Notification option for ``type`` :attr:`~.NotificationType.GORUSH`. 155 | :param int gorushRetry: Notification option for ``type`` :attr:`~.NotificationType.GORUSH`. 156 | :param str gorushTopic: Notification option for ``type`` :attr:`~.NotificationType.GORUSH`. 157 | :param str, optional gorushServerURL: Notification option for ``type`` :attr:`~.NotificationType.GORUSH`. 158 | :param str, optional gotifyserverurl: Notification option for ``type`` :attr:`~.NotificationType.GOTIFY`. 159 | :param str, optional gotifyapplicationToken: Notification option for ``type`` :attr:`~.NotificationType.GOTIFY`. 160 | :param int, optional gotifyPriority: Notification option for ``type`` :attr:`~.NotificationType.GOTIFY`. 161 | :param str notificationService: Notification option for ``type`` :attr:`~.NotificationType.HOMEASSISTANT`. 162 | :param str, optional homeAssistantUrl: Notification option for ``type`` :attr:`~.NotificationType.HOMEASSISTANT`. 163 | :param str, optional longLivedAccessToken: Notification option for ``type`` :attr:`~.NotificationType.HOMEASSISTANT`. 164 | :param str, optional kookGuildID: Notification option for ``type`` :attr:`~.NotificationType.KOOK`. 165 | :param str, optional kookBotToken: Notification option for ``type`` :attr:`~.NotificationType.KOOK`. 166 | :param str, optional lineChannelAccessToken: Notification option for ``type`` :attr:`~.NotificationType.LINE`. 167 | :param str, optional lineUserID: Notification option for ``type`` :attr:`~.NotificationType.LINE`. 168 | :param str, optional lineNotifyAccessToken: Notification option for ``type`` :attr:`~.NotificationType.LINENOTIFY`. 169 | :param str, optional lunaseaTarget: Notification option for ``type`` :attr:`~.NotificationType.LUNASEA`. Allowed values: "device", "user". 170 | :param str lunaseaUserID: Notification option for ``type`` :attr:`~.NotificationType.LUNASEA`. User ID. 171 | :param str lunaseaDevice: Notification option for ``type`` :attr:`~.NotificationType.LUNASEA`. Device ID. 172 | :param str, optional internalRoomId: Notification option for ``type`` :attr:`~.NotificationType.MATRIX`. 173 | :param str, optional accessToken: Notification option for ``type`` :attr:`~.NotificationType.MATRIX`. 174 | :param str, optional homeserverUrl: Notification option for ``type`` :attr:`~.NotificationType.MATRIX`. 175 | :param str mattermostusername: Notification option for ``type`` :attr:`~.NotificationType.MATTERMOST`. 176 | :param str, optional mattermostWebhookUrl: Notification option for ``type`` :attr:`~.NotificationType.MATTERMOST`. 177 | :param str mattermostchannel: Notification option for ``type`` :attr:`~.NotificationType.MATTERMOST`. 178 | :param str mattermosticonemo: Notification option for ``type`` :attr:`~.NotificationType.MATTERMOST`. 179 | :param str mattermosticonurl: Notification option for ``type`` :attr:`~.NotificationType.MATTERMOST`. 180 | :param str, optional sender: Notification option for ``type`` :attr:`~.NotificationType.NOSTR`. 181 | :param str, optional recipients: Notification option for ``type`` :attr:`~.NotificationType.NOSTR`. 182 | :param str, optional relays: Notification option for ``type`` :attr:`~.NotificationType.NOSTR`. 183 | :param str ntfyAuthenticationMethod: Notification option for ``type`` :attr:`~.NotificationType.NTFY`. Authentication Method. 184 | :param str ntfyusername: Notification option for ``type`` :attr:`~.NotificationType.NTFY`. 185 | :param str ntfypassword: Notification option for ``type`` :attr:`~.NotificationType.NTFY`. 186 | :param str ntfyaccesstoken: Notification option for ``type`` :attr:`~.NotificationType.NTFY`. Access Token. 187 | :param str, optional ntfytopic: Notification option for ``type`` :attr:`~.NotificationType.NTFY`. 188 | :param int, optional ntfyPriority: Notification option for ``type`` :attr:`~.NotificationType.NTFY`. 189 | :param str, optional ntfyserverurl: Notification option for ``type`` :attr:`~.NotificationType.NTFY`. 190 | :param str ntfyIcon: Notification option for ``type`` :attr:`~.NotificationType.NTFY`. 191 | :param str octopushVersion: Notification option for ``type`` :attr:`~.NotificationType.OCTOPUSH`. 192 | :param str, optional octopushAPIKey: Notification option for ``type`` :attr:`~.NotificationType.OCTOPUSH`. 193 | :param str, optional octopushLogin: Notification option for ``type`` :attr:`~.NotificationType.OCTOPUSH`. 194 | :param str, optional octopushPhoneNumber: Notification option for ``type`` :attr:`~.NotificationType.OCTOPUSH`. 195 | :param str octopushSMSType: Notification option for ``type`` :attr:`~.NotificationType.OCTOPUSH`. 196 | :param str octopushSenderName: Notification option for ``type`` :attr:`~.NotificationType.OCTOPUSH`. 197 | :param str, optional httpAddr: Notification option for ``type`` :attr:`~.NotificationType.ONEBOT`. 198 | :param str, optional accessToken: Notification option for ``type`` :attr:`~.NotificationType.ONEBOT`. 199 | :param str msgType: Notification option for ``type`` :attr:`~.NotificationType.ONEBOT`. 200 | :param str, optional recieverId: Notification option for ``type`` :attr:`~.NotificationType.ONEBOT`. 201 | :param int opsgeniePriority: Notification option for ``type`` :attr:`~.NotificationType.OPSGENIE`. Priority. Available values are numbers between ``1`` and ``5``. 202 | :param str, optional opsgenieRegion: Notification option for ``type`` :attr:`~.NotificationType.OPSGENIE`. Region. Available values are: 203 | 204 | - ``us``: US (Default) 205 | - ``eu``: EU 206 | :param str, optional opsgenieApiKey: Notification option for ``type`` :attr:`~.NotificationType.OPSGENIE`. API Key. 207 | :param str pagerdutyAutoResolve: Notification option for ``type`` :attr:`~.NotificationType.PAGERDUTY`. 208 | :param str pagerdutyIntegrationUrl: Notification option for ``type`` :attr:`~.NotificationType.PAGERDUTY`. 209 | :param str pagerdutyPriority: Notification option for ``type`` :attr:`~.NotificationType.PAGERDUTY`. 210 | :param str, optional pagerdutyIntegrationKey: Notification option for ``type`` :attr:`~.NotificationType.PAGERDUTY`. 211 | :param str pagertreeAutoResolve: Notification option for ``type`` :attr:`~.NotificationType.PAGERTREE`. 212 | 213 | Available values are: 214 | 215 | - ``0``: Do Nothing 216 | - ``resolve``: Auto Resolve 217 | :param str pagertreeIntegrationUrl: Notification option for ``type`` :attr:`~.NotificationType.PAGERTREE`. 218 | :param str pagertreeUrgency: Notification option for ``type`` :attr:`~.NotificationType.PAGERTREE`. 219 | 220 | Available values are: 221 | 222 | - ``silent``: Silent 223 | - ``low``: Low 224 | - ``medium``: Medium 225 | - ``high``: High 226 | - ``critical``: Critical 227 | :param bool promosmsAllowLongSMS: Notification option for ``type`` :attr:`~.NotificationType.PROMOSMS`. Allow long SMS. 228 | :param str, optional promosmsLogin: Notification option for ``type`` :attr:`~.NotificationType.PROMOSMS`. 229 | :param str, optional promosmsPassword: Notification option for ``type`` :attr:`~.NotificationType.PROMOSMS`. 230 | :param str, optional promosmsPhoneNumber: Notification option for ``type`` :attr:`~.NotificationType.PROMOSMS`. Phone number (for Polish recipient You can skip area codes). 231 | :param str promosmsSMSType: Notification option for ``type`` :attr:`~.NotificationType.PROMOSMS`. 232 | 233 | Available values are: 234 | 235 | - ``0``: SMS FLASH - Message will automatically show on recipient device. Limited only to Polish recipients. 236 | - ``1``: SMS ECO - cheap but slow and often overloaded. Limited only to Polish recipients. 237 | - ``3``: SMS FULL - Premium tier of SMS, You can use your Sender Name (You need to register name first). Reliable for alerts. 238 | - ``4``: SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price). 239 | :param str promosmsSenderName: Notification option for ``type`` :attr:`~.NotificationType.PROMOSMS`. 240 | :param str, optional pushbulletAccessToken: Notification option for ``type`` :attr:`~.NotificationType.PUSHBULLET`. 241 | :param str pushdeerServer: Notification option for ``type`` :attr:`~.NotificationType.PUSHDEER`. 242 | :param str, optional pushdeerKey: Notification option for ``type`` :attr:`~.NotificationType.PUSHDEER`. 243 | :param str, optional pushoveruserkey: Notification option for ``type`` :attr:`~.NotificationType.PUSHOVER`. 244 | :param str, optional pushoverapptoken: Notification option for ``type`` :attr:`~.NotificationType.PUSHOVER`. 245 | :param str pushoversounds: Notification option for ``type`` :attr:`~.NotificationType.PUSHOVER`. 246 | :param str pushoverpriority: Notification option for ``type`` :attr:`~.NotificationType.PUSHOVER`. 247 | :param str pushovertitle: Notification option for ``type`` :attr:`~.NotificationType.PUSHOVER`. 248 | :param str pushoverdevice: Notification option for ``type`` :attr:`~.NotificationType.PUSHOVER`. 249 | :param int pushoverttl: Notification option for ``type`` :attr:`~.NotificationType.PUSHOVER`. Message TTL (Seconds). 250 | :param str, optional pushyAPIKey: Notification option for ``type`` :attr:`~.NotificationType.PUSHY`. 251 | :param str, optional pushyToken: Notification option for ``type`` :attr:`~.NotificationType.PUSHY`. 252 | :param str rocketchannel: Notification option for ``type`` :attr:`~.NotificationType.ROCKET_CHAT`. 253 | :param str rocketusername: Notification option for ``type`` :attr:`~.NotificationType.ROCKET_CHAT`. 254 | :param str rocketiconemo: Notification option for ``type`` :attr:`~.NotificationType.ROCKET_CHAT`. 255 | :param str, optional rocketwebhookURL: Notification option for ``type`` :attr:`~.NotificationType.ROCKET_CHAT`. 256 | :param str, optional serverChanSendKey: Notification option for ``type`` :attr:`~.NotificationType.SERVERCHAN`. 257 | :param str, optional serwersmsUsername: Notification option for ``type`` :attr:`~.NotificationType.SERWERSMS`. 258 | :param str, optional serwersmsPassword: Notification option for ``type`` :attr:`~.NotificationType.SERWERSMS`. 259 | :param str, optional serwersmsPhoneNumber: Notification option for ``type`` :attr:`~.NotificationType.SERWERSMS`. 260 | :param str serwersmsSenderName: Notification option for ``type`` :attr:`~.NotificationType.SERWERSMS`. 261 | :param str, optional signalNumber: Notification option for ``type`` :attr:`~.NotificationType.SIGNAL`. 262 | :param str, optional signalRecipients: Notification option for ``type`` :attr:`~.NotificationType.SIGNAL`. 263 | :param str, optional signalURL: Notification option for ``type`` :attr:`~.NotificationType.SIGNAL`. 264 | :param bool slackchannelnotify: Notification option for ``type`` :attr:`~.NotificationType.SLACK`. 265 | :param str slackchannel: Notification option for ``type`` :attr:`~.NotificationType.SLACK`. 266 | :param str slackusername: Notification option for ``type`` :attr:`~.NotificationType.SLACK`. 267 | :param str slackiconemo: Notification option for ``type`` :attr:`~.NotificationType.SLACK`. 268 | :param str, optional slackwebhookURL: Notification option for ``type`` :attr:`~.NotificationType.SLACK`. 269 | :param str smscTranslit: Notification option for ``type`` :attr:`~.NotificationType.SMSC`. 270 | :param str, optional smscLogin: Notification option for ``type`` :attr:`~.NotificationType.SMSC`. 271 | :param str, optional smscPassword: Notification option for ``type`` :attr:`~.NotificationType.SMSC`. 272 | :param str, optional smscToNumber: Notification option for ``type`` :attr:`~.NotificationType.SMSC`. 273 | :param str smscSenderName: Notification option for ``type`` :attr:`~.NotificationType.SMSC`. 274 | :param bool smseagleEncoding: Notification option for ``type`` :attr:`~.NotificationType.SMSEAGLE`. True to send messages in unicode. 275 | :param int smseaglePriority: Notification option for ``type`` :attr:`~.NotificationType.SMSEAGLE`. Message priority (0-9, default = 0). 276 | :param str smseagleRecipientType: Notification option for ``type`` :attr:`~.NotificationType.SMSEAGLE`. Recipient type. 277 | 278 | Available values are: 279 | 280 | - ``smseagle-to``: Phone number(s) 281 | - ``smseagle-group``: Phonebook group name(s) 282 | - ``smseagle-contact``: Phonebook contact name(s) 283 | :param str, optional smseagleToken: Notification option for ``type`` :attr:`~.NotificationType.SMSEAGLE`. API Access token. 284 | :param str, optional smseagleRecipient: Notification option for ``type`` :attr:`~.NotificationType.SMSEAGLE`. Recipient(s) (multiple must be separated with comma). 285 | :param str, optional smseagleUrl: Notification option for ``type`` :attr:`~.NotificationType.SMSEAGLE`. Your SMSEagle device URL. 286 | :param str smsmanagerApiKey: Notification option for ``type`` :attr:`~.NotificationType.SMSMANAGER`. 287 | :param str numbers: Notification option for ``type`` :attr:`~.NotificationType.SMSMANAGER`. 288 | :param str messageType: Notification option for ``type`` :attr:`~.NotificationType.SMSMANAGER`. 289 | :param str, optional smtpHost: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 290 | :param int, optional smtpPort: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 291 | :param str smtpSecure: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 292 | :param bool smtpIgnoreTLSError: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 293 | :param str smtpDkimDomain: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 294 | :param str smtpDkimKeySelector: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 295 | :param str smtpDkimPrivateKey: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 296 | :param str smtpDkimHashAlgo: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 297 | :param str smtpDkimheaderFieldNames: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 298 | :param str smtpDkimskipFields: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 299 | :param str smtpUsername: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 300 | :param str smtpPassword: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 301 | :param str customSubject: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 302 | :param str, optional smtpFrom: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 303 | :param str smtpCC: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 304 | :param str smtpBCC: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 305 | :param str smtpTo: Notification option for ``type`` :attr:`~.NotificationType.SMTP`. 306 | :param str splunkAutoResolve: Notification option for ``type`` :attr:`~.NotificationType.SPLUNK`. Auto resolve or acknowledged. 307 | 308 | Available values are: 309 | 310 | - ``0``: do nothing 311 | - ``ACKNOWLEDGEMENT``: auto acknowledged 312 | - ``RECOVERY``: auto resolve 313 | :param str splunkSeverity: Notification option for ``type`` :attr:`~.NotificationType.SPLUNK`. Severity. 314 | 315 | Available values are: 316 | 317 | - ``INFO`` 318 | - ``WARNING`` 319 | - ``CRITICAL`` 320 | :param str, optional splunkRestURL: Notification option for ``type`` :attr:`~.NotificationType.SPLUNK`. Splunk Rest URL. 321 | :param str, optional squadcastWebhookURL: Notification option for ``type`` :attr:`~.NotificationType.SQUADCAST`. 322 | :param str, optional stackfieldwebhookURL: Notification option for ``type`` :attr:`~.NotificationType.STACKFIELD`. 323 | :param str, optional webhookUrl: Notification option for ``type`` :attr:`~.NotificationType.TEAMS`. 324 | :param str, optional pushAPIKey: Notification option for ``type`` :attr:`~.NotificationType.PUSHBYTECHULUS`. 325 | :param str, optional telegramChatID: Notification option for ``type`` :attr:`~.NotificationType.TELEGRAM`. 326 | :param bool telegramSendSilently: Notification option for ``type`` :attr:`~.NotificationType.TELEGRAM`. 327 | :param bool telegramProtectContent: Notification option for ``type`` :attr:`~.NotificationType.TELEGRAM`. 328 | :param str telegramMessageThreadID: Notification option for ``type`` :attr:`~.NotificationType.TELEGRAM`. 329 | :param str, optional telegramBotToken: Notification option for ``type`` :attr:`~.NotificationType.TELEGRAM`. 330 | :param str, optional twilioAccountSID: Notification option for ``type`` :attr:`~.NotificationType.TWILIO`. Account SID. 331 | :param str twilioApiKey: Notification option for ``type`` :attr:`~.NotificationType.TWILIO`. 332 | :param str, optional twilioAuthToken: Notification option for ``type`` :attr:`~.NotificationType.TWILIO`. Auth Token. 333 | :param str, optional twilioToNumber: Notification option for ``type`` :attr:`~.NotificationType.TWILIO`. To Number. 334 | :param str, optional twilioFromNumber: Notification option for ``type`` :attr:`~.NotificationType.TWILIO`. From Number. 335 | :param str, optional webhookContentType: Notification option for ``type`` :attr:`~.NotificationType.WEBHOOK`. 336 | :param str webhookCustomBody: Notification option for ``type`` :attr:`~.NotificationType.WEBHOOK`. 337 | :param str webhookAdditionalHeaders: Notification option for ``type`` :attr:`~.NotificationType.WEBHOOK`. 338 | :param str, optional webhookURL: Notification option for ``type`` :attr:`~.NotificationType.WEBHOOK`. 339 | :param str, optional weComBotKey: Notification option for ``type`` :attr:`~.NotificationType.WECOM`. 340 | :param str, optional webhookUrl: Notification option for ``type`` :attr:`~.NotificationType.ZOHOCLIQ`. 341 | """ 342 | 343 | 344 | def proxy_docstring(mode) -> str: 345 | return f""" 346 | :param ProxyProtocol{", optional" if mode == "edit" else ""} protocol: Proxy Protocol 347 | :param str{", optional" if mode == "edit" else ""} host: Proxy Server 348 | :param str{", optional" if mode == "edit" else ""} port: Port 349 | :param bool, optional auth: Proxy server has authentication, defaults to False 350 | :param str, optional username: User, defaults to None 351 | :param str, optional password: Password, defaults to None 352 | :param bool, optional active: Enabled. This proxy will not effect on monitor requests until it is activated. You can control temporarily disable the proxy from all monitors by activation status., defaults to True 353 | :param bool, optional default: Set As Default. This proxy will be enabled by default for new monitors. You can still disable the proxy separately for each monitor., , defaults to False 354 | :param bool, optional applyExisting: Apply on all existing monitors, defaults to False 355 | """ 356 | 357 | 358 | def docker_host_docstring(mode) -> str: 359 | return f""" 360 | :param str{", optional" if mode == "edit" else ""} name: Friendly Name 361 | :param DockerType{", optional" if mode == "edit" else ""} dockerType: Connection Type 362 | :param str, optional dockerDaemon: Docker Daemon, defaults to None 363 | """ 364 | 365 | 366 | def maintenance_docstring(mode) -> str: 367 | return f""" 368 | :param str{", optional" if mode == "edit" else ""} title: Title 369 | :param MaintenanceStrategy{", optional" if mode == "edit" else ""} strategy: Strategy 370 | :param bool, optional active: True if maintenance is active, defaults to ``True`` 371 | :param str, optional description: Description, defaults to ``""`` 372 | :param list, optional dateRange: DateTime Range, defaults to ``[""]`` 373 | :param int, optional intervalDay: Interval (Run once every day), defaults to ``1`` 374 | :param list, optional weekdays: List that contains the days of the week on which the maintenance is enabled (Sun = ``0``, Mon = ``1``, ..., Sat = ``6``). Required for ``strategy`` :attr:`~.MaintenanceStrategy.RECURRING_WEEKDAY`., defaults to ``[]``. 375 | :param list, optional daysOfMonth: List that contains the days of the month on which the maintenance is enabled (Day 1 = ``1``, Day 2 = ``2``, ..., Day 31 = ``31``) and the last day of the month (``"lastDay1"``). Required for ``strategy`` :attr:`~.MaintenanceStrategy.RECURRING_DAY_OF_MONTH`., defaults to ``[]``. 376 | :param list, optional timeRange: Maintenance Time Window of a Day, defaults to ``[{{"hours": 2, "minutes": 0}}, {{"hours": 3, "minutes": 0}}]``. 377 | :param str, optional cron: Cron Schedule. Required for ``strategy`` :attr:`~.MaintenanceStrategy.CRON`., defaults to ``"30 3 * * *"`` 378 | :param int, optional durationMinutes: Duration (Minutes). Required for ``strategy`` :attr:`~.MaintenanceStrategy.CRON`., defaults to ``60`` 379 | :param str, optional timezone: Timezone, defaults to ``None`` (Server Timezone) 380 | """ 381 | 382 | 383 | def tag_docstring(mode) -> str: 384 | return f""" 385 | :param str{", optional" if mode == "edit" else ""} name: Tag name 386 | :param str{", optional" if mode == "edit" else ""} color: Tag color 387 | """ 388 | -------------------------------------------------------------------------------- /uptime_kuma_api/event.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Event(str, Enum): 5 | CONNECT = "connect" 6 | DISCONNECT = "disconnect" 7 | MONITOR_LIST = "monitorList" 8 | NOTIFICATION_LIST = "notificationList" 9 | PROXY_LIST = "proxyList" 10 | STATUS_PAGE_LIST = "statusPageList" 11 | HEARTBEAT_LIST = "heartbeatList" 12 | IMPORTANT_HEARTBEAT_LIST = "importantHeartbeatList" 13 | AVG_PING = "avgPing" 14 | UPTIME = "uptime" 15 | HEARTBEAT = "heartbeat" 16 | INFO = "info" 17 | CERT_INFO = "certInfo" 18 | DOCKER_HOST_LIST = "dockerHostList" 19 | AUTO_LOGIN = "autoLogin" 20 | INIT_SERVER_TIMEZONE = "initServerTimezone" 21 | MAINTENANCE_LIST = "maintenanceList" 22 | API_KEY_LIST = "apiKeyList" 23 | -------------------------------------------------------------------------------- /uptime_kuma_api/exceptions.py: -------------------------------------------------------------------------------- 1 | class UptimeKumaException(Exception): 2 | """ 3 | There was an exception that occurred while communicating with Uptime Kuma. 4 | """ 5 | 6 | 7 | class Timeout(UptimeKumaException): 8 | """ 9 | A timeout has occurred while communicating with Uptime Kuma. 10 | """ 11 | -------------------------------------------------------------------------------- /uptime_kuma_api/incident_style.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class IncidentStyle(str, Enum): 5 | """Enumerate incident styles.""" 6 | 7 | INFO = "info" 8 | """Info""" 9 | 10 | WARNING = "warning" 11 | """Warning""" 12 | 13 | DANGER = "danger" 14 | """Danger""" 15 | 16 | PRIMARY = "primary" 17 | """Primary""" 18 | 19 | LIGHT = "light" 20 | """Light""" 21 | 22 | DARK = "dark" 23 | """Dark""" 24 | -------------------------------------------------------------------------------- /uptime_kuma_api/maintenance_strategy.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class MaintenanceStrategy(str, Enum): 5 | """Enumerate maintenance strategies.""" 6 | 7 | MANUAL = "manual" 8 | """Active/Inactive Manually""" 9 | 10 | SINGLE = "single" 11 | """Single Maintenance Window""" 12 | 13 | RECURRING_INTERVAL = "recurring-interval" 14 | """Recurring - Interval""" 15 | 16 | RECURRING_WEEKDAY = "recurring-weekday" 17 | """Recurring - Day of Week""" 18 | 19 | RECURRING_DAY_OF_MONTH = "recurring-day-of-month" 20 | """Recurring - Day of Month""" 21 | 22 | CRON = "cron" 23 | """Cron Expression""" 24 | -------------------------------------------------------------------------------- /uptime_kuma_api/monitor_status.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class MonitorStatus(int, Enum): 5 | """Enumerate monitor statuses.""" 6 | 7 | DOWN = 0 8 | """DOWN""" 9 | 10 | UP = 1 11 | """UP""" 12 | 13 | PENDING = 2 14 | """PENDING""" 15 | 16 | MAINTENANCE = 3 17 | """MAINTENANCE""" 18 | -------------------------------------------------------------------------------- /uptime_kuma_api/monitor_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class MonitorType(str, Enum): 5 | """Enumerate monitor types.""" 6 | 7 | GROUP = "group" 8 | """Group""" 9 | 10 | HTTP = "http" 11 | """HTTP(s)""" 12 | 13 | PORT = "port" 14 | """TCP Port""" 15 | 16 | PING = "ping" 17 | """Ping""" 18 | 19 | KEYWORD = "keyword" 20 | """HTTP(s) - Keyword""" 21 | 22 | JSON_QUERY = "json-query" 23 | """HTTP(s) - Json Query""" 24 | 25 | GRPC_KEYWORD = "grpc-keyword" 26 | """gRPC(s) - Keyword""" 27 | 28 | DNS = "dns" 29 | """DNS""" 30 | 31 | DOCKER = "docker" 32 | """Docker Container""" 33 | 34 | REAL_BROWSER = "real-browser" 35 | """HTTP(s) - Browser Engine (Chrome/Chromium)""" 36 | 37 | PUSH = "push" 38 | """Push""" 39 | 40 | STEAM = "steam" 41 | """Steam Game Server""" 42 | 43 | GAMEDIG = "gamedig" 44 | """GameDig""" 45 | 46 | MQTT = "mqtt" 47 | """MQTT""" 48 | 49 | KAFKA_PRODUCER = "kafka-producer" 50 | """Kafka Producer""" 51 | 52 | SQLSERVER = "sqlserver" 53 | """Microsoft SQL Server""" 54 | 55 | POSTGRES = "postgres" 56 | """PostgreSQL""" 57 | 58 | MYSQL = "mysql" 59 | """MySQL/MariaDB""" 60 | 61 | MONGODB = "mongodb" 62 | """MongoDB""" 63 | 64 | RADIUS = "radius" 65 | """Radius""" 66 | 67 | REDIS = "redis" 68 | """Redis""" 69 | 70 | TAILSCALE_PING = "tailscale-ping" 71 | """Tailscale Ping""" 72 | -------------------------------------------------------------------------------- /uptime_kuma_api/notification_providers.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class NotificationType(str, Enum): 5 | """Enumerate notification types.""" 6 | 7 | ALERTA = "alerta" 8 | """Alerta""" 9 | 10 | ALERTNOW = "AlertNow" 11 | """AlertNow""" 12 | 13 | ALIYUNSMS = "AliyunSMS" 14 | """AliyunSMS""" 15 | 16 | APPRISE = "apprise" 17 | """Apprise (Support 50+ Notification services)""" 18 | 19 | BARK = "Bark" 20 | """Bark""" 21 | 22 | CLICKSENDSMS = "clicksendsms" 23 | """ClickSend SMS""" 24 | 25 | DINGDING = "DingDing" 26 | """DingDing""" 27 | 28 | DISCORD = "discord" 29 | """Discord""" 30 | 31 | FEISHU = "Feishu" 32 | """Feishu""" 33 | 34 | FLASHDUTY = "FlashDuty" 35 | """FlashDuty""" 36 | 37 | FREEMOBILE = "FreeMobile" 38 | """FreeMobile (mobile.free.fr)""" 39 | 40 | GOALERT = "GoAlert" 41 | """GoAlert""" 42 | 43 | GOOGLECHAT = "GoogleChat" 44 | """Google Chat (Google Workspace)""" 45 | 46 | GORUSH = "gorush" 47 | """Gorush""" 48 | 49 | GOTIFY = "gotify" 50 | """Gotify""" 51 | 52 | HOMEASSISTANT = "HomeAssistant" 53 | """Home Assistant""" 54 | 55 | KOOK = "Kook" 56 | """Kook""" 57 | 58 | LINE = "line" 59 | """LINE Messenger""" 60 | 61 | LINENOTIFY = "LineNotify" 62 | """LINE Notify""" 63 | 64 | LUNASEA = "lunasea" 65 | """LunaSea""" 66 | 67 | MATRIX = "matrix" 68 | """Matrix""" 69 | 70 | MATTERMOST = "mattermost" 71 | """Mattermost""" 72 | 73 | NOSTR = "nostr" 74 | """Nostr""" 75 | 76 | NTFY = "ntfy" 77 | """Ntfy""" 78 | 79 | OCTOPUSH = "octopush" 80 | """Octopush""" 81 | 82 | ONEBOT = "OneBot" 83 | """OneBot""" 84 | 85 | OPSGENIE = "Opsgenie" 86 | """Opsgenie""" 87 | 88 | PAGERDUTY = "PagerDuty" 89 | """PagerDuty""" 90 | 91 | PAGERTREE = "PagerTree" 92 | """PagerTree""" 93 | 94 | PROMOSMS = "promosms" 95 | """PromoSMS""" 96 | 97 | PUSHBULLET = "pushbullet" 98 | """Pushbullet""" 99 | 100 | PUSHDEER = "PushDeer" 101 | """PushDeer""" 102 | 103 | PUSHOVER = "pushover" 104 | """Pushover""" 105 | 106 | PUSHY = "pushy" 107 | """Pushy""" 108 | 109 | ROCKET_CHAT = "rocket.chat" 110 | """Rocket.Chat""" 111 | 112 | SERVERCHAN = "ServerChan" 113 | """ServerChan""" 114 | 115 | SERWERSMS = "serwersms" 116 | """SerwerSMS.pl""" 117 | 118 | SIGNAL = "signal" 119 | """Signal""" 120 | 121 | SLACK = "slack" 122 | """Slack""" 123 | 124 | SMSC = "smsc" 125 | """SMSC""" 126 | 127 | SMSEAGLE = "SMSEagle" 128 | """SMSEagle""" 129 | 130 | SMSMANAGER = "SMSManager" 131 | """SmsManager (smsmanager.cz)""" 132 | 133 | SMTP = "smtp" 134 | """Email (SMTP)""" 135 | 136 | SPLUNK = "Splunk" 137 | """Splunk""" 138 | 139 | SQUADCAST = "squadcast" 140 | """SquadCast""" 141 | 142 | STACKFIELD = "stackfield" 143 | """Stackfield""" 144 | 145 | TEAMS = "teams" 146 | """Microsoft Teams""" 147 | 148 | PUSHBYTECHULUS = "PushByTechulus" 149 | """Push by Techulus""" 150 | 151 | TELEGRAM = "telegram" 152 | """Telegram""" 153 | 154 | TWILIO = "twilio" 155 | """Twilio""" 156 | 157 | WEBHOOK = "webhook" 158 | """Webhook""" 159 | 160 | WECOM = "WeCom" 161 | """WeCom""" 162 | 163 | ZOHOCLIQ = "ZohoCliq" 164 | """ZohoCliq""" 165 | 166 | 167 | notification_provider_options = { 168 | NotificationType.ALERTA: dict( 169 | alertaApiEndpoint=dict(type="str", required=True), 170 | alertaApiKey=dict(type="str", required=True), 171 | alertaEnvironment=dict(type="str", required=True), 172 | alertaAlertState=dict(type="str", required=True), 173 | alertaRecoverState=dict(type="str", required=True), 174 | ), 175 | NotificationType.ALERTNOW: dict( 176 | alertNowWebhookURL=dict(type="str", required=True), 177 | ), 178 | NotificationType.ALIYUNSMS: dict( 179 | phonenumber=dict(type="str", required=True), 180 | templateCode=dict(type="str", required=True), 181 | signName=dict(type="str", required=True), 182 | accessKeyId=dict(type="str", required=True), 183 | secretAccessKey=dict(type="str", required=True), 184 | ), 185 | NotificationType.APPRISE: dict( 186 | appriseURL=dict(type="str", required=True), 187 | title=dict(type="str", required=False), 188 | ), 189 | NotificationType.BARK: dict( 190 | barkEndpoint=dict(type="str", required=True), 191 | barkGroup=dict(type="str", required=True), 192 | barkSound=dict(type="str", required=True), 193 | ), 194 | NotificationType.CLICKSENDSMS: dict( 195 | clicksendsmsLogin=dict(type="str", required=True), 196 | clicksendsmsPassword=dict(type="str", required=True), 197 | clicksendsmsToNumber=dict(type="str", required=True), 198 | clicksendsmsSenderName=dict(type="str", required=False), 199 | ), 200 | NotificationType.DINGDING: dict( 201 | webHookUrl=dict(type="str", required=True), 202 | secretKey=dict(type="str", required=True), 203 | ), 204 | NotificationType.DISCORD: dict( 205 | discordUsername=dict(type="str", required=False), 206 | discordWebhookUrl=dict(type="str", required=True), 207 | discordPrefixMessage=dict(type="str", required=False), 208 | ), 209 | NotificationType.FEISHU: dict( 210 | feishuWebHookUrl=dict(type="str", required=True), 211 | ), 212 | NotificationType.FLASHDUTY: dict( 213 | flashdutySeverity=dict(type="str", required=True), 214 | flashdutyIntegrationKey=dict(type="str", required=False), 215 | ), 216 | NotificationType.FREEMOBILE: dict( 217 | freemobileUser=dict(type="str", required=True), 218 | freemobilePass=dict(type="str", required=True), 219 | ), 220 | NotificationType.GOALERT: dict( 221 | goAlertBaseURL=dict(type="str", required=True), 222 | goAlertToken=dict(type="str", required=True), 223 | ), 224 | NotificationType.GOOGLECHAT: dict( 225 | googleChatWebhookURL=dict(type="str", required=True), 226 | ), 227 | NotificationType.GORUSH: dict( 228 | gorushDeviceToken=dict(type="str", required=True), 229 | gorushPlatform=dict(type="str", required=False), 230 | gorushTitle=dict(type="str", required=False), 231 | gorushPriority=dict(type="str", required=False), 232 | gorushRetry=dict(type="int", required=False), 233 | gorushTopic=dict(type="str", required=False), 234 | gorushServerURL=dict(type="str", required=True), 235 | ), 236 | NotificationType.GOTIFY: dict( 237 | gotifyserverurl=dict(type="str", required=True), 238 | gotifyapplicationToken=dict(type="str", required=True), 239 | gotifyPriority=dict(type="int", required=True), 240 | ), 241 | NotificationType.HOMEASSISTANT: dict( 242 | notificationService=dict(type="str", required=False), 243 | homeAssistantUrl=dict(type="str", required=True), 244 | longLivedAccessToken=dict(type="str", required=True), 245 | ), 246 | NotificationType.KOOK: dict( 247 | kookGuildID=dict(type="str", required=True), 248 | kookBotToken=dict(type="str", required=True), 249 | ), 250 | NotificationType.LINE: dict( 251 | lineChannelAccessToken=dict(type="str", required=True), 252 | lineUserID=dict(type="str", required=True), 253 | ), 254 | NotificationType.LINENOTIFY: dict( 255 | lineNotifyAccessToken=dict(type="str", required=True), 256 | ), 257 | NotificationType.LUNASEA: dict( 258 | lunaseaTarget=dict(type="str", required=True), 259 | lunaseaUserID=dict(type="str", required=False), 260 | lunaseaDevice=dict(type="str", required=False), 261 | ), 262 | NotificationType.MATRIX: dict( 263 | internalRoomId=dict(type="str", required=True), 264 | accessToken=dict(type="str", required=True), 265 | homeserverUrl=dict(type="str", required=True), 266 | ), 267 | NotificationType.MATTERMOST: dict( 268 | mattermostusername=dict(type="str", required=False), 269 | mattermostWebhookUrl=dict(type="str", required=True), 270 | mattermostchannel=dict(type="str", required=False), 271 | mattermosticonemo=dict(type="str", required=False), 272 | mattermosticonurl=dict(type="str", required=False), 273 | ), 274 | NotificationType.NOSTR: dict( 275 | sender=dict(type="str", required=True), 276 | recipients=dict(type="str", required=True), 277 | relays=dict(type="str", required=True), 278 | ), 279 | NotificationType.NTFY: dict( 280 | ntfyAuthenticationMethod=dict(type="str", required=False), 281 | ntfyusername=dict(type="str", required=False), 282 | ntfypassword=dict(type="str", required=False), 283 | ntfyaccesstoken=dict(type="str", required=False), 284 | ntfytopic=dict(type="str", required=True), 285 | ntfyPriority=dict(type="int", required=True), 286 | ntfyserverurl=dict(type="str", required=True), 287 | ntfyIcon=dict(type="str", required=False), 288 | ), 289 | NotificationType.OCTOPUSH: dict( 290 | octopushVersion=dict(type="str", required=False), 291 | octopushAPIKey=dict(type="str", required=True), 292 | octopushLogin=dict(type="str", required=True), 293 | octopushPhoneNumber=dict(type="str", required=True), 294 | octopushSMSType=dict(type="str", required=False), 295 | octopushSenderName=dict(type="str", required=False), 296 | ), 297 | NotificationType.ONEBOT: dict( 298 | httpAddr=dict(type="str", required=True), 299 | accessToken=dict(type="str", required=True), 300 | msgType=dict(type="str", required=False), 301 | recieverId=dict(type="str", required=True), 302 | ), 303 | NotificationType.OPSGENIE: dict( 304 | opsgeniePriority=dict(type="int", required=False), 305 | opsgenieRegion=dict(type="str", required=True), 306 | opsgenieApiKey=dict(type="str", required=True), 307 | ), 308 | NotificationType.PAGERDUTY: dict( 309 | pagerdutyAutoResolve=dict(type="str", required=False), 310 | pagerdutyIntegrationUrl=dict(type="str", required=False), 311 | pagerdutyPriority=dict(type="str", required=False), 312 | pagerdutyIntegrationKey=dict(type="str", required=True), 313 | ), 314 | NotificationType.PAGERTREE: dict( 315 | pagertreeAutoResolve=dict(type="str", required=False), 316 | pagertreeIntegrationUrl=dict(type="str", required=False), 317 | pagertreeUrgency=dict(type="str", required=False), 318 | ), 319 | NotificationType.PROMOSMS: dict( 320 | promosmsAllowLongSMS=dict(type="bool", required=False), 321 | promosmsLogin=dict(type="str", required=True), 322 | promosmsPassword=dict(type="str", required=True), 323 | promosmsPhoneNumber=dict(type="str", required=True), 324 | promosmsSMSType=dict(type="str", required=False), 325 | promosmsSenderName=dict(type="str", required=False), 326 | ), 327 | NotificationType.PUSHBULLET: dict( 328 | pushbulletAccessToken=dict(type="str", required=True), 329 | ), 330 | NotificationType.PUSHDEER: dict( 331 | pushdeerServer=dict(type="str", required=False), 332 | pushdeerKey=dict(type="str", required=True), 333 | ), 334 | NotificationType.PUSHOVER: dict( 335 | pushoveruserkey=dict(type="str", required=True), 336 | pushoverapptoken=dict(type="str", required=True), 337 | pushoversounds=dict(type="str", required=False), 338 | pushoverpriority=dict(type="str", required=False), 339 | pushovertitle=dict(type="str", required=False), 340 | pushoverdevice=dict(type="str", required=False), 341 | pushoverttl=dict(type="int", required=False), 342 | ), 343 | NotificationType.PUSHY: dict( 344 | pushyAPIKey=dict(type="str", required=True), 345 | pushyToken=dict(type="str", required=True), 346 | ), 347 | NotificationType.ROCKET_CHAT: dict( 348 | rocketchannel=dict(type="str", required=False), 349 | rocketusername=dict(type="str", required=False), 350 | rocketiconemo=dict(type="str", required=False), 351 | rocketwebhookURL=dict(type="str", required=True), 352 | ), 353 | NotificationType.SERVERCHAN: dict( 354 | serverChanSendKey=dict(type="str", required=True), 355 | ), 356 | NotificationType.SERWERSMS: dict( 357 | serwersmsUsername=dict(type="str", required=True), 358 | serwersmsPassword=dict(type="str", required=True), 359 | serwersmsPhoneNumber=dict(type="str", required=True), 360 | serwersmsSenderName=dict(type="str", required=False), 361 | ), 362 | NotificationType.SIGNAL: dict( 363 | signalNumber=dict(type="str", required=True), 364 | signalRecipients=dict(type="str", required=True), 365 | signalURL=dict(type="str", required=True), 366 | ), 367 | NotificationType.SLACK: dict( 368 | slackchannelnotify=dict(type="bool", required=False), 369 | slackchannel=dict(type="str", required=False), 370 | slackusername=dict(type="str", required=False), 371 | slackiconemo=dict(type="str", required=False), 372 | slackwebhookURL=dict(type="str", required=True), 373 | ), 374 | NotificationType.SMSC: dict( 375 | smscTranslit=dict(type="str", required=False), 376 | smscLogin=dict(type="str", required=True), 377 | smscPassword=dict(type="str", required=True), 378 | smscToNumber=dict(type="str", required=True), 379 | smscSenderName=dict(type="str", required=False), 380 | ), 381 | NotificationType.SMSEAGLE: dict( 382 | smseagleEncoding=dict(type="bool", required=False), 383 | smseaglePriority=dict(type="int", required=False), 384 | smseagleRecipientType=dict(type="str", required=False), 385 | smseagleToken=dict(type="str", required=True), 386 | smseagleRecipient=dict(type="str", required=True), 387 | smseagleUrl=dict(type="str", required=True), 388 | ), 389 | NotificationType.SMSMANAGER: dict( 390 | smsmanagerApiKey=dict(type="str", required=False), 391 | numbers=dict(type="str", required=False), 392 | messageType=dict(type="str", required=False), 393 | ), 394 | NotificationType.SMTP: dict( 395 | smtpHost=dict(type="str", required=True), 396 | smtpPort=dict(type="int", required=True), 397 | smtpSecure=dict(type="str", required=False), 398 | smtpIgnoreTLSError=dict(type="bool", required=False), 399 | smtpDkimDomain=dict(type="str", required=False), 400 | smtpDkimKeySelector=dict(type="str", required=False), 401 | smtpDkimPrivateKey=dict(type="str", required=False), 402 | smtpDkimHashAlgo=dict(type="str", required=False), 403 | smtpDkimheaderFieldNames=dict(type="str", required=False), 404 | smtpDkimskipFields=dict(type="str", required=False), 405 | smtpUsername=dict(type="str", required=False), 406 | smtpPassword=dict(type="str", required=False), 407 | customSubject=dict(type="str", required=False), 408 | smtpFrom=dict(type="str", required=True), 409 | smtpCC=dict(type="str", required=False), 410 | smtpBCC=dict(type="str", required=False), 411 | smtpTo=dict(type="str", required=False), 412 | ), 413 | NotificationType.SPLUNK: dict( 414 | splunkAutoResolve=dict(type="str", required=False), 415 | splunkSeverity=dict(type="str", required=False), 416 | splunkRestURL=dict(type="str", required=True), 417 | ), 418 | NotificationType.SQUADCAST: dict( 419 | squadcastWebhookURL=dict(type="str", required=True), 420 | ), 421 | NotificationType.STACKFIELD: dict( 422 | stackfieldwebhookURL=dict(type="str", required=True), 423 | ), 424 | NotificationType.TEAMS: dict( 425 | webhookUrl=dict(type="str", required=True), 426 | ), 427 | NotificationType.PUSHBYTECHULUS: dict( 428 | pushAPIKey=dict(type="str", required=True), 429 | ), 430 | NotificationType.TELEGRAM: dict( 431 | telegramChatID=dict(type="str", required=True), 432 | telegramSendSilently=dict(type="bool", required=False), 433 | telegramProtectContent=dict(type="bool", required=False), 434 | telegramMessageThreadID=dict(type="str", required=False), 435 | telegramBotToken=dict(type="str", required=True), 436 | ), 437 | NotificationType.TWILIO: dict( 438 | twilioAccountSID=dict(type="str", required=True), 439 | twilioApiKey=dict(type="str", required=False), 440 | twilioAuthToken=dict(type="str", required=True), 441 | twilioToNumber=dict(type="str", required=True), 442 | twilioFromNumber=dict(type="str", required=True), 443 | ), 444 | NotificationType.WEBHOOK: dict( 445 | webhookContentType=dict(type="str", required=True), 446 | webhookCustomBody=dict(type="str", required=False), 447 | webhookAdditionalHeaders=dict(type="str", required=False), 448 | webhookURL=dict(type="str", required=True), 449 | ), 450 | NotificationType.WECOM: dict( 451 | weComBotKey=dict(type="str", required=True), 452 | ), 453 | NotificationType.ZOHOCLIQ: dict( 454 | webhookUrl=dict(type="str", required=True), 455 | ), 456 | } 457 | 458 | notification_provider_conditions = dict( 459 | gotifyPriority=dict( 460 | min=0, 461 | max=10, 462 | ), 463 | ntfyPriority=dict( 464 | min=1, 465 | max=5, 466 | ), 467 | opsgeniePriority=dict( 468 | min=1, 469 | max=5, 470 | ), 471 | pushoverttl=dict( 472 | min=0, 473 | ), 474 | smseaglePriority=dict( 475 | min=0, 476 | max=9, 477 | ), 478 | smtpPort=dict( 479 | min=0, 480 | max=65535, 481 | ), 482 | ) 483 | -------------------------------------------------------------------------------- /uptime_kuma_api/proxy_protocol.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ProxyProtocol(str, Enum): 5 | """Enumerate proxy protocols.""" 6 | 7 | HTTPS = "https" 8 | """HTTPS""" 9 | 10 | HTTP = "http" 11 | """HTTP""" 12 | 13 | SOCKS = "socks" 14 | """SOCKS""" 15 | 16 | SOCKS5 = "socks5" 17 | """SOCKS v5""" 18 | 19 | SOCKS5H = "socks5h" 20 | """SOCKS v5 (+DNS)""" 21 | 22 | SOCKS4 = "socks4" 23 | """SOCKS v4""" 24 | --------------------------------------------------------------------------------