mirror of
https://github.com/itsdave-de/msp.git
synced 2025-05-06 15:35:12 +02:00
docuemtation improvements
This commit is contained in:
parent
0bafeac13f
commit
e83d61d8a4
1
msp/doctype/msp_documentation/msp_documentation.js
Normal file
1
msp/doctype/msp_documentation/msp_documentation.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -36,7 +36,10 @@
|
|||||||
"monitoring_link",
|
"monitoring_link",
|
||||||
"oitc_host_uuid",
|
"oitc_host_uuid",
|
||||||
"rmm_agent_id",
|
"rmm_agent_id",
|
||||||
"rmm_instance"
|
"rmm_instance",
|
||||||
|
"documentation_section",
|
||||||
|
"visible_in_documentation",
|
||||||
|
"documentation_text"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -215,14 +218,35 @@
|
|||||||
"fieldname": "rmm_software",
|
"fieldname": "rmm_software",
|
||||||
"fieldtype": "Markdown Editor",
|
"fieldtype": "Markdown Editor",
|
||||||
"label": "RMM Software"
|
"label": "RMM Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"collapsible_depends_on": "eval: !doc.documentation_text",
|
||||||
|
"fieldname": "documentation_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Documentation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This will be visible on the on the MSP Documentation",
|
||||||
|
"fieldname": "documentation_text",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Documentation Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "If checked, this object will be selected when creating a MSP Documentation.",
|
||||||
|
"fieldname": "visible_in_documentation",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "visible in Documentation"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-08 23:00:31.338542",
|
"modified": "2025-03-10 14:55:30.929828",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "MSP",
|
"module": "MSP",
|
||||||
"name": "IT Object",
|
"name": "IT Object",
|
||||||
|
"naming_rule": "Expression (old style)",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -265,6 +289,7 @@
|
|||||||
"search_fields": "title, main_ip, item",
|
"search_fields": "title, main_ip, item",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"title_field": "title",
|
"title_field": "title",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_views": 1
|
"track_views": 1
|
||||||
|
@ -5,22 +5,80 @@
|
|||||||
frappe.ui.form.on('MSP Documentation', {
|
frappe.ui.form.on('MSP Documentation', {
|
||||||
refresh(frm) {
|
refresh(frm) {
|
||||||
frm.add_custom_button('1. Get Tactical Agents', function(){
|
frm.add_custom_button('1. Get Tactical Agents', function(){
|
||||||
|
frappe.dom.freeze('Fetching Tactical Agents...');
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'msp.tactical-rmm.get_agents_pretty',
|
method: 'msp.tactical-rmm.get_agents_pretty',
|
||||||
args: { documentation: frm.doc.name },
|
args: { documentation: frm.doc.name },
|
||||||
callback:function(r){
|
callback: function(r) {
|
||||||
console.log(r.message)
|
frappe.dom.unfreeze();
|
||||||
frm.reload_doc()
|
if (r.exc) {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Failed to fetch tactical agents. Please try again.')
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Successfully fetched tactical agents'),
|
||||||
|
indicator: 'green'
|
||||||
|
});
|
||||||
|
frm.reload_doc();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 'Workflow');
|
}, 'Workflow');
|
||||||
frm.add_custom_button('2. office suche', function(){
|
|
||||||
|
frm.add_custom_button('2. Office Search', function(){
|
||||||
|
frappe.dom.freeze('Searching for Office installations...');
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'msp.tactical-rmm.search_office',
|
method: 'msp.tactical-rmm.search_office',
|
||||||
args: { documentation: frm.doc.name },
|
args: { documentation: frm.doc.name },
|
||||||
callback:function(r){
|
callback: function(r) {
|
||||||
console.log(r.message)
|
frappe.dom.unfreeze();
|
||||||
frm.reload_doc()
|
if (r.exc) {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Failed to complete Office search. Please try again.')
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Office search completed'),
|
||||||
|
indicator: 'green'
|
||||||
|
});
|
||||||
|
frm.reload_doc();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 'Workflow');
|
||||||
|
|
||||||
|
// Add new button for IT Objects documentation
|
||||||
|
frm.add_custom_button('3. Generate IT Objects', function(){
|
||||||
|
frappe.dom.freeze('Generating IT Objects documentation...');
|
||||||
|
frappe.call({
|
||||||
|
method: 'msp.tools.get_documentation_html',
|
||||||
|
args: {
|
||||||
|
it_landscape: frm.doc.landscape
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
frappe.dom.unfreeze();
|
||||||
|
if (r.exc) {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Failed to generate IT Objects documentation. Please try again.')
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (r.message) {
|
||||||
|
frm.set_value('it_objects', r.message);
|
||||||
|
frm.save().then(() => {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('IT Objects documentation generated successfully'),
|
||||||
|
indicator: 'green'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 'Workflow');
|
}, 'Workflow');
|
||||||
|
@ -11,11 +11,12 @@
|
|||||||
"customer",
|
"customer",
|
||||||
"customer_name",
|
"customer_name",
|
||||||
"tactical_rmm_tenant_caption",
|
"tactical_rmm_tenant_caption",
|
||||||
|
"tactical_rmm_site_name",
|
||||||
"generation_date",
|
"generation_date",
|
||||||
"introduction",
|
"introduction_text",
|
||||||
|
"it_objects",
|
||||||
"server_list",
|
"server_list",
|
||||||
"workstation_list",
|
"workstation_list",
|
||||||
"system_list",
|
|
||||||
"backup",
|
"backup",
|
||||||
"aditional_data"
|
"aditional_data"
|
||||||
],
|
],
|
||||||
@ -34,16 +35,6 @@
|
|||||||
"options": "Customer",
|
"options": "Customer",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "introduction",
|
|
||||||
"fieldtype": "Markdown Editor",
|
|
||||||
"label": "Introduction"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "system_list",
|
|
||||||
"fieldtype": "Markdown Editor",
|
|
||||||
"label": "System List"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fetch_from": "landscape.tactical_rmm_tenant_caption",
|
"fetch_from": "landscape.tactical_rmm_tenant_caption",
|
||||||
"fieldname": "tactical_rmm_tenant_caption",
|
"fieldname": "tactical_rmm_tenant_caption",
|
||||||
@ -52,7 +43,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "backup",
|
"fieldname": "backup",
|
||||||
"fieldtype": "Markdown Editor",
|
"fieldtype": "Text Editor",
|
||||||
"label": "Backup"
|
"label": "Backup"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -67,12 +58,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "server_list",
|
"fieldname": "server_list",
|
||||||
"fieldtype": "Markdown Editor",
|
"fieldtype": "Text Editor",
|
||||||
"label": "Server List"
|
"label": "Server List"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "workstation_list",
|
"fieldname": "workstation_list",
|
||||||
"fieldtype": "Markdown Editor",
|
"fieldtype": "Text Editor",
|
||||||
"label": "Workstation List"
|
"label": "Workstation List"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -81,11 +72,27 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Customer Name",
|
"label": "Customer Name",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Explain: What is the target of this particular Documentation? Give an over the solution you are describing.",
|
||||||
|
"fieldname": "introduction_text",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Introduction Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "it_objects",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "IT Objects"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "tactical_rmm_site_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Tactical RMM Site Name"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-09 07:14:25.256192",
|
"modified": "2025-03-11 08:06:34.303790",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "MSP",
|
"module": "MSP",
|
||||||
"name": "MSP Documentation",
|
"name": "MSP Documentation",
|
||||||
@ -105,5 +112,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
0
msp/msp/print_format/__init__.py
Normal file
0
msp/msp/print_format/__init__.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"absolute_value": 0,
|
||||||
|
"align_labels_right": 0,
|
||||||
|
"creation": "2025-03-11 00:26:31.608123",
|
||||||
|
"custom_format": 0,
|
||||||
|
"default_print_language": "de",
|
||||||
|
"disabled": 0,
|
||||||
|
"doc_type": "MSP Documentation",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Print Format",
|
||||||
|
"font": "Default",
|
||||||
|
"font_size": 0,
|
||||||
|
"format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div class=\\\"row clearfix main\\\">\\r\\n\\t<div class=\\\"col-xs-5 center\\\">\\r\\n\\t\\t<div style=\\\"height: 75px;\\\">\\r\\n\\t\\t\\t<img src=\\\"/files/logo-1024x300.svg\\\" height=\\\"100%\\\">\\r\\n\\t\\t</div>\\r\\n\\t</div>\\r\\n\\t<div class=\\\"col-xs-5 center\\\" style=\\\"height: 75px; display: table;\\\">\\r\\n\\t\\t<p style=\\\"display: table-cell; vertical-align: middle; text-align: right; color: #333A3F; text-transform: uppercase; font-size: 22px;\\\">\\r\\nIT Documentation\\r\\n\\t\\t<br>{{ doc.name }}</p>\\r\\n\\t</div>\\r\\n\\t<div class=\\\"col-xs-2 center\\\">\\r\\n\\t\\t<img class=\\\"vcenter\\\" style=\\\"height: 75px;\\\" src=\\\"https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=https://erpnext.itsdave.de/app/msp-documentation/{{ doc.name }}&format=svg&color=333A3F\\\">\\r\\n\\t</div>\\r\\n</div>\\r\\n<div class=\\\"row\\\">\\r\\n\\t<div class=\\\"col-xs-12\\\">\\r\\n\\t\\t<hr style=\\\"border-color: #333A3F;\\\">\\r\\n\\t</div>\\r\\n</div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"customer\", \"print_hide\": 0, \"label\": \"Customer\"}, {\"fieldname\": \"customer_name\", \"print_hide\": 0, \"label\": \"Customer Name\"}, {\"fieldname\": \"generation_date\", \"print_hide\": 0, \"label\": \"Generation Date\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"landscape\", \"print_hide\": 0, \"label\": \"Landscape\"}, {\"fieldname\": \"tactical_rmm_tenant_caption\", \"print_hide\": 0, \"label\": \"Tactical RMM Tenant Caption\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<h3>Introduction</h3>\"}, {\"fieldname\": \"introduction_text\", \"print_hide\": 0, \"label\": \"Introduction Text\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<hr>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"IT Objects\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"it_objects\", \"print_hide\": 0, \"label\": \"IT Objects\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Systems from RMM\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<h3>Server Systems</h3>\\n<hr>\"}, {\"fieldname\": \"server_list\", \"print_hide\": 0, \"label\": \"Server List\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<h3>Workstation Systems</h3>\\n<hr>\"}, {\"fieldname\": \"workstation_list\", \"print_hide\": 0, \"label\": \"Workstation List\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Backups\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"backup\", \"print_hide\": 0, \"label\": \"Backup\"}, {\"fieldname\": \"aditional_data\", \"print_hide\": 0, \"label\": \"Aditional Data\"}]",
|
||||||
|
"idx": 0,
|
||||||
|
"line_breaks": 0,
|
||||||
|
"margin_bottom": 0.0,
|
||||||
|
"margin_left": 0.0,
|
||||||
|
"margin_right": 0.0,
|
||||||
|
"margin_top": 0.0,
|
||||||
|
"modified": "2025-03-11 00:27:41.672487",
|
||||||
|
"modified_by": "D.Malinowski@itsdave.de",
|
||||||
|
"module": "MSP",
|
||||||
|
"name": "HTML MSP Documentation",
|
||||||
|
"owner": "D.Malinowski@itsdave.de",
|
||||||
|
"page_number": "Hide",
|
||||||
|
"print_format_builder": 1,
|
||||||
|
"print_format_builder_beta": 0,
|
||||||
|
"print_format_type": "Jinja",
|
||||||
|
"raw_printing": 0,
|
||||||
|
"show_section_headings": 1,
|
||||||
|
"standard": "Yes"
|
||||||
|
}
|
@ -77,7 +77,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2024-07-30 07:36:09.918509",
|
"modified": "2024-10-11 10:44:54.148354",
|
||||||
"modified_by": "D.Malinowski@itsdave.de",
|
"modified_by": "D.Malinowski@itsdave.de",
|
||||||
"module": "MSP",
|
"module": "MSP",
|
||||||
"name": "Technik",
|
"name": "Technik",
|
||||||
@ -87,7 +87,7 @@
|
|||||||
"public": 1,
|
"public": 1,
|
||||||
"quick_lists": [],
|
"quick_lists": [],
|
||||||
"roles": [],
|
"roles": [],
|
||||||
"sequence_id": 2.0,
|
"sequence_id": 3.0,
|
||||||
"shortcuts": [],
|
"shortcuts": [],
|
||||||
"title": "Technik"
|
"title": "Technik"
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ import requests
|
|||||||
import json
|
import json
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
import re
|
import re
|
||||||
|
from .tools import render_card_html, render_single_card
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_agents(it_landscape, rmm_instance = None, tactical_rmm_tenant_caption = None):
|
def get_agents(it_landscape, rmm_instance = None, tactical_rmm_tenant_caption = None):
|
||||||
@ -88,36 +89,70 @@ def get_agents_pretty(documentation):
|
|||||||
documentation_doc = frappe.get_doc("MSP Documentation", documentation)
|
documentation_doc = frappe.get_doc("MSP Documentation", documentation)
|
||||||
|
|
||||||
if not documentation_doc.tactical_rmm_tenant_caption:
|
if not documentation_doc.tactical_rmm_tenant_caption:
|
||||||
frappe.throw("Tennant Caption missing")
|
frappe.throw("Tenant Caption missing")
|
||||||
|
|
||||||
client_name = documentation_doc.tactical_rmm_tenant_caption
|
client_name = documentation_doc.tactical_rmm_tenant_caption
|
||||||
|
site_name = documentation_doc.tactical_rmm_site_name
|
||||||
agents = get_all_agents()
|
agents = get_all_agents()
|
||||||
print(agents)
|
|
||||||
|
# Filter and organize agents
|
||||||
agent_list = []
|
agent_list = []
|
||||||
workstation_list = []
|
|
||||||
server_list = []
|
|
||||||
#if not agent_list:
|
|
||||||
# frappe.throw("API Abfrage hat keine Agents geliefert.")
|
|
||||||
|
|
||||||
|
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
if agent["client_name"] == client_name:
|
# Filter by client_name and optionally by site_name if provided
|
||||||
agent_list.append(agent)
|
if agent["client_name"] == client_name and (not site_name or agent["site_name"] == site_name):
|
||||||
if agent["monitoring_type"] == "workstation":
|
# Format agent data for card rendering
|
||||||
workstation_list.append(agent)
|
agent_item = {
|
||||||
if agent["monitoring_type"] == "server":
|
'title': agent['hostname'], # Using hostname as title
|
||||||
server_list.append(agent)
|
'type': agent['monitoring_type'], # workstation/server
|
||||||
pprint(agent)
|
'ip': agent['local_ips'],
|
||||||
|
'location': agent['site_name'],
|
||||||
|
'metadata': {
|
||||||
|
'Operating System': agent['operating_system'],
|
||||||
|
'Hardware Model': render_model(agent['make_model']),
|
||||||
|
'Serial Number': agent.get('serial_number', ''),
|
||||||
|
'CPU': ", ".join(agent['cpu_model']) if isinstance(agent['cpu_model'], list) else agent['cpu_model'],
|
||||||
|
'Graphics': agent['graphics'],
|
||||||
|
'Storage': ", ".join(agent['physical_disks']) if isinstance(agent['physical_disks'], list) else agent['physical_disks'],
|
||||||
|
'Public IP': agent['public_ip'],
|
||||||
|
'Last Seen': agent['last_seen'],
|
||||||
|
'Last User': agent['logged_username']
|
||||||
|
},
|
||||||
|
'description': agent.get('description', '')
|
||||||
|
}
|
||||||
|
agent_list.append(agent_item)
|
||||||
|
|
||||||
output = make_agent_md_output(agent_list)
|
# Check if any agents were found
|
||||||
output_workstation = make_agent_md_output(workstation_list)
|
if not agent_list:
|
||||||
output_server = make_agent_md_output(server_list)
|
no_agents_message = f"<div class='alert alert-warning'>No agents found for client '{client_name}'"
|
||||||
|
if site_name:
|
||||||
|
no_agents_message += f" at site '{site_name}'"
|
||||||
|
no_agents_message += ".</div>"
|
||||||
|
|
||||||
documentation_doc.system_list = output
|
# Update the documentation with the message
|
||||||
documentation_doc.workstation_list = output_workstation
|
documentation_doc.system_list = no_agents_message
|
||||||
documentation_doc.server_list = output_server
|
documentation_doc.workstation_list = no_agents_message
|
||||||
|
documentation_doc.server_list = no_agents_message
|
||||||
|
documentation_doc.save()
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Generate HTML using the shared render_card_html function from tools.py
|
||||||
|
all_agents_html = render_card_html(agent_list, "tactical")
|
||||||
|
|
||||||
|
# Filter lists for workstations and servers
|
||||||
|
workstation_list = [a for a in agent_list if a['type'].lower() == 'workstation']
|
||||||
|
server_list = [a for a in agent_list if a['type'].lower() == 'server']
|
||||||
|
|
||||||
|
# Generate separate HTML for workstations and servers
|
||||||
|
workstation_html = render_card_html(workstation_list, "tactical")
|
||||||
|
server_html = render_card_html(server_list, "tactical")
|
||||||
|
|
||||||
|
# Update the documentation
|
||||||
|
documentation_doc.system_list = all_agents_html
|
||||||
|
documentation_doc.workstation_list = workstation_html
|
||||||
|
documentation_doc.server_list = server_html
|
||||||
documentation_doc.save()
|
documentation_doc.save()
|
||||||
|
|
||||||
return agent_list
|
return agent_list
|
||||||
|
|
||||||
|
|
||||||
@ -179,29 +214,32 @@ def get_patches_for_agent(agent_id=None):
|
|||||||
|
|
||||||
|
|
||||||
def make_agent_md_output(agents):
|
def make_agent_md_output(agents):
|
||||||
md_output = ""
|
# Prepare items for card rendering
|
||||||
|
items = []
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
md_output += f'''
|
items.append({
|
||||||
#### {agent["hostname"]}
|
'title': agent['hostname'],
|
||||||
- OS: {agent["operating_system"]}
|
'type': agent['monitoring_type'],
|
||||||
- CPU: {agent["cpu_model"]}
|
'ip': agent['local_ips'],
|
||||||
- GPU: {agent["graphics"]}
|
'location': agent['site_name'],
|
||||||
- Disks: {agent["physical_disks"]}
|
'status': agent['status'],
|
||||||
- Model: {render_model(agent["make_model"])}
|
'metadata': {
|
||||||
- Serial Number: {agent["serial_number"]}
|
'OS': agent['operating_system'],
|
||||||
- Type: {agent["monitoring_type"]}
|
'CPU': ", ".join(agent['cpu_model']) if isinstance(agent['cpu_model'], list) else agent['cpu_model'],
|
||||||
- Site: {agent["site_name"]}
|
'GPU': agent['graphics'],
|
||||||
- Local IPs: {agent["local_ips"]}
|
'Disks': ", ".join(agent['physical_disks']) if isinstance(agent['physical_disks'], list) else agent['physical_disks'],
|
||||||
- Public IP: {agent["public_ip"]}
|
'Model': render_model(agent['make_model']),
|
||||||
- Last Seen: {agent["last_seen"]}
|
'Serial Number': agent.get('serial_number'),
|
||||||
- Last User: {agent["logged_username"]}
|
'Type': agent['monitoring_type'],
|
||||||
'''
|
'Site': agent['site_name'],
|
||||||
if agent["description"]:
|
'Public IP': agent['public_ip'],
|
||||||
md_output += f'''Description:
|
'Last Seen': agent['last_seen'],
|
||||||
{agent["description"]}
|
'Last User': agent['logged_username']
|
||||||
'''
|
},
|
||||||
|
'description': agent.get('description')
|
||||||
|
})
|
||||||
|
|
||||||
return md_output
|
return render_card_html(items, "agent")
|
||||||
|
|
||||||
|
|
||||||
def render_model(model):
|
def render_model(model):
|
||||||
|
211
msp/tools.py
211
msp/tools.py
@ -620,6 +620,217 @@ def get_status_from_ticket():
|
|||||||
print(f"{field}: {value}")
|
print(f"{field}: {value}")
|
||||||
print("---")
|
print("---")
|
||||||
|
|
||||||
|
def render_card_html(items, item_type):
|
||||||
|
"""
|
||||||
|
Generate formatted HTML cards for displaying IT objects or RMM agents.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
items: List of dictionaries containing item data
|
||||||
|
item_type: Type of items ("it_object", "tactical", or "agent")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted HTML string with cards
|
||||||
|
"""
|
||||||
|
html = '''
|
||||||
|
<style>
|
||||||
|
/* Base card styles with more specific selectors */
|
||||||
|
div.card {
|
||||||
|
border: 1px solid #ddd !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
padding: 15px !important;
|
||||||
|
margin-bottom: 15px !important;
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
|
||||||
|
page-break-inside: avoid !important;
|
||||||
|
display: block !important;
|
||||||
|
width: 100% !important;
|
||||||
|
-webkit-print-color-adjust: exact !important;
|
||||||
|
print-color-adjust: exact !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header styles */
|
||||||
|
div.card h3 {
|
||||||
|
margin: 0 0 10px 0 !important;
|
||||||
|
color: #000000 !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Metadata container */
|
||||||
|
div.card div.metadata {
|
||||||
|
margin: 10px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Individual metadata items */
|
||||||
|
div.card div.metadata-item {
|
||||||
|
margin-bottom: 5px !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
line-height: 1.4 !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Metadata labels */
|
||||||
|
div.card span.metadata-label {
|
||||||
|
color: #666666 !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
min-width: 120px !important;
|
||||||
|
display: inline-block !important;
|
||||||
|
margin-right: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status badges */
|
||||||
|
div.card span.status-badge {
|
||||||
|
display: inline-block !important;
|
||||||
|
padding: 2px 8px !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
margin-left: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Horizontal rule */
|
||||||
|
div.card hr {
|
||||||
|
margin: 10px 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
border-top: 1px solid #eee !important;
|
||||||
|
height: 1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section headers */
|
||||||
|
div.section-break h3 {
|
||||||
|
margin: 20px 0 10px 0 !important;
|
||||||
|
color: #333333 !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
border-bottom: 2px solid #eee !important;
|
||||||
|
padding-bottom: 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
div.card {
|
||||||
|
border: 1px solid #000000 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Group items by type if needed
|
||||||
|
if item_type == "tactical":
|
||||||
|
server_items = [i for i in items if i.get('type', '').lower() == 'server']
|
||||||
|
workstation_items = [i for i in items if i.get('type', '').lower() == 'workstation']
|
||||||
|
|
||||||
|
html += '<div class="section-break" data-label="Systems from RMM">'
|
||||||
|
if server_items:
|
||||||
|
html += '<h3>Server Systems</h3>'
|
||||||
|
html += ''.join(render_single_card(item) for item in server_items)
|
||||||
|
|
||||||
|
if workstation_items:
|
||||||
|
html += '<h3>Workstation Systems</h3>'
|
||||||
|
html += ''.join(render_single_card(item) for item in workstation_items)
|
||||||
|
html += '</div>'
|
||||||
|
else:
|
||||||
|
html += ''.join(render_single_card(item) for item in items)
|
||||||
|
|
||||||
|
return html
|
||||||
|
|
||||||
|
def render_single_card(item):
|
||||||
|
"""
|
||||||
|
Generate HTML for a single card.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item: Dictionary containing item data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HTML string for a single card
|
||||||
|
"""
|
||||||
|
# Get the basic information
|
||||||
|
title = item.get('title', '')
|
||||||
|
type_value = item.get('type', '').capitalize()
|
||||||
|
ip = item.get('ip', '') or item.get('metadata', {}).get('IP', '') or item.get('metadata', {}).get('Local IPs', '')
|
||||||
|
location = item.get('location', '')
|
||||||
|
|
||||||
|
# Create a header that matches the previous style
|
||||||
|
header = f"{title}"
|
||||||
|
if type_value:
|
||||||
|
header += f" ({type_value})"
|
||||||
|
if ip:
|
||||||
|
header += f" - {ip}"
|
||||||
|
|
||||||
|
html = f'''
|
||||||
|
<div class="card">
|
||||||
|
<h3>{header}</h3>
|
||||||
|
<hr>
|
||||||
|
<div class="metadata">
|
||||||
|
<div class="metadata-item">
|
||||||
|
<span class="metadata-label">Location:</span>
|
||||||
|
<span>{location}</span>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Add all metadata fields if they exist
|
||||||
|
if 'metadata' in item:
|
||||||
|
for key, value in item['metadata'].items():
|
||||||
|
if value and key.lower() != 'ip' and key.lower() != 'local ips' and key.lower() != 'type': # Skip IP and Type as they're already in the header
|
||||||
|
html += f'''
|
||||||
|
<div class="metadata-item">
|
||||||
|
<span class="metadata-label">{key}:</span>
|
||||||
|
<span>{value}</span>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Add description if it exists
|
||||||
|
description = item.get('description', '') or item.get('documentation_text', '')
|
||||||
|
if description:
|
||||||
|
html += f'''
|
||||||
|
<div class="metadata-item">
|
||||||
|
<span class="metadata-label">Description:</span>
|
||||||
|
<span>{description}</span>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
html += '</div></div>'
|
||||||
|
return html
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_documentation_html(it_landscape):
|
||||||
|
"""Generate formatted HTML cards for all IT objects in a landscape that are marked for documentation."""
|
||||||
|
|
||||||
|
it_objects = frappe.get_all(
|
||||||
|
"IT Object",
|
||||||
|
filters={
|
||||||
|
"visible_in_documentation": 1,
|
||||||
|
"it_landscape": it_landscape
|
||||||
|
},
|
||||||
|
fields=[
|
||||||
|
"title", "type", "location", "location_full_path",
|
||||||
|
"status", "main_ip", "image", "documentation_text"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not it_objects:
|
||||||
|
return "<p>No documented IT objects found in this landscape.</p>"
|
||||||
|
|
||||||
|
# Prepare items for card rendering
|
||||||
|
items = []
|
||||||
|
for obj in it_objects:
|
||||||
|
items.append({
|
||||||
|
'title': obj.title,
|
||||||
|
'type': obj.type,
|
||||||
|
'ip': obj.main_ip,
|
||||||
|
'location': obj.location_full_path or obj.location,
|
||||||
|
'status': obj.status,
|
||||||
|
'metadata': {
|
||||||
|
'Type': obj.type,
|
||||||
|
'Status': obj.status
|
||||||
|
},
|
||||||
|
'description': obj.documentation_text
|
||||||
|
})
|
||||||
|
|
||||||
|
return render_card_html(items, "it_object")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user