From cb6fbb333a59cc1f332d1f33d8082753216cf733 Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 22 Feb 2023 20:11:56 +0100 Subject: [PATCH] Documentation, Tactical RMM API Uage, Contract Management --- msp/msp/doctype/it_contract/it_contract.json | 115 ++++++++++++++++- .../it_contract_delivery_note/__init__.py | 0 .../it_contract_delivery_note.json | 58 +++++++++ .../it_contract_delivery_note.py | 8 ++ msp/msp/doctype/it_contract_item/__init__.py | 0 .../it_contract_item/it_contract_item.js | 8 ++ .../it_contract_item/it_contract_item.json | 67 ++++++++++ .../it_contract_item/it_contract_item.py | 8 ++ .../it_contract_item/test_it_contract_item.py | 8 ++ .../doctype/it_landscape/it_landscape.json | 32 +++-- msp/msp/doctype/msp_documentation/__init__.py | 0 .../msp_documentation/msp_documentation.js | 19 +++ .../msp_documentation/msp_documentation.json | 87 +++++++++++++ .../msp_documentation/msp_documentation.py | 8 ++ .../test_msp_documentation.py | 8 ++ .../doctype/msp_settings/msp_settings.json | 22 +++- msp/msp/workspace/msp/msp.json | 11 +- msp/tactical-rmm.py | 116 ++++++++++++++++++ 18 files changed, 557 insertions(+), 18 deletions(-) create mode 100644 msp/msp/doctype/it_contract_delivery_note/__init__.py create mode 100644 msp/msp/doctype/it_contract_delivery_note/it_contract_delivery_note.json create mode 100644 msp/msp/doctype/it_contract_delivery_note/it_contract_delivery_note.py create mode 100644 msp/msp/doctype/it_contract_item/__init__.py create mode 100644 msp/msp/doctype/it_contract_item/it_contract_item.js create mode 100644 msp/msp/doctype/it_contract_item/it_contract_item.json create mode 100644 msp/msp/doctype/it_contract_item/it_contract_item.py create mode 100644 msp/msp/doctype/it_contract_item/test_it_contract_item.py create mode 100644 msp/msp/doctype/msp_documentation/__init__.py create mode 100644 msp/msp/doctype/msp_documentation/msp_documentation.js create mode 100644 msp/msp/doctype/msp_documentation/msp_documentation.json create mode 100644 msp/msp/doctype/msp_documentation/msp_documentation.py create mode 100644 msp/msp/doctype/msp_documentation/test_msp_documentation.py create mode 100644 msp/tactical-rmm.py diff --git a/msp/msp/doctype/it_contract/it_contract.json b/msp/msp/doctype/it_contract/it_contract.json index 18e2d75..5250920 100644 --- a/msp/msp/doctype/it_contract/it_contract.json +++ b/msp/msp/doctype/it_contract/it_contract.json @@ -7,42 +7,66 @@ "engine": "InnoDB", "field_order": [ "title", - "customer", - "it_landscape", "it_contract_type", + "status", + "column_break_3", + "it_landscape", + "customer", + "customer_name", "sla_details_section", "html_10", "reaction_time_emergeny", "reaction_time_urgent", "reaction_time_issue", "reaction_time_change", - "column_break_11" + "introduction_text_section", + "introduction_text", + "contained_items_section", + "items", + "billing_information_section", + "contract_start", + "contract_end", + "column_break_19", + "accounting_period", + "billing_active", + "history_section", + "delivery_note_list" ], "fields": [ { "fieldname": "title", "fieldtype": "Data", + "in_list_view": 1, "label": "Title" }, { + "fetch_from": "it_landscape.customer", "fieldname": "customer", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Customer", - "options": "Customer" + "options": "Customer", + "read_only": 1 }, { "fieldname": "it_landscape", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "IT Landscape", "options": "IT Landscape" }, { "fieldname": "it_contract_type", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "IT Contract Type", "options": "IT Contract Type" }, { + "depends_on": "eval:doc.it_contract_type == \"SLA\"", "fieldname": "sla_details_section", "fieldtype": "Section Break", "label": "SLA Details" @@ -73,12 +97,91 @@ "options": "Reaktion Times May be written Hours like \"4H\" or in Business Days Like \"1BD\"" }, { - "fieldname": "column_break_11", + "fieldname": "contained_items_section", + "fieldtype": "Section Break", + "label": "Contained Items" + }, + { + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "options": "IT Contract Item" + }, + { + "fieldname": "billing_information_section", + "fieldtype": "Section Break", + "label": "Billing Information" + }, + { + "fieldname": "contract_start", + "fieldtype": "Date", + "label": "Contract Start" + }, + { + "fieldname": "contract_end", + "fieldtype": "Date", + "label": "Contract End" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "\nplaned\nactive\npaused\nexpired" + }, + { + "fieldname": "accounting_period", + "fieldtype": "Select", + "label": "Accounting Period", + "options": "first of month\nlast of month\nday of month\nweekly\nquarterly\nyearly" + }, + { + "fieldname": "history_section", + "fieldtype": "Section Break", + "label": "History" + }, + { + "fieldname": "delivery_note_list", + "fieldtype": "Table", + "label": "Delivery Note List", + "options": "IT Contract Delivery Note" + }, + { + "fieldname": "column_break_3", "fieldtype": "Column Break" + }, + { + "fetch_from": "customer.customer_name", + "fieldname": "customer_name", + "fieldtype": "Data", + "label": "Customer Name", + "read_only": 1 + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "billing_active", + "fieldtype": "Check", + "label": "Billing Active" + }, + { + "fieldname": "introduction_text_section", + "fieldtype": "Section Break", + "label": "Introduction Text" + }, + { + "description": "Text that should appear on delivery notes and invoices. Placeholders [month] [year] [isoweek] get replaced, if used inside the text.", + "fieldname": "introduction_text", + "fieldtype": "Text Editor", + "label": "Introduction Text" } ], "links": [], - "modified": "2022-06-17 11:19:34.968766", + "modified": "2023-02-22 20:09:42.111402", "modified_by": "Administrator", "module": "MSP", "name": "IT Contract", diff --git a/msp/msp/doctype/it_contract_delivery_note/__init__.py b/msp/msp/doctype/it_contract_delivery_note/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/it_contract_delivery_note/it_contract_delivery_note.json b/msp/msp/doctype/it_contract_delivery_note/it_contract_delivery_note.json new file mode 100644 index 0000000..e650fbe --- /dev/null +++ b/msp/msp/doctype/it_contract_delivery_note/it_contract_delivery_note.json @@ -0,0 +1,58 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-02-22 19:25:18.711799", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "delivery_note", + "delivery_date", + "total", + "grand_total" + ], + "fields": [ + { + "fieldname": "delivery_note", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Delivery Note", + "options": "Delivery Note" + }, + { + "fetch_from": "delivery_note.posting_date", + "fieldname": "delivery_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Delivery Date", + "read_only": 1 + }, + { + "fetch_from": "delivery_note.total", + "fieldname": "total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "total", + "read_only": 1 + }, + { + "fetch_from": "delivery_note.grand_total", + "fieldname": "grand_total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "grand total", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-02-22 19:34:55.524995", + "modified_by": "Administrator", + "module": "MSP", + "name": "IT Contract Delivery Note", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/msp/msp/doctype/it_contract_delivery_note/it_contract_delivery_note.py b/msp/msp/doctype/it_contract_delivery_note/it_contract_delivery_note.py new file mode 100644 index 0000000..b863190 --- /dev/null +++ b/msp/msp/doctype/it_contract_delivery_note/it_contract_delivery_note.py @@ -0,0 +1,8 @@ +# Copyright (c) 2023, itsdave GmbH and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class ITContractDeliveryNote(Document): + pass diff --git a/msp/msp/doctype/it_contract_item/__init__.py b/msp/msp/doctype/it_contract_item/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/it_contract_item/it_contract_item.js b/msp/msp/doctype/it_contract_item/it_contract_item.js new file mode 100644 index 0000000..ceac9eb --- /dev/null +++ b/msp/msp/doctype/it_contract_item/it_contract_item.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, itsdave GmbH and contributors +// For license information, please see license.txt + +frappe.ui.form.on('IT Contract Item', { + // refresh: function(frm) { + + // } +}); diff --git a/msp/msp/doctype/it_contract_item/it_contract_item.json b/msp/msp/doctype/it_contract_item/it_contract_item.json new file mode 100644 index 0000000..324a2b8 --- /dev/null +++ b/msp/msp/doctype/it_contract_item/it_contract_item.json @@ -0,0 +1,67 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-02-22 19:06:38.038508", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "description", + "qty", + "rate", + "amount" + ], + "fields": [ + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item", + "options": "Item" + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Name" + }, + { + "fetch_from": "item_code.description", + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description" + }, + { + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity" + }, + { + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-02-22 19:51:22.759330", + "modified_by": "Administrator", + "module": "MSP", + "name": "IT Contract Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/msp/msp/doctype/it_contract_item/it_contract_item.py b/msp/msp/doctype/it_contract_item/it_contract_item.py new file mode 100644 index 0000000..4660b80 --- /dev/null +++ b/msp/msp/doctype/it_contract_item/it_contract_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2023, itsdave GmbH and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class ITContractItem(Document): + pass diff --git a/msp/msp/doctype/it_contract_item/test_it_contract_item.py b/msp/msp/doctype/it_contract_item/test_it_contract_item.py new file mode 100644 index 0000000..8fe0d00 --- /dev/null +++ b/msp/msp/doctype/it_contract_item/test_it_contract_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2023, itsdave GmbH and Contributors +# See license.txt + +# import frappe +import unittest + +class TestITContractItem(unittest.TestCase): + pass diff --git a/msp/msp/doctype/it_landscape/it_landscape.json b/msp/msp/doctype/it_landscape/it_landscape.json index a690bd8..77526a1 100644 --- a/msp/msp/doctype/it_landscape/it_landscape.json +++ b/msp/msp/doctype/it_landscape/it_landscape.json @@ -13,13 +13,15 @@ "abbreviation", "column_break_5", "operating_mode", - "contract", + "it_contract", + "it_contract_title", "description_section", "description", "section_break_6", "landscape_image", "monitoring_link", - "ticket_system_link" + "ticket_system_link", + "tactical_rmm_tenant_caption" ], "fields": [ { @@ -83,12 +85,6 @@ "fieldtype": "Section Break", "label": "Description" }, - { - "fieldname": "contract", - "fieldtype": "Link", - "label": "Contract", - "options": "IT Contract" - }, { "fieldname": "monitoring_link", "fieldtype": "Data", @@ -98,11 +94,29 @@ "fieldname": "ticket_system_link", "fieldtype": "Data", "label": "Ticket System Link" + }, + { + "fieldname": "tactical_rmm_tenant_caption", + "fieldtype": "Data", + "label": "Tactical RMM Tenant Caption" + }, + { + "fetch_from": "it_contract.title", + "fieldname": "it_contract_title", + "fieldtype": "Data", + "label": "IT Contract Title", + "read_only": 1 + }, + { + "fieldname": "it_contract", + "fieldtype": "Link", + "label": "IT Contract", + "options": "IT Contract" } ], "image_field": "landscape_image", "links": [], - "modified": "2022-06-17 12:03:50.957612", + "modified": "2023-02-22 20:02:46.515700", "modified_by": "Administrator", "module": "MSP", "name": "IT Landscape", diff --git a/msp/msp/doctype/msp_documentation/__init__.py b/msp/msp/doctype/msp_documentation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/msp_documentation/msp_documentation.js b/msp/msp/doctype/msp_documentation/msp_documentation.js new file mode 100644 index 0000000..e1ba6c0 --- /dev/null +++ b/msp/msp/doctype/msp_documentation/msp_documentation.js @@ -0,0 +1,19 @@ +// Copyright (c) 2023, itsdave GmbH and contributors +// For license information, please see license.txt + + +frappe.ui.form.on('MSP Documentation', { + refresh(frm) { + frm.add_custom_button('1. Get Tactical Agents', function(){ + frappe.call({ + method: 'msp.tactical-rmm.get_agents_pretty', + args: { documentation: frm.doc.name }, + callback:function(r){ + console.log(r.message) + frm.reload_doc() + } + }); + }, 'Workflow'); + } +}); + diff --git a/msp/msp/doctype/msp_documentation/msp_documentation.json b/msp/msp/doctype/msp_documentation/msp_documentation.json new file mode 100644 index 0000000..7809efb --- /dev/null +++ b/msp/msp/doctype/msp_documentation/msp_documentation.json @@ -0,0 +1,87 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "MSPDOC-.#####", + "creation": "2023-02-01 22:24:45.950665", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "landscape", + "customer", + "tactical_rmm_tenant_caption", + "generation_date", + "introduction", + "system_list", + "backup", + "aditional_data" + ], + "fields": [ + { + "fieldname": "landscape", + "fieldtype": "Link", + "label": "Landscape", + "options": "IT Landscape" + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, + { + "fieldname": "introduction", + "fieldtype": "Markdown Editor", + "label": "Introduction" + }, + { + "fieldname": "system_list", + "fieldtype": "Markdown Editor", + "label": "System List" + }, + { + "fetch_from": "landscape.tactical_rmm_tenant_caption", + "fieldname": "tactical_rmm_tenant_caption", + "fieldtype": "Data", + "label": "Tactical RMM Tenant Caption" + }, + { + "fieldname": "backup", + "fieldtype": "Markdown Editor", + "label": "Backup" + }, + { + "fieldname": "aditional_data", + "fieldtype": "Text Editor", + "label": "Aditional Data" + }, + { + "fieldname": "generation_date", + "fieldtype": "Date", + "label": "Generation Date" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-02-02 01:08:47.664035", + "modified_by": "Administrator", + "module": "MSP", + "name": "MSP Documentation", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/msp/msp/doctype/msp_documentation/msp_documentation.py b/msp/msp/doctype/msp_documentation/msp_documentation.py new file mode 100644 index 0000000..a84c117 --- /dev/null +++ b/msp/msp/doctype/msp_documentation/msp_documentation.py @@ -0,0 +1,8 @@ +# Copyright (c) 2023, itsdave GmbH and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class MSPDocumentation(Document): + pass diff --git a/msp/msp/doctype/msp_documentation/test_msp_documentation.py b/msp/msp/doctype/msp_documentation/test_msp_documentation.py new file mode 100644 index 0000000..98d3a4d --- /dev/null +++ b/msp/msp/doctype/msp_documentation/test_msp_documentation.py @@ -0,0 +1,8 @@ +# Copyright (c) 2023, itsdave GmbH and Contributors +# See license.txt + +# import frappe +import unittest + +class TestMSPDocumentation(unittest.TestCase): + pass diff --git a/msp/msp/doctype/msp_settings/msp_settings.json b/msp/msp/doctype/msp_settings/msp_settings.json index 17d5b54..fb0825e 100644 --- a/msp/msp/doctype/msp_settings/msp_settings.json +++ b/msp/msp/doctype/msp_settings/msp_settings.json @@ -16,7 +16,10 @@ "oitc_status_unreachable_color", "qr_code_settings_section", "qr_code_dark_color", - "qr_code_scale" + "qr_code_scale", + "tactical_rmm_integration_section", + "api_url", + "api_key" ], "fields": [ { @@ -77,12 +80,27 @@ "fieldname": "oitc_status_unreachable_color", "fieldtype": "Color", "label": "OITC Status Unreachable Color" + }, + { + "fieldname": "tactical_rmm_integration_section", + "fieldtype": "Section Break", + "label": "Tactical RMM Integration" + }, + { + "fieldname": "api_url", + "fieldtype": "Data", + "label": "API URL" + }, + { + "fieldname": "api_key", + "fieldtype": "Password", + "label": "API Key" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-07-19 12:16:04.805493", + "modified": "2023-02-01 22:22:53.062314", "modified_by": "Administrator", "module": "MSP", "name": "MSP Settings", diff --git a/msp/msp/workspace/msp/msp.json b/msp/msp/workspace/msp/msp.json index 5103fb4..c8b2387 100644 --- a/msp/msp/workspace/msp/msp.json +++ b/msp/msp/workspace/msp/msp.json @@ -102,6 +102,15 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Documentation", + "link_to": "MSP Documentation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -129,7 +138,7 @@ "type": "Link" } ], - "modified": "2022-01-27 22:49:32.927072", + "modified": "2023-02-01 22:47:16.924689", "modified_by": "Administrator", "module": "MSP", "name": "MSP", diff --git a/msp/tactical-rmm.py b/msp/tactical-rmm.py new file mode 100644 index 0000000..a7294ee --- /dev/null +++ b/msp/tactical-rmm.py @@ -0,0 +1,116 @@ +import frappe +from frappe.utils.password import get_decrypted_password + +import requests +import json +from pprint import pprint + +@frappe.whitelist() +def get_agents_pretty(documentation): + documentation_doc = frappe.get_doc("MSP Documentation", documentation) + + if not documentation_doc.tactical_rmm_tenant_caption: + frappe.throw("Tennant Caption missing") + + client_name = documentation_doc.tactical_rmm_tenant_caption + + agents = get_all_agents() + + agent_list = [] + for agent in agents: + if agent["client_name"] == client_name: + agent_list.append(agent) + + output = make_agent_md_output(agent_list) + + documentation_doc.system_list = output + documentation_doc.save() + return agent_list + + +def get_all_agents(): + settings = frappe.get_single("MSP Settings") + if not settings.api_key: + frappe.throw("API Key is missing") + if not settings.api_url: + frappe.throw("API URL is missing") + + API = settings.api_url + HEADERS = { + "Content-Type": "application/json", + "X-API-KEY": get_decrypted_password("MSP Settings", "MSP Settings", "api_key", raise_exception=True), + } + + agents = requests.get(f"{API}/agents/?detail=true", headers=HEADERS) + + return agents.json() + + +def make_agent_md_output(agents): + md_output = "" + for agent in agents: + md_output += f''' +#### {agent["hostname"]} +- OS: {agent["operating_system"]} +- CPU: {agent["cpu_model"]} +- GPU: {agent["graphics"]} +- Disks: {agent["physical_disks"]} +- Model: {render_model(agent["make_model"])} +- Type: {agent["monitoring_type"]} +- Site: {agent["site_name"]} +- Last User: {agent["logged_username"]} +''' + if agent["description"]: + md_output += f'''Description: +{agent["description"]} +''' + + return md_output + + +def render_model(model): + if model == "System manufacturer System Product Name": + return "not specified" + if model == "Xen HVM domU": + return "Virtual Mashine running on Xen Hypervisor" + return model + + + +""" +{'agent_id': 'mXXJYhUHwrMPcAAuvsmGMFhcVsjWVMQqHKaVCfBN', +23:31:32 web.1 | 'alert_template': None, +23:31:32 web.1 | 'block_policy_inheritance': False, +23:31:32 web.1 | 'boot_time': 1675142998.0, +23:31:32 web.1 | 'checks': {'failing': 0, +23:31:32 web.1 | 'has_failing_checks': False, +23:31:32 web.1 | 'info': 0, +23:31:32 web.1 | 'passing': 0, +23:31:32 web.1 | 'total': 0, +23:31:32 web.1 | 'warning': 0}, +23:31:32 web.1 | 'client_name': 'Cohrs Werkstaetten', +23:31:32 web.1 | 'cpu_model': ['Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz'], +23:31:32 web.1 | 'description': '', +23:31:32 web.1 | 'goarch': 'amd64', +23:31:32 web.1 | 'graphics': 'NVIDIA Quadro K2000D', +23:31:32 web.1 | 'has_patches_pending': False, +23:31:32 web.1 | 'hostname': 'CWWS13', +23:31:32 web.1 | 'italic': False, +23:31:32 web.1 | 'last_seen': '2023-01-31T14:34:41.479173Z', +23:31:32 web.1 | 'local_ips': '192.168.24.161', +23:31:32 web.1 | 'logged_username': 'o.ruschmeyer', +23:31:32 web.1 | 'maintenance_mode': False, +23:31:32 web.1 | 'make_model': 'System manufacturer System Product Name', +23:31:32 web.1 | 'monitoring_type': 'workstation', +23:31:32 web.1 | 'needs_reboot': False, +23:31:32 web.1 | 'operating_system': 'Windows 10 Pro, 64 bit v22H2 (build 19045.2486)', +23:31:32 web.1 | 'overdue_dashboard_alert': False, +23:31:32 web.1 | 'overdue_email_alert': False, +23:31:32 web.1 | 'overdue_text_alert': False, +23:31:32 web.1 | 'pending_actions_count': 0, +23:31:32 web.1 | 'physical_disks': ['Kingston SHPM2280P2/240G 224GB IDE'], +23:31:32 web.1 | 'plat': 'windows', +23:31:32 web.1 | 'public_ip': '90.187.0.65', +23:31:32 web.1 | 'site_name': 'Fallingbostel', +23:31:32 web.1 | 'status': 'overdue', +23:31:32 web.1 | 'version': '2.4.4'} """ \ No newline at end of file