From f9829258f0674967176d3142a57316fff7519717 Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 9 Jun 2023 07:03:37 +0200 Subject: [PATCH] Reporting, RRM features --- msp/msp/doctype/it_landscape/it_landscape.js | 18 +++ .../doctype/it_landscape/it_landscape.json | 11 +- msp/msp/doctype/it_object/it_object.json | 36 ++++- .../doctype/it_software_matching/__init__.py | 0 .../it_software_matching.js | 8 + .../it_software_matching.json | 70 +++++++++ .../it_software_matching.py | 8 + .../test_it_software_matching.py | 8 + .../msp_documentation/msp_documentation.js | 10 ++ .../msp_documentation/msp_documentation.json | 14 +- msp/msp/doctype/msp_functions/__init__.py | 0 .../doctype/msp_functions/msp_functions.js | 8 + .../doctype/msp_functions/msp_functions.json | 41 +++++ .../doctype/msp_functions/msp_functions.py | 8 + .../msp_functions/test_msp_functions.py | 8 + msp/msp/doctype/rmm_instance/__init__.py | 0 msp/msp/doctype/rmm_instance/rmm_instance.js | 8 + .../doctype/rmm_instance/rmm_instance.json | 76 ++++++++++ msp/msp/doctype/rmm_instance/rmm_instance.py | 8 + .../doctype/rmm_instance/test_rmm_instance.py | 8 + msp/tactical-rmm.py | 140 +++++++++++++++++- 21 files changed, 481 insertions(+), 7 deletions(-) create mode 100644 msp/msp/doctype/it_software_matching/__init__.py create mode 100644 msp/msp/doctype/it_software_matching/it_software_matching.js create mode 100644 msp/msp/doctype/it_software_matching/it_software_matching.json create mode 100644 msp/msp/doctype/it_software_matching/it_software_matching.py create mode 100644 msp/msp/doctype/it_software_matching/test_it_software_matching.py create mode 100644 msp/msp/doctype/msp_functions/__init__.py create mode 100644 msp/msp/doctype/msp_functions/msp_functions.js create mode 100644 msp/msp/doctype/msp_functions/msp_functions.json create mode 100644 msp/msp/doctype/msp_functions/msp_functions.py create mode 100644 msp/msp/doctype/msp_functions/test_msp_functions.py create mode 100644 msp/msp/doctype/rmm_instance/__init__.py create mode 100644 msp/msp/doctype/rmm_instance/rmm_instance.js create mode 100644 msp/msp/doctype/rmm_instance/rmm_instance.json create mode 100644 msp/msp/doctype/rmm_instance/rmm_instance.py create mode 100644 msp/msp/doctype/rmm_instance/test_rmm_instance.py diff --git a/msp/msp/doctype/it_landscape/it_landscape.js b/msp/msp/doctype/it_landscape/it_landscape.js index edfc078..c86a399 100644 --- a/msp/msp/doctype/it_landscape/it_landscape.js +++ b/msp/msp/doctype/it_landscape/it_landscape.js @@ -9,6 +9,9 @@ frappe.ui.form.on('IT Landscape', { }; if (frm.doc.monitoring_link) { frm.add_custom_button('Open Monitoring', () => frm.trigger('open_monitoring'), 'Actions'); + }; + if (frm.doc.rmm_instance) { + frm.add_custom_button('Get Agents From RMM', () => frm.trigger('rmm_get_agents'), 'RMM'); } }, open_ticket_system: function(frm) { @@ -17,6 +20,21 @@ frappe.ui.form.on('IT Landscape', { open_monitoring: function(frm) { window.open(frm.doc.monitoring_link, '_blank').focus(); }, + rmm_get_agents: function(frm) { + frappe.call({ + "method": "msp.tactical-rmm.get_agents", + args: { + "it_landscape": frm.doc.name, + "rmm_instance": frm.doc.rmm_instance, + "tactical_rmm_tenant_caption": frm.doc.tactical_rmm_tenant_caption + }, + callback: (response) => { + frappe.msgprint(__(response.message)); + } + + }) + }, + copy_ssh_keys: function(frm) { frappe.call({ diff --git a/msp/msp/doctype/it_landscape/it_landscape.json b/msp/msp/doctype/it_landscape/it_landscape.json index 77526a1..180470b 100644 --- a/msp/msp/doctype/it_landscape/it_landscape.json +++ b/msp/msp/doctype/it_landscape/it_landscape.json @@ -21,7 +21,8 @@ "landscape_image", "monitoring_link", "ticket_system_link", - "tactical_rmm_tenant_caption" + "tactical_rmm_tenant_caption", + "rmm_instance" ], "fields": [ { @@ -112,11 +113,17 @@ "fieldtype": "Link", "label": "IT Contract", "options": "IT Contract" + }, + { + "fieldname": "rmm_instance", + "fieldtype": "Link", + "label": "RMM Instance", + "options": "RMM Instance" } ], "image_field": "landscape_image", "links": [], - "modified": "2023-02-22 20:02:46.515700", + "modified": "2023-06-08 23:20:35.777651", "modified_by": "Administrator", "module": "MSP", "name": "IT Landscape", diff --git a/msp/msp/doctype/it_object/it_object.json b/msp/msp/doctype/it_object/it_object.json index 58f9c63..6381bb7 100644 --- a/msp/msp/doctype/it_object/it_object.json +++ b/msp/msp/doctype/it_object/it_object.json @@ -28,10 +28,15 @@ "linked_objects", "network_config_section", "ip_adresses", + "rmm_data_section", + "rmm_specs", + "rmm_software", "external_links_section", "admin_interface_link", "monitoring_link", - "oitc_host_uuid" + "oitc_host_uuid", + "rmm_agent_id", + "rmm_instance" ], "fields": [ { @@ -183,11 +188,38 @@ "fieldname": "serial_number", "fieldtype": "Data", "label": "Serial Number" + }, + { + "fieldname": "rmm_agent_id", + "fieldtype": "Data", + "label": "RMM Agent ID" + }, + { + "fieldname": "rmm_instance", + "fieldtype": "Link", + "label": "RMM Instance", + "options": "RMM Instance" + }, + { + "collapsible": 1, + "fieldname": "rmm_data_section", + "fieldtype": "Section Break", + "label": "RMM Data" + }, + { + "fieldname": "rmm_specs", + "fieldtype": "Markdown Editor", + "label": "RMM Specs" + }, + { + "fieldname": "rmm_software", + "fieldtype": "Markdown Editor", + "label": "RMM Software" } ], "image_field": "image", "links": [], - "modified": "2023-03-02 10:10:07.173113", + "modified": "2023-06-08 23:00:31.338542", "modified_by": "Administrator", "module": "MSP", "name": "IT Object", diff --git a/msp/msp/doctype/it_software_matching/__init__.py b/msp/msp/doctype/it_software_matching/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/it_software_matching/it_software_matching.js b/msp/msp/doctype/it_software_matching/it_software_matching.js new file mode 100644 index 0000000..c2bf0f9 --- /dev/null +++ b/msp/msp/doctype/it_software_matching/it_software_matching.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, itsdave GmbH and contributors +// For license information, please see license.txt + +frappe.ui.form.on('IT Software Matching', { + // refresh: function(frm) { + + // } +}); diff --git a/msp/msp/doctype/it_software_matching/it_software_matching.json b/msp/msp/doctype/it_software_matching/it_software_matching.json new file mode 100644 index 0000000..a148241 --- /dev/null +++ b/msp/msp/doctype/it_software_matching/it_software_matching.json @@ -0,0 +1,70 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "ITSWMATCH-.#####", + "creation": "2023-06-08 22:10:40.703139", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "search_regex", + "software_name", + "category", + "active" + ], + "fields": [ + { + "fieldname": "search_regex", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Search Regex" + }, + { + "fieldname": "software_name", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Software Name" + }, + { + "fieldname": "category", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Category", + "options": "\nSoftware\nMS Office" + }, + { + "default": "0", + "fieldname": "active", + "fieldtype": "Check", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Active" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-06-08 22:10:59.456228", + "modified_by": "Administrator", + "module": "MSP", + "name": "IT Software Matching", + "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/it_software_matching/it_software_matching.py b/msp/msp/doctype/it_software_matching/it_software_matching.py new file mode 100644 index 0000000..980eb79 --- /dev/null +++ b/msp/msp/doctype/it_software_matching/it_software_matching.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 ITSoftwareMatching(Document): + pass diff --git a/msp/msp/doctype/it_software_matching/test_it_software_matching.py b/msp/msp/doctype/it_software_matching/test_it_software_matching.py new file mode 100644 index 0000000..127dff4 --- /dev/null +++ b/msp/msp/doctype/it_software_matching/test_it_software_matching.py @@ -0,0 +1,8 @@ +# Copyright (c) 2023, itsdave GmbH and Contributors +# See license.txt + +# import frappe +import unittest + +class TestITSoftwareMatching(unittest.TestCase): + pass diff --git a/msp/msp/doctype/msp_documentation/msp_documentation.js b/msp/msp/doctype/msp_documentation/msp_documentation.js index e1ba6c0..313c602 100644 --- a/msp/msp/doctype/msp_documentation/msp_documentation.js +++ b/msp/msp/doctype/msp_documentation/msp_documentation.js @@ -14,6 +14,16 @@ frappe.ui.form.on('MSP Documentation', { } }); }, 'Workflow'); + frm.add_custom_button('2. office suche', function(){ + frappe.call({ + method: 'msp.tactical-rmm.search_office', + 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 index 7809efb..1e201f6 100644 --- a/msp/msp/doctype/msp_documentation/msp_documentation.json +++ b/msp/msp/doctype/msp_documentation/msp_documentation.json @@ -12,6 +12,8 @@ "tactical_rmm_tenant_caption", "generation_date", "introduction", + "server_list", + "workstation_list", "system_list", "backup", "aditional_data" @@ -59,11 +61,21 @@ "fieldname": "generation_date", "fieldtype": "Date", "label": "Generation Date" + }, + { + "fieldname": "server_list", + "fieldtype": "Markdown Editor", + "label": "Server List" + }, + { + "fieldname": "workstation_list", + "fieldtype": "Markdown Editor", + "label": "Workstation List" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-02-02 01:08:47.664035", + "modified": "2023-06-08 21:12:01.615350", "modified_by": "Administrator", "module": "MSP", "name": "MSP Documentation", diff --git a/msp/msp/doctype/msp_functions/__init__.py b/msp/msp/doctype/msp_functions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/msp_functions/msp_functions.js b/msp/msp/doctype/msp_functions/msp_functions.js new file mode 100644 index 0000000..418b0b1 --- /dev/null +++ b/msp/msp/doctype/msp_functions/msp_functions.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, itsdave GmbH and contributors +// For license information, please see license.txt + +frappe.ui.form.on('MSP Functions', { + // refresh: function(frm) { + + // } +}); diff --git a/msp/msp/doctype/msp_functions/msp_functions.json b/msp/msp/doctype/msp_functions/msp_functions.json new file mode 100644 index 0000000..11abc23 --- /dev/null +++ b/msp/msp/doctype/msp_functions/msp_functions.json @@ -0,0 +1,41 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-06-08 21:34:09.516160", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "get_office_versions" + ], + "fields": [ + { + "fieldname": "get_office_versions", + "fieldtype": "Button", + "label": "Get Office Versions", + "options": "msp.tactical-rmm.search_office" + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2023-06-08 21:34:09.516160", + "modified_by": "Administrator", + "module": "MSP", + "name": "MSP Functions", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 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_functions/msp_functions.py b/msp/msp/doctype/msp_functions/msp_functions.py new file mode 100644 index 0000000..d3ccc12 --- /dev/null +++ b/msp/msp/doctype/msp_functions/msp_functions.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 MSPFunctions(Document): + pass diff --git a/msp/msp/doctype/msp_functions/test_msp_functions.py b/msp/msp/doctype/msp_functions/test_msp_functions.py new file mode 100644 index 0000000..a72b6ed --- /dev/null +++ b/msp/msp/doctype/msp_functions/test_msp_functions.py @@ -0,0 +1,8 @@ +# Copyright (c) 2023, itsdave GmbH and Contributors +# See license.txt + +# import frappe +import unittest + +class TestMSPFunctions(unittest.TestCase): + pass diff --git a/msp/msp/doctype/rmm_instance/__init__.py b/msp/msp/doctype/rmm_instance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/rmm_instance/rmm_instance.js b/msp/msp/doctype/rmm_instance/rmm_instance.js new file mode 100644 index 0000000..a2da5c2 --- /dev/null +++ b/msp/msp/doctype/rmm_instance/rmm_instance.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, itsdave GmbH and contributors +// For license information, please see license.txt + +frappe.ui.form.on('RMM Instance', { + // refresh: function(frm) { + + // } +}); diff --git a/msp/msp/doctype/rmm_instance/rmm_instance.json b/msp/msp/doctype/rmm_instance/rmm_instance.json new file mode 100644 index 0000000..aed07e1 --- /dev/null +++ b/msp/msp/doctype/rmm_instance/rmm_instance.json @@ -0,0 +1,76 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format:RMMINST-{caption}", + "creation": "2023-06-08 22:46:28.727092", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "caption", + "type", + "api_url", + "user", + "key" + ], + "fields": [ + { + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Type", + "options": "Tactical RMM", + "reqd": 1 + }, + { + "fieldname": "api_url", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "API URL", + "reqd": 1 + }, + { + "fieldname": "caption", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Caption", + "reqd": 1 + }, + { + "fieldname": "user", + "fieldtype": "Data", + "label": "User" + }, + { + "fieldname": "key", + "fieldtype": "Password", + "label": "Key" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-06-08 22:48:17.914520", + "modified_by": "Administrator", + "module": "MSP", + "name": "RMM Instance", + "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/rmm_instance/rmm_instance.py b/msp/msp/doctype/rmm_instance/rmm_instance.py new file mode 100644 index 0000000..52848c3 --- /dev/null +++ b/msp/msp/doctype/rmm_instance/rmm_instance.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 RMMInstance(Document): + pass diff --git a/msp/msp/doctype/rmm_instance/test_rmm_instance.py b/msp/msp/doctype/rmm_instance/test_rmm_instance.py new file mode 100644 index 0000000..bbe8c34 --- /dev/null +++ b/msp/msp/doctype/rmm_instance/test_rmm_instance.py @@ -0,0 +1,8 @@ +# Copyright (c) 2023, itsdave GmbH and Contributors +# See license.txt + +# import frappe +import unittest + +class TestRMMInstance(unittest.TestCase): + pass diff --git a/msp/tactical-rmm.py b/msp/tactical-rmm.py index a7294ee..de743bc 100644 --- a/msp/tactical-rmm.py +++ b/msp/tactical-rmm.py @@ -4,6 +4,84 @@ from frappe.utils.password import get_decrypted_password import requests import json from pprint import pprint +import re + +@frappe.whitelist() +def get_agents(it_landscape, rmm_instance = None, tactical_rmm_tenant_caption = None): + pass + + +@frappe.whitelist() +def get_relevant_software_for_agent(agent_id): + found_software = [] + software_for_agent = get_software_for_agent(agent_id) + if "software" in software_for_agent: + for s in software_for_agent["software"]: + result = _match_software(s) + if result: + found_software.append(result) + + return(found_software) + +def _match_software(rmm_software_elememt): + sw_match_list = frappe.get_all("IT Software Matching", fields=["search_regex", "software_name", "category"], filters={"active": 1}) + for el in sw_match_list: + result = re.match(el["search_regex"]) + if result: + return {el["software_name"], el["category"]} + return None + +@frappe.whitelist() +def search_office(agents = None): + points_for_found_strings = {} + agents_with_office = [] + + if not agents: + agents = get_all_agents() + todo = len(agents) + count = 0 + for agent in agents: + found_office = False + count = count + 1 + print("verarbeite agent " + str(count) + " von " + str(todo)) + software_for_agent = get_software_for_agent(agent["agent_id"]) + if "software" in software_for_agent: + for s in software_for_agent["software"]: + if (s["name"].lower().startswith("Microsoft Office".lower())): + found_string = s["name"] + found_office = True + + if found_string in points_for_found_strings.keys(): + points_for_found_strings[found_string] = points_for_found_strings[found_string] + 1 + else: + points_for_found_strings[found_string] = 1 + if found_office: + agents_with_office.append(agent) + else: + pass + + + + print(points_for_found_strings) + #print(len(agents_with_office)) + search_for_office_patches(agents_with_office) + print(points_for_found_strings) + +def search_for_office_patches(agents_with_office): + count = 0 + for agent in agents_with_office: + found_patches_for_office = False + patches = get_patches_for_agent(agent["agent_id"]) + for patch in patches: + if "office".lower() in patch["title"].lower(): + found_patches_for_office = True + if found_patches_for_office == False: + print("kein patch für agent mit office gefunden: " + agent["hostname"] + " | " + agent["client_name"]) + count = count + 1 + print("Anzahl Clients: " + str(count)) + + + @frappe.whitelist() def get_agents_pretty(documentation): @@ -15,15 +93,30 @@ def get_agents_pretty(documentation): client_name = documentation_doc.tactical_rmm_tenant_caption agents = get_all_agents() - + print(agents) agent_list = [] + workstation_list = [] + server_list = [] + #if not agent_list: + # frappe.throw("API Abfrage hat keine Agents geliefert.") + + for agent in agents: if agent["client_name"] == client_name: agent_list.append(agent) + if agent["monitoring_type"] == "workstation": + workstation_list.append(agent) + if agent["monitoring_type"] == "server": + server_list.append(agent) + pprint(agent) output = make_agent_md_output(agent_list) + output_workstation = make_agent_md_output(workstation_list) + output_server = make_agent_md_output(server_list) documentation_doc.system_list = output + documentation_doc.workstation_list = output_workstation + documentation_doc.server_list = output_server documentation_doc.save() return agent_list @@ -42,9 +135,48 @@ def get_all_agents(): } agents = requests.get(f"{API}/agents/?detail=true", headers=HEADERS) - return agents.json() +def get_software_for_agent(agent_id=None): + 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), + } + + if not agent_id: + frappe.throw("Agent ID fehlt") + + software_for_agent = requests.get(f"{API}/software/{agent_id}/", headers=HEADERS) + return software_for_agent.json() + +def get_patches_for_agent(agent_id=None): + 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), + } + + if not agent_id: + frappe.throw("Agent ID fehlt") + + patches_for_agent = requests.get(f"{API}/winupdate/{agent_id}/", headers=HEADERS) + return patches_for_agent.json() + + + def make_agent_md_output(agents): md_output = "" @@ -56,8 +188,12 @@ def make_agent_md_output(agents): - GPU: {agent["graphics"]} - Disks: {agent["physical_disks"]} - Model: {render_model(agent["make_model"])} +- Serial Number: {agent["serial_number"]} - Type: {agent["monitoring_type"]} - Site: {agent["site_name"]} +- Local IPs: {agent["local_ips"]} +- Public IP: {agent["public_ip"]} +- Last Seen: {agent["last_seen"]} - Last User: {agent["logged_username"]} ''' if agent["description"]: