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",
|
||||
"oitc_host_uuid",
|
||||
"rmm_agent_id",
|
||||
"rmm_instance"
|
||||
"rmm_instance",
|
||||
"documentation_section",
|
||||
"visible_in_documentation",
|
||||
"documentation_text"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -215,14 +218,35 @@
|
||||
"fieldname": "rmm_software",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"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",
|
||||
"links": [],
|
||||
"modified": "2023-06-08 23:00:31.338542",
|
||||
"modified": "2025-03-10 14:55:30.929828",
|
||||
"modified_by": "Administrator",
|
||||
"module": "MSP",
|
||||
"name": "IT Object",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@ -265,6 +289,7 @@
|
||||
"search_fields": "title, main_ip, item",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1,
|
||||
"track_views": 1
|
||||
|
@ -5,22 +5,80 @@
|
||||
frappe.ui.form.on('MSP Documentation', {
|
||||
refresh(frm) {
|
||||
frm.add_custom_button('1. Get Tactical Agents', function(){
|
||||
frappe.dom.freeze('Fetching Tactical Agents...');
|
||||
frappe.call({
|
||||
method: 'msp.tactical-rmm.get_agents_pretty',
|
||||
args: { documentation: frm.doc.name },
|
||||
callback: function(r) {
|
||||
console.log(r.message)
|
||||
frm.reload_doc()
|
||||
frappe.dom.unfreeze();
|
||||
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');
|
||||
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({
|
||||
method: 'msp.tactical-rmm.search_office',
|
||||
args: { documentation: frm.doc.name },
|
||||
callback: function(r) {
|
||||
console.log(r.message)
|
||||
frm.reload_doc()
|
||||
frappe.dom.unfreeze();
|
||||
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');
|
||||
|
@ -11,11 +11,12 @@
|
||||
"customer",
|
||||
"customer_name",
|
||||
"tactical_rmm_tenant_caption",
|
||||
"tactical_rmm_site_name",
|
||||
"generation_date",
|
||||
"introduction",
|
||||
"introduction_text",
|
||||
"it_objects",
|
||||
"server_list",
|
||||
"workstation_list",
|
||||
"system_list",
|
||||
"backup",
|
||||
"aditional_data"
|
||||
],
|
||||
@ -34,16 +35,6 @@
|
||||
"options": "Customer",
|
||||
"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",
|
||||
"fieldname": "tactical_rmm_tenant_caption",
|
||||
@ -52,7 +43,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "backup",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Backup"
|
||||
},
|
||||
{
|
||||
@ -67,12 +58,12 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "server_list",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Server List"
|
||||
},
|
||||
{
|
||||
"fieldname": "workstation_list",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Workstation List"
|
||||
},
|
||||
{
|
||||
@ -81,11 +72,27 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Customer Name",
|
||||
"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,
|
||||
"links": [],
|
||||
"modified": "2023-06-09 07:14:25.256192",
|
||||
"modified": "2025-03-11 08:06:34.303790",
|
||||
"modified_by": "Administrator",
|
||||
"module": "MSP",
|
||||
"name": "MSP Documentation",
|
||||
@ -105,5 +112,6 @@
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"modified": "2024-07-30 07:36:09.918509",
|
||||
"modified": "2024-10-11 10:44:54.148354",
|
||||
"modified_by": "D.Malinowski@itsdave.de",
|
||||
"module": "MSP",
|
||||
"name": "Technik",
|
||||
@ -87,7 +87,7 @@
|
||||
"public": 1,
|
||||
"quick_lists": [],
|
||||
"roles": [],
|
||||
"sequence_id": 2.0,
|
||||
"sequence_id": 3.0,
|
||||
"shortcuts": [],
|
||||
"title": "Technik"
|
||||
}
|
@ -5,6 +5,7 @@ import requests
|
||||
import json
|
||||
from pprint import pprint
|
||||
import re
|
||||
from .tools import render_card_html, render_single_card
|
||||
|
||||
@frappe.whitelist()
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
site_name = documentation_doc.tactical_rmm_site_name
|
||||
agents = get_all_agents()
|
||||
print(agents)
|
||||
|
||||
# Filter and organize 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)
|
||||
# Filter by client_name and optionally by site_name if provided
|
||||
if agent["client_name"] == client_name and (not site_name or agent["site_name"] == site_name):
|
||||
# Format agent data for card rendering
|
||||
agent_item = {
|
||||
'title': agent['hostname'], # Using hostname as title
|
||||
'type': agent['monitoring_type'], # workstation/server
|
||||
'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)
|
||||
output_workstation = make_agent_md_output(workstation_list)
|
||||
output_server = make_agent_md_output(server_list)
|
||||
# Check if any agents were found
|
||||
if not agent_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
|
||||
documentation_doc.workstation_list = output_workstation
|
||||
documentation_doc.server_list = output_server
|
||||
# Update the documentation with the message
|
||||
documentation_doc.system_list = no_agents_message
|
||||
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()
|
||||
|
||||
return agent_list
|
||||
|
||||
|
||||
@ -179,29 +214,32 @@ def get_patches_for_agent(agent_id=None):
|
||||
|
||||
|
||||
def make_agent_md_output(agents):
|
||||
md_output = ""
|
||||
# Prepare items for card rendering
|
||||
items = []
|
||||
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"])}
|
||||
- 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"]:
|
||||
md_output += f'''Description:
|
||||
{agent["description"]}
|
||||
'''
|
||||
items.append({
|
||||
'title': agent['hostname'],
|
||||
'type': agent['monitoring_type'],
|
||||
'ip': agent['local_ips'],
|
||||
'location': agent['site_name'],
|
||||
'status': agent['status'],
|
||||
'metadata': {
|
||||
'OS': agent['operating_system'],
|
||||
'CPU': ", ".join(agent['cpu_model']) if isinstance(agent['cpu_model'], list) else agent['cpu_model'],
|
||||
'GPU': agent['graphics'],
|
||||
'Disks': ", ".join(agent['physical_disks']) if isinstance(agent['physical_disks'], list) else agent['physical_disks'],
|
||||
'Model': render_model(agent['make_model']),
|
||||
'Serial Number': agent.get('serial_number'),
|
||||
'Type': agent['monitoring_type'],
|
||||
'Site': agent['site_name'],
|
||||
'Public IP': agent['public_ip'],
|
||||
'Last Seen': agent['last_seen'],
|
||||
'Last User': agent['logged_username']
|
||||
},
|
||||
'description': agent.get('description')
|
||||
})
|
||||
|
||||
return md_output
|
||||
return render_card_html(items, "agent")
|
||||
|
||||
|
||||
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("---")
|
||||
|
||||
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