diff --git a/.gitignore b/.gitignore index e5cb804..2335d78 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /django_debug.log +**/__pycache__/ diff --git a/apps/monitoring/statistics.py b/apps/monitoring/statistics.py index 7bd4283..4194667 100644 --- a/apps/monitoring/statistics.py +++ b/apps/monitoring/statistics.py @@ -1,10 +1,11 @@ from datetime import timedelta +from django.db.models import Avg +from django.utils import timezone from django.utils.timezone import now from apps.monitoring.models import UptimeRecord - QUERY_TIME_RANGE_TYPE = { 1: "Last 1 hour", 3: "Last 3 hours", @@ -12,31 +13,103 @@ 24: "Last 24 hours", 168: "Last 7 days", 720: "Last 30 days", + -1: "All time", } -def calculate_past(time_range=None): +def calculate_past_summary(time_range=None): """ Given an time range in HOUR?DATE, query all UptimeRecord and calculate uptime percentage and the average response time - :return: uptime_percentage and avg_response_time + :return:total_records, uptime_percentage and avg_response_time """ - uptime_percentage, avg_response_time = None, None - if not time_range or time_range.value not in QUERY_TIME_RANGE_TYPE.keys(): + if (not time_range) or (time_range not in QUERY_TIME_RANGE_TYPE.keys()): return uptime_percentage and avg_response_time - time_delta = time_range.value + time_delta = time_range results = UptimeRecord.objects.filter( created_at__gte=now() - timedelta(hours=time_delta) ) - # results = UptimeRecord.objects.filter(created_at? range) - sum_avg_time, sum_uptime = 0, 0 total_records = results.count() - for record in results: - sum_avg_time += record.response_time - sum_uptime += 1 if record.status else 0 - if total_records: - avg_response_time = sum_avg_time / total_records - uptime_percentage = (sum_uptime / total_records) * 100 - return uptime_percentage, avg_response_time + up_records = results.filter(status=True).count() + average_response_time = ( + results.filter(status=True).aggregate(Avg("response_time"))[ + "response_time__avg" + ] + or 0 + ) + + uptime_percentage = (up_records / total_records) * 100 if total_records else 0 + + return total_records, uptime_percentage, average_response_time + + +def calculate_past_chart(time_range, split_interval): + """ + Given a time range in HOUR, query all UptimeRecord and + calculate uptime_percentage and the average_response_time in the interval for chart, + the interval is calculated by showing 30 records in the chart. E.g. if the time_range is 720 hours, + the chart will show 30 records, each record represents 24 hours. + :return: a json contains a summary of uptime percentage and average response time, following 30 detailed records + where each record contains total_records, uptime_percentage, average_response_time, time_start and time_end + + """ + + if (not time_range) or (time_range not in QUERY_TIME_RANGE_TYPE.keys()): + return KeyError("Invalid time range") + # iterate 30 intervals in the given time range + if split_interval < 1: + split_interval = 1 + delta = timedelta(hours=time_range / split_interval) + start_time = now() - timedelta(hours=time_range) + total_records, total_up_records = 0, 0 + all_results = [] + total_avg_response_time = [] + + for _ in range(split_interval): + end_time = start_time + delta + results = UptimeRecord.objects.filter( + created_at__gte=start_time, created_at__lt=end_time + ) + interval_total_records = results.count() + interval_up_records = results.filter(status=True).count() + average_response_time = ( + results.filter(status=True).aggregate(Avg("response_time"))[ + "response_time__avg" + ] + or 0 + ) + all_results.append( + { + "uptime_percentage": (interval_up_records / interval_total_records) + * 100 + if interval_total_records + else 0, + "average_response_time": average_response_time, + "time_start": timezone.localtime(end_time).strftime("%b. %-d, %H:%M"), + } + ) + total_records += interval_total_records + total_up_records += interval_up_records + total_avg_response_time.append(average_response_time) + start_time = end_time + + total_avg_response_time = sum(total_avg_response_time) / len( + total_avg_response_time + ) + uptime_percentage = (total_up_records / total_records) * 100 if total_records else 0 + + summary = { + "time_range": time_range, + "total_records": total_records, + "uptime_percentage": uptime_percentage, + "average_response_time": total_avg_response_time, + "time_start": timezone.localtime(now()).strftime("%b. %-d, %H:%M"), + "time_end": timezone.localtime(now()).strftime("%b. %-d, %H:%M"), + } + response = { + "summary": summary, + "data": all_results, + } + return response diff --git a/apps/monitoring/tasks.py b/apps/monitoring/tasks.py index 1ae0be8..b4798bd 100644 --- a/apps/monitoring/tasks.py +++ b/apps/monitoring/tasks.py @@ -1,7 +1,7 @@ from celery import shared_task -from .models import Service, UptimeRecord +from apps.monitoring.models import Service, UptimeRecord from apps.notification.models import NotificationChannel, NotificationLog -from .utils import check_service_status +from apps.monitoring.utils import check_service_status # logger = get_task_logger(__nae__) @@ -29,16 +29,21 @@ def check_monitor_services_status(service_id=None): error_message=error_message, service=service, ) - # breakpoint() + if not Service.objects.filter(id=service_id).exists(): + return if not is_up: message = f"Service {service.name} is down." - channels = NotificationChannel.objects.all() # Example: Notify all channels + channels = Service.objects.get(id=service_id).notification_channel.all() for channel in channels: was_success = channel.send_notification(service, message) NotificationLog.objects.create( service=service, message=message, was_success=was_success ) else: + # check the last three records are up or not, if all up, do not send notification + records = UptimeRecord.objects.filter(service=service).order_by("-check_at")[:3] + if len(records) == 3 and all(record.status for record in records): + return message = f"Service {service.name} is up." channels = NotificationChannel.objects.all() # Example: Notify all channels for channel in channels: @@ -46,3 +51,8 @@ def check_monitor_services_status(service_id=None): NotificationLog.objects.create( service=service, message=message, was_success=was_success ) + + +if __name__ == "__main__": + check_monitor_services_status(service_id=1) + print("check_monitor_services_status()") diff --git a/apps/monitoring/views.py b/apps/monitoring/views.py index a2ed5bc..2671b07 100644 --- a/apps/monitoring/views.py +++ b/apps/monitoring/views.py @@ -1,12 +1,19 @@ from rest_framework import viewsets from django_celery_beat.models import IntervalSchedule, PeriodicTask +from rest_framework.decorators import action +from rest_framework.response import Response -from .models import UptimeRecord -from .serializers import ( +from apps.monitoring.models import UptimeRecord +from apps.monitoring.serializers import ( IntervalScheduleSerializer, PeriodicTaskSerializer, UptimeRecordSerializer, ) +from apps.monitoring.statistics import ( + QUERY_TIME_RANGE_TYPE, + calculate_past_summary, + calculate_past_chart, +) class IntervalScheduleViewSet(viewsets.ModelViewSet): @@ -22,3 +29,37 @@ class PeriodicTaskViewSet(viewsets.ModelViewSet): class UptimeRecordViewSet(viewsets.ModelViewSet): queryset = UptimeRecord.objects.all() serializer_class = UptimeRecordSerializer + + @action(detail=False, methods=["get"]) + def stats(self, request): + # service_id = request.query_params.get("service_id") + time_range = int(request.query_params.get("time_range", 1)) + + # Apply time_range if specified and valid + if time_range not in QUERY_TIME_RANGE_TYPE: + return Response({"error": "Invalid time range"}, status=400) + ( + total_records, + uptime_percentage, + average_response_time, + ) = calculate_past_summary(time_range=time_range) + + data = { + "total_records": total_records, + "uptime_percentage": uptime_percentage, + "average_response_time": average_response_time, + } + + return Response(data) + + @action(detail=False, methods=["get"]) + def chart(self, request): + time_range = int(request.query_params.get("time_range", 1)) + split_interval = int(request.query_params.get("split_interval", 6)) + if time_range not in QUERY_TIME_RANGE_TYPE: + return Response({"error": "Invalid time range"}, status=400) + + data = calculate_past_chart( + time_range=time_range, split_interval=split_interval + ) + return Response(data) diff --git a/apps/notification/serializers.py b/apps/notification/serializers.py index 04652a7..ec8cd31 100644 --- a/apps/notification/serializers.py +++ b/apps/notification/serializers.py @@ -1,9 +1,15 @@ +from pydantic_core import ValidationError from rest_framework import serializers from apps.notification.models import NotificationChannel, NotificationType +from apps.notification.notify_services.bark import Bark +from apps.notification.notify_services.telegram import Telegram class NotificationChannelSerializer(serializers.HyperlinkedModelSerializer): + type = serializers.ChoiceField(choices=NotificationType.choices) + url = serializers.URLField(required=False) + class Meta: model = NotificationChannel fields = ( @@ -13,4 +19,34 @@ class Meta: "type", "url", ) # Explicitly include 'id' and other fields you need - type = serializers.ChoiceField(choices=NotificationType.choices) + + def validate(self, attrs): + details = attrs.get("details") + channel_type = attrs.get("type") + match channel_type: + case NotificationType.TELEGRAM: + pydantic_model = Telegram + case NotificationType.BARK: + pydantic_model = Bark + case _: + pydantic_model = None + + if pydantic_model: + try: + # Validates the details using the Pydantic model + pydantic_model(**details) + except ValidationError as e: + raise serializers.ValidationError({"details": e.errors()}) + + return attrs + + def create(self, validated_data): + channel = NotificationChannel.objects.create(**validated_data) + return channel + + def update(self, instance, validated_data): + instance.name = validated_data.get("name", instance.name) + instance.details = validated_data.get("details", instance.details) + instance.type = validated_data.get("type", instance.type) + instance.save() + return instance diff --git a/apps/service/serializers.py b/apps/service/serializers.py index 8a1efff..2498379 100644 --- a/apps/service/serializers.py +++ b/apps/service/serializers.py @@ -49,6 +49,7 @@ class ServiceSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Service fields = ( + "id", "name", "description", "monitoring_endpoint", @@ -67,23 +68,31 @@ class Meta: def create(self, validated_data): periodic_task_data = validated_data.pop("periodic_task_data", None) notification_channels_data = validated_data.pop("notification_channel", []) - + interval_data = periodic_task_data.pop("interval", None) service = Service.objects.create(**validated_data) if notification_channels_data: service.notification_channel.set(notification_channels_data) - if periodic_task_data: - periodic_task_data["kwargs"] = json.dumps( - {"service_id": service.id} - ) # Update this line based on the task argument structure - periodic_task_serializer = PeriodicTaskSerializer(data=periodic_task_data) - if periodic_task_serializer.is_valid(raise_exception=True): - periodic_task = periodic_task_serializer.save() - service.periodic_task = ( - periodic_task # Bind the PeriodicTask to the Service - ) - service.save() + if not periodic_task_data or not interval_data: + raise ValueError( + "Invalid or missing 'periodic_task_data' for Service creation." + ) + # create interval schedule + interval_serializer = IntervalScheduleSerializer(data=interval_data) + if not interval_serializer.is_valid(raise_exception=True): + raise ValueError("Invalid 'interval' data for PeriodicTask creation.") + # overwrite periodic_task_data kwargs with service id + periodic_task_data["kwargs"] = json.dumps({"service_id": service.id}) + # create periodic task + periodic_task_data["interval"] = interval_data + periodic_task_serializer = PeriodicTaskSerializer(data=periodic_task_data) + if periodic_task_serializer.is_valid(raise_exception=True): + periodic_task = periodic_task_serializer.save() + service.periodic_task = ( + periodic_task # Bind the PeriodicTask to the Service + ) + service.save() return service diff --git a/docs/readme-img/design.excalidraw b/docs/readme-img/design.excalidraw new file mode 100644 index 0000000..9b6960e --- /dev/null +++ b/docs/readme-img/design.excalidraw @@ -0,0 +1,3124 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 71, + "versionNonce": 820586357, + "isDeleted": false, + "id": "xE8GUTKYtZVZiybNWbOqT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 385.49006872538797, + "y": 188.91559752159458, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 367.1235880938769, + "height": 191.86733679032722, + "seed": 1744552520, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1709080515715, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 252, + "versionNonce": 188151387, + "isDeleted": false, + "id": "y97u_PVsfWbTcUoO6Qdxk", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 414.72511421121413, + "y": 213.71307695803137, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 399.00390625, + "height": 115, + "seed": 568849224, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515715, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "scope:\n 1 support system services (smb, shairport)\n 2. docker container monitoring\n 3. database monitoring \n ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "scope:\n 1 support system services (smb, shairport)\n 2. docker container monitoring\n 3. database monitoring \n ", + "lineHeight": 1.15 + }, + { + "type": "rectangle", + "version": 201, + "versionNonce": 547254485, + "isDeleted": false, + "id": "tlgRkBtDaA9UyZwJPcsDm", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 386.6089049591895, + "y": -34.93156611254696, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 367.1235880938769, + "height": 191.86733679032722, + "seed": 2105595192, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1709080515715, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 306, + "versionNonce": 324891387, + "isDeleted": false, + "id": "RD1h4qVnzlCPtu0iFIgHv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 403.5105972599416, + "y": -13.943942007314803, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 313.466796875, + "height": 92, + "seed": 35745336, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515715, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "objectives:\n 1. uptime dashboard\n 2. notify me when server is down\n ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "objectives:\n 1. uptime dashboard\n 2. notify me when server is down\n ", + "lineHeight": 1.15 + }, + { + "type": "rectangle", + "version": 101, + "versionNonce": 1906544181, + "isDeleted": false, + "id": "zU6YRngZ9DyOAE1swG11q", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1227.6761874594627, + "y": 64.64580393505446, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 326.1841805427887, + "height": 181.99586653936728, + "seed": 1607489864, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "QePZ0J-Ma5FNfHbbgpC_J", + "type": "arrow" + } + ], + "updated": 1709080515715, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 36, + "versionNonce": 382426011, + "isDeleted": false, + "id": "zSGjr9HueQ1cT6PWFhOY2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1332.2158692459143, + "y": 138.9151451791929, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.73828125, + "height": 23, + "seed": 1057673288, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515715, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "django server ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "django server ", + "lineHeight": 1.15 + }, + { + "type": "ellipse", + "version": 133, + "versionNonce": 316538773, + "isDeleted": false, + "id": "wRf8oZzWtbnF_bbl0bGlb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2052.891090172112, + "y": 7.329348687753509, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 147.83309103692022, + "height": 69.31150992036652, + "seed": 1651230776, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "F3BK-3PhDmMOXyb-6TiiK" + } + ], + "updated": 1709080515715, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 119, + "versionNonce": 296955963, + "isDeleted": false, + "id": "F3BK-3PhDmMOXyb-6TiiK", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2097.026096675085, + "y": 30.479784308451855, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 60.029296875, + "height": 23, + "seed": 825075272, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515715, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "docker", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "wRf8oZzWtbnF_bbl0bGlb", + "originalText": "docker", + "lineHeight": 1.15 + }, + { + "type": "ellipse", + "version": 123, + "versionNonce": 2055423221, + "isDeleted": false, + "id": "7CjWAyPqqtl7m15fVWHh_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2058.608250044529, + "y": 290.55889733254645, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 147.83309103692022, + "height": 79, + "seed": 965729608, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "SSI87o19H7kDrlMv3E0lE" + } + ], + "updated": 1709080515715, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 124, + "versionNonce": 1442279643, + "isDeleted": false, + "id": "SSI87o19H7kDrlMv3E0lE", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2086.068451860002, + "y": 307.1281794756778, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 93.37890625, + "height": 46, + "seed": 1429181000, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515715, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "database \nmonitoring", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "7CjWAyPqqtl7m15fVWHh_", + "originalText": "database monitoring", + "lineHeight": 1.15 + }, + { + "type": "ellipse", + "version": 79, + "versionNonce": 1008262741, + "isDeleted": false, + "id": "HlYNWKHoou4Gc43mkKdz4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2055.6550460891813, + "y": 143.40803379164035, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 147.83309103692022, + "height": 79, + "seed": 1798442296, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "3ZPX61XHzMftm4B1vlrOS" + } + ], + "updated": 1709080515715, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 85, + "versionNonce": 1428856187, + "isDeleted": false, + "id": "3ZPX61XHzMftm4B1vlrOS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2095.356458842154, + "y": 159.97731593477172, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 68.896484375, + "height": 46, + "seed": 1359444552, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "system \nservice", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "HlYNWKHoou4Gc43mkKdz4", + "originalText": "system service", + "lineHeight": 1.15 + }, + { + "type": "rectangle", + "version": 400, + "versionNonce": 32591195, + "isDeleted": false, + "id": "RwTA30T9y3GfnqneCn3A0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1025.007893232662, + "y": 683.1323391333179, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 128.19579087526694, + "height": 118.74707380189585, + "seed": 245347640, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1709080520860, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 587, + "versionNonce": 488328213, + "isDeleted": false, + "id": "qZH1weTiF99EXoGoRDnXc", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1031.9539296685696, + "y": 699.2831607134799, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 66.81805419921875, + "height": 86.44543064157213, + "seed": 662563144, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "vlHrjvQUjMINsD1AhbB1a", + "type": "arrow" + } + ], + "updated": 1709080560718, + "link": null, + "locked": false, + "fontSize": 12.528323281387266, + "fontFamily": 2, + "text": "Monitoring\n heartbeat\n ping\n wait \n\n ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Monitoring\n heartbeat\n ping\n wait \n\n ", + "lineHeight": 1.15 + }, + { + "type": "rectangle", + "version": 410, + "versionNonce": 1176236699, + "isDeleted": false, + "id": "q0902ok0ZSTJmIIlO72TO", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1170.9917428609883, + "y": 678.1211236346775, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 135.11548330446448, + "height": 128.7695047991768, + "seed": 2077610040, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1709080520860, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 537, + "versionNonce": 801527957, + "isDeleted": false, + "id": "m-FWMnpu3pxEEj2OT0Icy", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1316.5482341582392, + "y": 689.0348601923183, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 150.79962529987208, + "height": 106.94203168389538, + "seed": 1976485448, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "PAPciE6Wrc5zAFs4Gg2sX", + "type": "arrow" + } + ], + "updated": 1709080520860, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 313, + "versionNonce": 1310649275, + "isDeleted": false, + "id": "ApSGK2gpvRggI9_SYEFS6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1192.7636008509926, + "y": 706.4869466002775, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 95.33648681640625, + "height": 72.03785886797677, + "seed": 141112120, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080560718, + "link": null, + "locked": false, + "fontSize": 12.528323281387264, + "fontFamily": 2, + "text": "(service type)\n\n http?\n bash output?\n docker status?", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "(service type)\n\n http?\n bash output?\n docker status?", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 519, + "versionNonce": 1482883445, + "isDeleted": false, + "id": "xShtrUClndpsQQMxPIIxA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1332.9778482842785, + "y": 703.6673013167839, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 54.27107238769531, + "height": 74.89053894740204, + "seed": 1875698488, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080560718, + "link": null, + "locked": false, + "fontSize": 10.853701296724934, + "fontFamily": 2, + "text": "notifyType\n\n bark\n telegram\n smtp\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "notifyType\n\n bark\n telegram\n smtp\n", + "lineHeight": 1.15 + }, + { + "type": "rectangle", + "version": 474, + "versionNonce": 761023771, + "isDeleted": false, + "id": "4IexatyT2IX77QFCp3DaY", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1089.1906749141392, + "y": 523.2505624094628, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 143.98755934206912, + "height": 117.19570451785415, + "seed": 1087875912, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "vlHrjvQUjMINsD1AhbB1a", + "type": "arrow" + }, + { + "id": "jkr-CqKESRV6TKhzgGvv7", + "type": "arrow" + }, + { + "id": "PAPciE6Wrc5zAFs4Gg2sX", + "type": "arrow" + } + ], + "updated": 1709080520860, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 342, + "versionNonce": 341818459, + "isDeleted": false, + "id": "IXBjGmHBTTU2-nQkPRobj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1124.6731248408444, + "y": 535.2195227914376, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 68.89657592773438, + "height": 72.03785886797677, + "seed": 1067737144, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080560718, + "link": null, + "locked": false, + "fontSize": 12.528323281387264, + "fontFamily": 2, + "text": "service\nMonitorType\nServiceType\nNotifyType\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "service\nMonitorType\nServiceType\nNotifyType\n", + "lineHeight": 1.15 + }, + { + "type": "arrow", + "version": 1367, + "versionNonce": 2118614965, + "isDeleted": false, + "id": "vlHrjvQUjMINsD1AhbB1a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1119.0821874586102, + "y": 647.6440349828993, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 25.889657224346244, + "height": 51.01270956651125, + "seed": 638868280, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080520861, + "link": null, + "locked": false, + "startBinding": { + "elementId": "4IexatyT2IX77QFCp3DaY", + "focus": 0.08561703296907883, + "gap": 7.197768055582458 + }, + "endBinding": { + "elementId": "qZH1weTiF99EXoGoRDnXc", + "focus": 0.10029844655135538, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -25.889657224346244, + 51.01270956651125 + ] + ] + }, + { + "type": "arrow", + "version": 396, + "versionNonce": 1761138293, + "isDeleted": false, + "id": "jkr-CqKESRV6TKhzgGvv7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1203.6788009645618, + "y": 640.5633514015843, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 21.827473115281425, + "height": 47.46604586796351, + "seed": 1973958968, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080520861, + "link": null, + "locked": false, + "startBinding": { + "elementId": "4IexatyT2IX77QFCp3DaY", + "focus": -0.15659975023348183, + "gap": 1 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 21.827473115281425, + 47.46604586796351 + ] + ] + }, + { + "type": "arrow", + "version": 699, + "versionNonce": 1379405461, + "isDeleted": false, + "id": "PAPciE6Wrc5zAFs4Gg2sX", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1233.8046504202775, + "y": 603.1775087980345, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 73.54223236503262, + "height": 67.15819899586158, + "seed": 870653256, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080520861, + "link": null, + "locked": false, + "startBinding": { + "elementId": "4IexatyT2IX77QFCp3DaY", + "focus": -0.36180134114108814, + "gap": 1 + }, + "endBinding": { + "elementId": "m-FWMnpu3pxEEj2OT0Icy", + "focus": -0.041585442216696335, + "gap": 18.699152398422314 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 73.54223236503262, + 67.15819899586158 + ] + ] + }, + { + "type": "rectangle", + "version": 171, + "versionNonce": 1856177467, + "isDeleted": false, + "id": "j4GdRluPu_JbDLw9z5kpH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1666.8250920853675, + "y": 120.6959328027682, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 188.2994616908927, + "height": 91.61653588518777, + "seed": 1938600520, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "NOBL2YFnIixUFyxIb9MbC" + }, + { + "id": "yrn_7zQOYoU4X4_b4doaG", + "type": "arrow" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 257, + "versionNonce": 82264053, + "isDeleted": false, + "id": "NOBL2YFnIixUFyxIb9MbC", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1734.3000182433138, + "y": 155.00420074536208, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 53.349609375, + "height": 23, + "seed": 630939976, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "celery", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "j4GdRluPu_JbDLw9z5kpH", + "originalText": "celery", + "lineHeight": 1.15 + }, + { + "type": "arrow", + "version": 41, + "versionNonce": 270837211, + "isDeleted": false, + "id": "5NBA64TN_CCxs--uJOz2v", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1843.6481789118075, + "y": 156.58296820952444, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 252.04100465750207, + "height": 110.47879923462722, + "seed": 1596929608, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 252.04100465750207, + -110.47879923462722 + ] + ] + }, + { + "type": "arrow", + "version": 33, + "versionNonce": 1850046805, + "isDeleted": false, + "id": "KEMKkdlLBfTPl548u_F0I", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1828.148530937301, + "y": 167.29300533872822, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 271.9751313560987, + "height": 15.219280510391286, + "seed": 1989572152, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 271.9751313560987, + 15.219280510391286 + ] + ] + }, + { + "type": "arrow", + "version": 25, + "versionNonce": 501822075, + "isDeleted": false, + "id": "WOwa0Q4yglrOLH1jzjm-s", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1832.9521601558095, + "y": 191.75973937385527, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 256.2371710370944, + "height": 139.5015045616276, + "seed": 1000533320, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 256.2371710370944, + 139.5015045616276 + ] + ] + }, + { + "type": "arrow", + "version": 44, + "versionNonce": 1985316533, + "isDeleted": false, + "id": "QePZ0J-Ma5FNfHbbgpC_J", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1553.9771877789658, + "y": 165.9425687199063, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 136.44082641170235, + "height": 3.1401155980911426, + "seed": 1199528264, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": { + "elementId": "zU6YRngZ9DyOAE1swG11q", + "focus": 0.06905081340209505, + "gap": 1 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 136.44082641170235, + 3.1401155980911426 + ] + ] + }, + { + "type": "ellipse", + "version": 76, + "versionNonce": 1756572443, + "isDeleted": false, + "id": "zlObsmnIHOSXHOZlisSGS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1161.4907747639813, + "y": -213.5955402440211, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 213.13534622043812, + "height": 153.4918410209802, + "seed": 1402800200, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "PJkYNNmLZ-BoxjWTqYqIE" + }, + { + "id": "PblfCKhDU_-r97J0o3Wt-", + "type": "arrow" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 30, + "versionNonce": 264038421, + "isDeleted": false, + "id": "PJkYNNmLZ-BoxjWTqYqIE", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1210.952747000193, + "y": -159.6171805549023, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 114.501953125, + "height": 46, + "seed": 763792968, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "relational DB\npostgres?", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "zlObsmnIHOSXHOZlisSGS", + "originalText": "relational DB\npostgres?", + "lineHeight": 1.15 + }, + { + "type": "ellipse", + "version": 64, + "versionNonce": 83169211, + "isDeleted": false, + "id": "t2PCGv-93nrogJRRUAXt5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1460.4045466304901, + "y": -202.76401054703405, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 247.73736408333252, + "height": 154.01052082959347, + "seed": 1989373512, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "9gn1iqSJ4SXNQWB_mDpvy", + "type": "arrow" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 28, + "versionNonce": 1014046069, + "isDeleted": false, + "id": "eNrdfj-6nQyn0D6MPCCb9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1527.2020949559565, + "y": -145.0363496856973, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 131.1328125, + "height": 92, + "seed": 1641011256, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "yrn_7zQOYoU4X4_b4doaG", + "type": "arrow" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "time-series DB\n\nTimescaleDB\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "time-series DB\n\nTimescaleDB\n", + "lineHeight": 1.15 + }, + { + "type": "arrow", + "version": 39, + "versionNonce": 1423007835, + "isDeleted": false, + "id": "9gn1iqSJ4SXNQWB_mDpvy", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1459.0073821009823, + "y": -100.71959919120826, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 93.6567513877103, + "height": 4.831665964920035, + "seed": 685216840, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": { + "elementId": "t2PCGv-93nrogJRRUAXt5", + "focus": -0.40768111992482947, + "gap": 7.229169242585186 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -93.6567513877103, + -4.831665964920035 + ] + ] + }, + { + "type": "arrow", + "version": 57, + "versionNonce": 599238357, + "isDeleted": false, + "id": "PblfCKhDU_-r97J0o3Wt-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1295.5204409842925, + "y": -49.96374260420487, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 35.62068631584657, + "height": 141.44071285509165, + "seed": 535795016, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": { + "elementId": "zlObsmnIHOSXHOZlisSGS", + "focus": -0.05152517182339696, + "gap": 12.522163987595135 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 35.62068631584657, + 141.44071285509165 + ] + ] + }, + { + "type": "arrow", + "version": 56, + "versionNonce": 1377475835, + "isDeleted": false, + "id": "yrn_7zQOYoU4X4_b4doaG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1750.5474896612595, + "y": 109.26161305793289, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 85.42329352485831, + "height": 165.7812815313668, + "seed": 774640696, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": { + "elementId": "j4GdRluPu_JbDLw9z5kpH", + "focus": 0.16193514799239167, + "gap": 11.434319744835307 + }, + "endBinding": { + "elementId": "eNrdfj-6nQyn0D6MPCCb9", + "focus": -0.5651204583562478, + "gap": 6.78928868044477 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -85.42329352485831, + -165.7812815313668 + ] + ] + }, + { + "type": "rectangle", + "version": 71, + "versionNonce": 1747321909, + "isDeleted": false, + "id": "bcLZLdmWOPdsaN358AjY1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1793.7645122664876, + "y": 662.3766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 246.984375, + "height": 134, + "seed": 2066669880, + "groupIds": [ + "mGOszL3nP_lUlfkY1iKPV" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "x4pe2UwA0QEODD8p8vnwu", + "type": "arrow" + }, + { + "id": "8aAgmMC7Orl0jFDe4OY4_", + "type": "arrow" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 71, + "versionNonce": 408110491, + "isDeleted": false, + "id": "HNbtVTLGP-qTK9SG9kCd6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1882.7996685164876, + "y": 432.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 266.8359375, + "height": 134, + "seed": 908982840, + "groupIds": [ + "ET1G1MULyPttP6pQx3o3q" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "8aAgmMC7Orl0jFDe4OY4_", + "type": "arrow" + }, + { + "id": "IDIqfXUIyEZ7sjKkjyKIY", + "type": "arrow" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 70, + "versionNonce": 1034007957, + "isDeleted": false, + "id": "Ru8p481VrcbYyytA1Z3gL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2189.6082622664876, + "y": 645.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 256.7421875, + "height": 167, + "seed": 1407316792, + "groupIds": [ + "DwaB01ms7bGVlBTYWg93L" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "dlLm_vk8K-vpRXv5VXBZH", + "type": "arrow" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 72, + "versionNonce": 783145531, + "isDeleted": false, + "id": "pQ9ukuOpVLrKpOkt6aDb3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1955.7918560164876, + "y": 891.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 318.7734375, + "height": 167, + "seed": 1789840440, + "groupIds": [ + "w7n8yTaV6SN9o56sCL_0K" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "x4pe2UwA0QEODD8p8vnwu", + "type": "arrow" + }, + { + "id": "IDIqfXUIyEZ7sjKkjyKIY", + "type": "arrow" + }, + { + "id": "dlLm_vk8K-vpRXv5VXBZH", + "type": "arrow" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 69, + "versionNonce": 1063271157, + "isDeleted": false, + "id": "Q1RRxT5UpuEOftiMaqGfs", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1793.7645122664876, + "y": 703.3766717603934, + "strokeColor": "#000", + "backgroundColor": "transparent", + "width": 246.984375, + "height": 0, + "seed": 611878200, + "groupIds": [ + "mGOszL3nP_lUlfkY1iKPV" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 246.984375, + 0 + ] + ] + }, + { + "type": "line", + "version": 69, + "versionNonce": 302832347, + "isDeleted": false, + "id": "fKTQS0lVWFBnCjSQH8BnI", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1793.7645122664876, + "y": 785.3766717603934, + "strokeColor": "#000", + "backgroundColor": "transparent", + "width": 246.984375, + "height": 0, + "seed": 1319312952, + "groupIds": [ + "mGOszL3nP_lUlfkY1iKPV" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 246.984375, + 0 + ] + ] + }, + { + "type": "line", + "version": 69, + "versionNonce": 99030101, + "isDeleted": false, + "id": "E1UxZ5riV6ANW24Ppgico", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1882.7996685164876, + "y": 473.8766717603934, + "strokeColor": "#000", + "backgroundColor": "transparent", + "width": 266.8359375, + "height": 0, + "seed": 1779603256, + "groupIds": [ + "ET1G1MULyPttP6pQx3o3q" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 266.8359375, + 0 + ] + ] + }, + { + "type": "line", + "version": 69, + "versionNonce": 2130888571, + "isDeleted": false, + "id": "WGUNQKcs_lAD_wreGPfOF", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1882.7996685164876, + "y": 555.8766717603934, + "strokeColor": "#000", + "backgroundColor": "transparent", + "width": 266.8359375, + "height": 0, + "seed": 992669752, + "groupIds": [ + "ET1G1MULyPttP6pQx3o3q" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 266.8359375, + 0 + ] + ] + }, + { + "type": "line", + "version": 69, + "versionNonce": 2097922485, + "isDeleted": false, + "id": "7Up1Gyw6BgFHvDFjMfqyH", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2189.6082622664876, + "y": 686.8766717603934, + "strokeColor": "#000", + "backgroundColor": "transparent", + "width": 256.7421875, + "height": 0, + "seed": 1506814264, + "groupIds": [ + "DwaB01ms7bGVlBTYWg93L" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 256.7421875, + 0 + ] + ] + }, + { + "type": "line", + "version": 69, + "versionNonce": 2077368347, + "isDeleted": false, + "id": "rmuRz5_JnhmaMTeORWtFE", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2189.6082622664876, + "y": 801.8766717603934, + "strokeColor": "#000", + "backgroundColor": "transparent", + "width": 256.7421875, + "height": 0, + "seed": 1097220664, + "groupIds": [ + "DwaB01ms7bGVlBTYWg93L" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 256.7421875, + 0 + ] + ] + }, + { + "type": "line", + "version": 69, + "versionNonce": 2046018325, + "isDeleted": false, + "id": "-DroJUCC_eF9CRxk2fEoz", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1955.7918560164876, + "y": 932.8766717603934, + "strokeColor": "#000", + "backgroundColor": "transparent", + "width": 318.7734375, + "height": 0, + "seed": 1624324920, + "groupIds": [ + "w7n8yTaV6SN9o56sCL_0K" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 318.7734375, + 0 + ] + ] + }, + { + "type": "line", + "version": 69, + "versionNonce": 577594555, + "isDeleted": false, + "id": "gEr3nKRauTUuetidRnu8M", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1955.7918560164876, + "y": 1047.8766717603935, + "strokeColor": "#000", + "backgroundColor": "transparent", + "width": 318.7734375, + "height": 0, + "seed": 712315960, + "groupIds": [ + "w7n8yTaV6SN9o56sCL_0K" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 318.7734375, + 0 + ] + ] + }, + { + "type": "arrow", + "version": 203, + "versionNonce": 286562421, + "isDeleted": false, + "id": "x4pe2UwA0QEODD8p8vnwu", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1917.7565122664876, + "y": 796.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 63.56, + "height": 95.5, + "seed": 607505720, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "yfgQaR7s1x4aqzKujH6gc" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": { + "elementId": "bcLZLdmWOPdsaN358AjY1", + "focus": 0.26325122899789294, + "gap": 1 + }, + "endBinding": { + "elementId": "pQ9ukuOpVLrKpOkt6aDb3", + "focus": -0.36919535286341354, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 62.56, + 94.5 + ] + ] + }, + { + "type": "arrow", + "version": 203, + "versionNonce": 712447323, + "isDeleted": false, + "id": "8aAgmMC7Orl0jFDe4OY4_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1953.4605122664875, + "y": 567.3766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 36.70400000000001, + "height": 95.5, + "seed": 2004313656, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "DtexY_-BQ89v0vLXczfsz" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": { + "elementId": "HNbtVTLGP-qTK9SG9kCd6", + "focus": 0.2346992894277625, + "gap": 1 + }, + "endBinding": { + "elementId": "yXuIxxy0bg114YjFRkxMa", + "focus": 0.18168600694628861, + "gap": 5.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -35.70400000000001, + 94.5 + ] + ] + }, + { + "type": "arrow", + "version": 205, + "versionNonce": 388037653, + "isDeleted": false, + "id": "IDIqfXUIyEZ7sjKkjyKIY", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2076.945078062386, + "y": 587.0515179320727, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 36.6445961100635, + "height": 304.3251538283207, + "seed": 18734904, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "LiQzoTgECI9lEIlw2k9oT" + } + ], + "updated": 1709081380851, + "link": null, + "locked": false, + "startBinding": { + "elementId": "mYkc7Ztlo7hr49J3VCJgD", + "focus": -3.435938115814488, + "gap": 12.244003295898438 + }, + "endBinding": { + "elementId": "IfD94GCnQ87BfOzT8iWjr", + "focus": 0.31108021685574433, + "gap": 5.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 36.6445961100635, + 304.3251538283207 + ] + ] + }, + { + "type": "arrow", + "version": 203, + "versionNonce": 859943419, + "isDeleted": false, + "id": "dlLm_vk8K-vpRXv5VXBZH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2317.4795122664877, + "y": 813.3766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 65.12700000000001, + "height": 79, + "seed": 1636644920, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "DtWDY-f9c-KDrnA9Gx1HI" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Ru8p481VrcbYyytA1Z3gL", + "focus": -0.3479852190327824, + "gap": 1 + }, + "endBinding": { + "elementId": "pQ9ukuOpVLrKpOkt6aDb3", + "focus": 0.30308479621001505, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -64.12700000000001, + 78 + ] + ] + }, + { + "type": "text", + "version": 72, + "versionNonce": 1617346357, + "isDeleted": false, + "id": "yXuIxxy0bg114YjFRkxMa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1801.2645122664876, + "y": 667.3766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 173.4375, + "height": 23, + "seed": 212431160, + "groupIds": [ + "mGOszL3nP_lUlfkY1iKPV" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "8aAgmMC7Orl0jFDe4OY4_", + "type": "arrow" + } + ], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "NotificationChannel", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "NotificationChannel", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 1305459355, + "isDeleted": false, + "id": "2RIfHEsIDGNnR0sdwNoCU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1801.2645122664876, + "y": 712.3766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 116.708984375, + "height": 23, + "seed": 1507232312, + "groupIds": [ + "mGOszL3nP_lUlfkY1iKPV" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "-name: string", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "-name: string", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 68358293, + "isDeleted": false, + "id": "p4o2DNqWBF5nwEJuFROmf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1801.2645122664876, + "y": 745.3766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 112.275390625, + "height": 23, + "seed": 335053624, + "groupIds": [ + "mGOszL3nP_lUlfkY1iKPV" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "-details: json", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "-details: json", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 1188278075, + "isDeleted": false, + "id": "lZzkwwjE83BNHtXdt6bfB", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1927.3113872664876, + "y": 437.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 132.3046875, + "height": 23, + "seed": 158555192, + "groupIds": [ + "ET1G1MULyPttP6pQx3o3q" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "NotificationLog", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "NotificationLog", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 82720245, + "isDeleted": false, + "id": "IhmMHOYHSYPXj1nengmC6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1890.2996685164876, + "y": 482.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 131.162109375, + "height": 23, + "seed": 472585528, + "groupIds": [ + "ET1G1MULyPttP6pQx3o3q" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "-message: text", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "-message: text", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 816844763, + "isDeleted": false, + "id": "q7XB1pD9_oVCdWnSYpbZ6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1890.2996685164876, + "y": 515.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 207.890625, + "height": 23, + "seed": 1367584312, + "groupIds": [ + "ET1G1MULyPttP6pQx3o3q" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "-was_success: boolean", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "-was_success: boolean", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 2113777493, + "isDeleted": false, + "id": "-Yu4myR_n2pR_-jmY-z1K", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2234.7254497664876, + "y": 650.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 127.822265625, + "height": 23, + "seed": 1650534200, + "groupIds": [ + "DwaB01ms7bGVlBTYWg93L" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "UptimeRecord", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "UptimeRecord", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 762797179, + "isDeleted": false, + "id": "VbvaCFmjCHidjxgN_AlJm", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2197.1082622664876, + "y": 695.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 142.314453125, + "height": 23, + "seed": 1260879928, + "groupIds": [ + "DwaB01ms7bGVlBTYWg93L" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "-status: boolean", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "-status: boolean", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 227998901, + "isDeleted": false, + "id": "vOhqitR5uAROJ_myTWPK-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2197.1082622664876, + "y": 728.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 186.7578125, + "height": 23, + "seed": 1538963768, + "groupIds": [ + "DwaB01ms7bGVlBTYWg93L" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "-response_time: float", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "-response_time: float", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 2028737819, + "isDeleted": false, + "id": "q1kw6VKdqSVjqweumGt68", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2197.1082622664876, + "y": 761.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 184.51171875, + "height": 23, + "seed": 1199493688, + "groupIds": [ + "DwaB01ms7bGVlBTYWg93L" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "-error_message: text", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "-error_message: text", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 73, + "versionNonce": 568233301, + "isDeleted": false, + "id": "IfD94GCnQ87BfOzT8iWjr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2071.488310873555, + "y": 896.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 66.689453125, + "height": 23, + "seed": 894946104, + "groupIds": [ + "w7n8yTaV6SN9o56sCL_0K" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "IDIqfXUIyEZ7sjKkjyKIY", + "type": "arrow" + } + ], + "updated": 1709081380851, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "Service", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "Service", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 847296955, + "isDeleted": false, + "id": "x-NEHuMpPrGUOPQeW4E8Q", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1963.2918560164876, + "y": 941.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 116.708984375, + "height": 23, + "seed": 1336517688, + "groupIds": [ + "w7n8yTaV6SN9o56sCL_0K" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "-name: string", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "-name: string", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 1045238645, + "isDeleted": false, + "id": "Z0vzY8tHyu8nYpoT1klYh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1963.2918560164876, + "y": 974.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 146.728515625, + "height": 23, + "seed": 1415077176, + "groupIds": [ + "w7n8yTaV6SN9o56sCL_0K" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "-description: text", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "-description: text", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 83, + "versionNonce": 208400597, + "isDeleted": false, + "id": "oAbnHkXMjhNH34ghp80YZ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1963.2918560164876, + "y": 1007.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 326.8359375, + "height": 23, + "seed": 617084472, + "groupIds": [ + "w7n8yTaV6SN9o56sCL_0K" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709081407749, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 2, + "text": "-moniotoring_endpoint: url (https/tcp)", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "-moniotoring_endpoint: url (https/tcp)", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 849665237, + "isDeleted": false, + "id": "z703S6uPlBYE7rR7UMYSK", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1897.2565122664876, + "y": 801.3766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 8.8984375, + "height": 18.4, + "seed": 1440509752, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "1", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "1", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 1547945723, + "isDeleted": false, + "id": "3q4U4QmJ_J7LhOY2rFyXP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2000.8165122664875, + "y": 866.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 6.2265625, + "height": 18.4, + "seed": 1073334328, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "*", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "*", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 1341946421, + "isDeleted": false, + "id": "sdVrjddegFaIpF9jnvXrr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1918.9605122664875, + "y": 571.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 6.2265625, + "height": 18.4, + "seed": 251920696, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "*", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "*", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 398929819, + "isDeleted": false, + "id": "2r4NPyQOx-Hj88Ql4KST-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1952.2565122664876, + "y": 637.3766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 8.8984375, + "height": 18.4, + "seed": 1381964344, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515716, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "1", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "1", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 72, + "versionNonce": 255421333, + "isDeleted": false, + "id": "mYkc7Ztlo7hr49J3VCJgD", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2058.4745122664876, + "y": 571.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 6.2265625, + "height": 18.4, + "seed": 216994616, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "IDIqfXUIyEZ7sjKkjyKIY", + "type": "arrow" + } + ], + "updated": 1709080515717, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "*", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "*", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 739542075, + "isDeleted": false, + "id": "h-vNXyx2MCOWCxoYOUyNr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2135.178512266488, + "y": 866.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 8.8984375, + "height": 18.4, + "seed": 1159195704, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515717, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "1", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "1", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 814821621, + "isDeleted": false, + "id": "d3yvayfoe0iqUkpr8UmYL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2282.9795122664877, + "y": 817.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 6.2265625, + "height": 18.4, + "seed": 347972920, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515717, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "*", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "*", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 71, + "versionNonce": 1091454171, + "isDeleted": false, + "id": "DzwhHPH-z7yjzT5KfVSGB", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2287.852512266488, + "y": 866.8766717603934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 8.8984375, + "height": 18.4, + "seed": 960773688, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515717, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "1", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": "1", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 7, + "versionNonce": 1367951957, + "isDeleted": false, + "id": "yfgQaR7s1x4aqzKujH6gc", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1878.3177622664875, + "y": 834.9266717603933, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 141.4375, + "height": 18.4, + "seed": 596621112, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515717, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "notification_channel", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "x4pe2UwA0QEODD8p8vnwu", + "originalText": "notification_channel", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 7, + "versionNonce": 1581707643, + "isDeleted": false, + "id": "DtexY_-BQ89v0vLXczfsz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1907.5850747664877, + "y": 605.4266717603933, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 56.046875, + "height": 18.4, + "seed": 134484024, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515717, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "channel", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "8aAgmMC7Orl0jFDe4OY4_", + "originalText": "channel", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 7, + "versionNonce": 1123497909, + "isDeleted": false, + "id": "LiQzoTgECI9lEIlw2k9oT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2071.486668516488, + "y": 720.1766717603933, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 50.6796875, + "height": 18.4, + "seed": 1854228792, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515717, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "service", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "IDIqfXUIyEZ7sjKkjyKIY", + "originalText": "service", + "lineHeight": 1.15 + }, + { + "type": "text", + "version": 8, + "versionNonce": 1963002395, + "isDeleted": false, + "id": "DtWDY-f9c-KDrnA9Gx1HI", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2260.076168516488, + "y": 843.1766717603933, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 50.6796875, + "height": 18.4, + "seed": 1115120184, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709080515717, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 2, + "text": "service", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "dlLm_vk8K-vpRXv5VXBZH", + "originalText": "service", + "lineHeight": 1.15 + }, + { + "type": "arrow", + "version": 84, + "versionNonce": 187489621, + "isDeleted": false, + "id": "VC7_-iWNVREVYZFVBbw8k", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1478.5736328919256, + "y": 660.6967350861164, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 266.62270683909196, + "height": 6.638863484861986, + "seed": 160144027, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1709080536997, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 266.62270683909196, + 6.638863484861986 + ] + ] + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/ping_server b/ping_server new file mode 100755 index 0000000..925d565 Binary files /dev/null and b/ping_server differ diff --git a/test/ping_server.go b/test/ping_server.go new file mode 100644 index 0000000..5047e18 --- /dev/null +++ b/test/ping_server.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "net/http" + "time" +) +var pong = make(chan int) + +func ping(w http.ResponseWriter, req *http.Request,){ + fmt.Fprintf(w,"pong") + pong <- 1 +} + +func main() { + fmt.Println("you can ping at http://localhost:8090/ping") + http.HandleFunc("/ping",ping) + go func(){ + for range pong { + fmt.Println("pong",time.Now()) + } + }() + http.ListenAndServe(":8090", nil) +} diff --git a/test/test_monitering.py b/test/test_monitering.py index 10c3224..f1a7abd 100644 --- a/test/test_monitering.py +++ b/test/test_monitering.py @@ -1,8 +1,17 @@ import pytest +from dj_rest_auth.tests.mixins import APIClient +from rest_framework import status from rest_framework.exceptions import ErrorDetail +from apps.monitoring.models import UptimeRecord from apps.monitoring.serializers import PeriodicTaskSerializer from apps.notification.models import NotificationChannel, NotificationType +from apps.service.models import Service + + +@pytest.fixture +def api_client(): + return APIClient() # Setup a fixture for the notification channel that can be used across multiple tests @@ -15,6 +24,25 @@ def notification_channel(db): ) +@pytest.fixture +def test_service(db): + return Service.objects.create( + name="Test Service", + description="A test service.", + monitoring_endpoint="http://127.0.0.1", + is_active=True, + ) + + +@pytest.fixture +def test_uptime_record(db, test_service): + return UptimeRecord.objects.create( + service=test_service, + status=True, + response_time=100.0, + ) + + @pytest.mark.django_db def test_create_valid_periodic_task_data(notification_channel): """Test service creation with valid periodic_task data.""" @@ -86,3 +114,66 @@ def test_create_invalid_periodic_task_data_missing_interval(notification_channel assert serializer.errors == { "interval": [ErrorDetail(string="This field is required.", code="required")] } + + +def test_create_valid_uptime_record(test_uptime_record): + """Test uptime record creation with valid data.""" + assert test_uptime_record.status + assert test_uptime_record.response_time == 100.0 + assert test_uptime_record.error_message is None + + +def test_uptime_record_list(api_client, test_uptime_record): + """ + Test fetching the list of uptime records. + """ + response = api_client.get("/uptime/") + assert response.status_code == status.HTTP_200_OK + assert len(response.data) > 0 + + +def test_filter_uptime_records_by_service(api_client, test_service, test_uptime_record): + """ + Test filtering uptime records by service. + """ + response = api_client.get(f"/uptime/?service_id={test_service.id}") + assert response.status_code == status.HTTP_200_OK + assert len(response.data) == 1 + assert response.data[0]["service"] == test_service.id + + +def test_uptime_record_creation(api_client, test_service): + """ + Test creating a new uptime record. + """ + response = api_client.post( + "/uptime/", + { + "service": test_service.id, + "status": True, + "response_time": 50.0, + }, + ) + + response = api_client.post( + "/uptime/", + { + "service": test_service.id, + "status": True, + "response_time": 150.0, + }, + ) + assert response.status_code == status.HTTP_201_CREATED + assert UptimeRecord.objects.count() == 2 + assert UptimeRecord.objects.first().service == test_service + response = api_client.get(f"/uptime/?service_id={test_service.id}") + assert response.status_code == status.HTTP_200_OK + assert len(response.data) == 2 + assert response.data[0]["service"] == test_service.id + # / uptime / stats /?service_id = 17 + + response = api_client.get(f"/uptime/stats/?service_id={test_service.id}") + assert response.status_code == status.HTTP_200_OK + assert response.data["total_records"] == 2 + assert response.data["uptime_percentage"] == 100.0 + assert response.data["average_response_time"] == 100.0 diff --git a/test/test_notification.py b/test/test_notification.py index e69de29..fcd31ca 100644 --- a/test/test_notification.py +++ b/test/test_notification.py @@ -0,0 +1,64 @@ +import pytest +from rest_framework.exceptions import ValidationError +from apps.notification.serializers import NotificationChannelSerializer + + +@pytest.fixture +def valid_telegram_data(): + return { + "name": "Test Channel", + "details": {"token": "test_token", "chat_id": "test_chat_id"}, + "type": "telegram", + "url": "http://example.com", + } + + +@pytest.fixture +def valid_bark_data(): + return { + "name": "Test Channel bark", + "details": { + "bark-endpoint": "http://example.com", + }, + "type": "bark", + "url": "http://example.com", + } + + +@pytest.mark.django_db +def test_notification_channel_serializer_with_valid_data( + valid_bark_data, +): + serializer = NotificationChannelSerializer(data=valid_bark_data) + assert serializer.is_valid(), serializer.errors + + +@pytest.mark.django_db +def test_notification_channel_serializer_with_invalid_data( + valid_bark_data, +): + # Change the data to make it invalid + valid_bark_data["type"] = "INVALID_TYPE" + serializer = NotificationChannelSerializer(data=valid_bark_data) + with pytest.raises(ValidationError): + assert serializer.is_valid(raise_exception=True) + + +@pytest.mark.django_db +def test_notification_channel_serializer_with_invalid_detail(): + # Change the data to make it invalid + valid_bark_data = { + "name": "Test Channel bark", + "details": { + "NOT-REAL-bark-endpoint": "http://example.com", + }, + "type": "bark", + "url": "http://example.com", + } + serializer = NotificationChannelSerializer(data=valid_bark_data) + with pytest.raises(ValidationError): + assert serializer.is_valid(raise_exception=True) + + serializer = NotificationChannelSerializer(data=valid_bark_data) + assert not serializer.is_valid() + assert "(string='bark-endpoint'" in str(serializer.errors) diff --git a/test/test_service.py b/test/test_service.py index 18d6ae7..36e28af 100644 --- a/test/test_service.py +++ b/test/test_service.py @@ -71,6 +71,10 @@ def test_create_service_with_periodic_task_data(periodic_task_data): service = serializer.save() assert service.periodic_task is not None assert service.periodic_task.name == "Test Task" + assert ( + service.periodic_task.interval.every == 10 + ), service.periodic_task.interval.every + assert service.periodic_task.interval.period == "seconds" @pytest.mark.django_db diff --git a/test/test_tasks.py b/test/test_tasks.py new file mode 100644 index 0000000..de5552b --- /dev/null +++ b/test/test_tasks.py @@ -0,0 +1,51 @@ +import pytest + +from apps.notification.models import NotificationChannel, NotificationType +from apps.service.serializers import ServiceSerializer + + +# Setup a fixture for the notification channel that can be used across multiple tests +@pytest.fixture +def notification_channel(db): + return NotificationChannel.objects.create( + name="Test Channel", + type=NotificationType.BARK, + details={"url": "https://bark.example.com"}, + ) + + +# Setup a fixture for the periodic task data +@pytest.fixture +def periodic_task_data(): + return { + "name": "Test Task", + "task": "apps.monitoring.tasks.check_monitor_services_status", + # "kwargs": {}, + "interval": { + "every": 10, + "period": "seconds", + }, + "enabled": True, + } + + +@pytest.mark.django_db +def test_create_service_with_notification_id(notification_channel, periodic_task_data): + """Test service creation with a_notification_id.""" + data = { + "name": "brruno", + "description": "test", + "monitoring_endpoint": "http://localhost:8000/service/", + "is_active": True, + "notification_channel": [notification_channel.id], + "monitoring_type": "http", + "periodic_task_data": periodic_task_data, + } + + assert isinstance( + notification_channel.id, int + ), "Notification channel id is not an integer" + serializer = ServiceSerializer(data=data) + assert serializer.is_valid(), serializer.errors + service = serializer.save() + assert service.notification_channel.first().id == notification_channel.id diff --git a/uptimemonitor/settings.py b/uptimemonitor/settings.py index eb2793e..24f3117 100644 --- a/uptimemonitor/settings.py +++ b/uptimemonitor/settings.py @@ -159,7 +159,7 @@ REST_SESSION_LOGIN = False -CELERY_TIMEZONE = "UTC" +CELERY_TIMEZONE = "Europe/London" CELERY_TASK_TRACK_STARTED = True CELERY_TASK_TIME_LIMIT = 30 * 60