From 1da787072072bd536e22a907ed9be1c39ffb34e2 Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 4 Aug 2024 22:26:24 +0200 Subject: [PATCH] Fill VSP server field with Proxmox VM CT ID data --- ow_vm_management/__manifest__.py | 6 +- .../__pycache__/vps_server.cpython-39.pyc | Bin 1092 -> 4773 bytes ow_vm_management/models/vps_server.py | 158 +++++++++++++++++- ow_vm_management/views/portal_templates.xml | 29 ++-- ow_vm_management/views/vps_server_views.xml | 36 +++- 5 files changed, 200 insertions(+), 29 deletions(-) diff --git a/ow_vm_management/__manifest__.py b/ow_vm_management/__manifest__.py index 68cf405..acdb619 100644 --- a/ow_vm_management/__manifest__.py +++ b/ow_vm_management/__manifest__.py @@ -9,7 +9,11 @@ """, 'author': 'Your Company', 'website': 'https://www.yourcompany.com', - 'depends': ['base', 'portal'], + 'depends': [ + 'base', + 'portal', + 'mail', + ], 'data': [ 'security/ir.model.access.csv', 'views/vps_server_views.xml', diff --git a/ow_vm_management/models/__pycache__/vps_server.cpython-39.pyc b/ow_vm_management/models/__pycache__/vps_server.cpython-39.pyc index fa7d6b73521138627f525110bc8b0520fb445fa1..5c2bc224af0624759870140490bb9591d6d0fdd0 100644 GIT binary patch literal 4773 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 delta 577 zcmYk3ziZn-6vyv}B+IfT%V}tr4uR4^g%;Ae6hfdSgN?zo!!6>_JG%;!&b_k(^^&21 zbjc7jcJ7*gAZxe$2RCIcbnH^-&^skV?!n{py?0;U@!pT;Kf{E_afIxdTwg8zkjv!h zrx#WM0$iaQGh7o!YRV`^?-3B7X9%dk$KF@UeDLNd^M5&eMomyl5S2E;q7)TqL;0#K zT*$ zQ#L+F3k)Mj&qB6yjy%L3*-ws5|Kn^v7wSZ)ZFI8Iue9U#FP`ob6X>-no23br_`E5V zfNgX%+dBYILhEh+aQ0#EP+HEg3he*e@8qC;LT`OHxU~I8Uy^LM{hdxvjo0v6n5bOk z{LXE9N6lI+g)+2QZH)h}l6=jm;tNzM zFDkBeA*w3R)!5$MrIDi@m)uw>k_hR?mO|{=2uXqyJiL#TbdRQ4U_1#@D&l<9CC?_~ dPu%vpHxD{=y<7(qzOn1wRN^hRN;~r|`48{Xjv)X5 diff --git a/ow_vm_management/models/vps_server.py b/ow_vm_management/models/vps_server.py index ce220a4..339952f 100644 --- a/ow_vm_management/models/vps_server.py +++ b/ow_vm_management/models/vps_server.py @@ -1,19 +1,159 @@ -from odoo import models, fields, api +from odoo import models, fields, api, _ +from odoo.exceptions import UserError +import requests +import urllib3 +import ipaddress class VPSServer(models.Model): _name = 'vps.server' _description = 'VPS Server' _inherit = ['portal.mixin', 'mail.thread', 'mail.activity.mixin'] - name = fields.Char(string='Server Name', required=True) - ip_address = fields.Char(string='IP Address', required=True) - cpu = fields.Integer(string='CPU Cores') - ram = fields.Float(string='RAM (GB)') - storage = fields.Float(string='Storage (GB)') - os = fields.Char(string='Operating System') - customer_id = fields.Many2one('res.partner', string='Customer', required=True) + name = fields.Char(string='Server Name', required=True, tracking=True) + ipv4_address = fields.Text(string='IPv4 Addresses', tracking=True) + ipv6_address = fields.Text(string='IPv6 Addresses', tracking=True) + cpu = fields.Integer(string='CPU Cores', tracking=True) + ram = fields.Float(string='RAM (GB)', tracking=True) + storage = fields.Float(string='Storage (GB)', tracking=True) + os = fields.Char(string='Operating System', tracking=True) + customer_id = fields.Many2one('res.partner', string='Customer', required=True, tracking=True) + proxmox_server_id = fields.Many2one('proxmox.server', string='Proxmox Server', tracking=True) + ct_id = fields.Integer(string='CT ID (LXC)', tracking=True) + vm_id = fields.Integer(string='VM ID (QEMU)', tracking=True) def _compute_access_url(self): super()._compute_access_url() for server in self: - server.access_url = '/my/vps-servers/%s' % server.id \ No newline at end of file + server.access_url = '/my/vps-servers/%s' % server.id + + @api.onchange('ct_id', 'vm_id') + def _onchange_id(self): + if self.ct_id and self.vm_id: + raise UserError(_("Please provide either CT ID or VM ID, not both.")) + + def action_fetch_proxmox_data(self): + self.ensure_one() + if not self.proxmox_server_id: + raise UserError(_("Please select a Proxmox server first.")) + + if not self.ct_id and not self.vm_id: + raise UserError(_("Please provide either CT ID or VM ID.")) + + proxmox = self.proxmox_server_id + base_url = proxmox.url.rstrip('/') + headers = { + "Authorization": f"PVEAPIToken={proxmox.api_token_id}={proxmox.api_token_secret}" + } + + try: + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + # Determine if it's a container or VM + if self.ct_id: + vm_type = 'lxc' + vm_id = self.ct_id + else: + vm_type = 'qemu' + vm_id = self.vm_id + + # Fetch VM/CT status + status_response = requests.get( + f"{base_url}/api2/json/nodes/pve/{vm_type}/{vm_id}/status/current", + verify=False, + headers=headers + ) + status_response.raise_for_status() + status_data = status_response.json()['data'] + + # Fetch VM/CT config + config_response = requests.get( + f"{base_url}/api2/json/nodes/pve/{vm_type}/{vm_id}/config", + verify=False, + headers=headers + ) + config_response.raise_for_status() + config_data = config_response.json()['data'] + + # Update fields + self.name = config_data.get('name', f"{vm_type}-{vm_id}") + self.cpu = status_data.get('cpus', 0) + self.ram = status_data.get('maxmem', 0) / (1024 * 1024 * 1024) # Convert to GB + self.storage = status_data.get('maxdisk', 0) / (1024 * 1024 * 1024) # Convert to GB + + # Try to get IP addresses + ipv4_addresses = [] + ipv6_addresses = [] + + def parse_ip(ip): + try: + ip_obj = ipaddress.ip_address(ip.split('/')[0]) + return str(ip_obj), 'v4' if ip_obj.version == 4 else 'v6' + except ValueError: + return None, None + + if vm_type == 'lxc': + for key, value in config_data.items(): + if key.startswith('net'): + ip_configs = value.split(',') + for config in ip_configs: + if config.startswith('ip=') or config.startswith('ip6='): + ip = config.split('=')[1] + parsed_ip, ip_type = parse_ip(ip) + if parsed_ip: + if ip_type == 'v4': + ipv4_addresses.append(parsed_ip) + else: + ipv6_addresses.append(parsed_ip) + else: # QEMU + for key, value in config_data.items(): + if key.startswith('ipconfig'): + ips = value.split(',') + for ip_config in ips: + if '=' in ip_config: + ip = ip_config.split('=')[1] + parsed_ip, ip_type = parse_ip(ip) + if parsed_ip: + if ip_type == 'v4': + ipv4_addresses.append(parsed_ip) + else: + ipv6_addresses.append(parsed_ip) + + self.ipv4_address = ', '.join(ipv4_addresses) if ipv4_addresses else 'No IPv4 found' + self.ipv6_address = ', '.join(ipv6_addresses) if ipv6_addresses else 'No IPv6 found' + + # Try to get OS info + if vm_type == 'lxc': + self.os = config_data.get('ostype', 'Unknown') + else: # QEMU + self.os = config_data.get('ostype', 'Unknown') + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('Data Fetched'), + 'message': _('Successfully fetched data from Proxmox server.'), + 'sticky': False, + 'type': 'success', + } + } + + except requests.exceptions.RequestException as e: + raise UserError(_("Failed to fetch data from Proxmox server: %s") % str(e)) + + @api.model + def create(self, vals): + record = super(VPSServer, self).create(vals) + record.message_subscribe(partner_ids=[record.customer_id.id]) + return record + + def write(self, vals): + res = super(VPSServer, self).write(vals) + if 'customer_id' in vals: + self.message_subscribe(partner_ids=[vals['customer_id']]) + return res + + def unlink(self): + for record in self: + record.message_unsubscribe(partner_ids=[record.customer_id.id]) + return super(VPSServer, self).unlink() \ No newline at end of file diff --git a/ow_vm_management/views/portal_templates.xml b/ow_vm_management/views/portal_templates.xml index 6e29808..e078d01 100644 --- a/ow_vm_management/views/portal_templates.xml +++ b/ow_vm_management/views/portal_templates.xml @@ -4,15 +4,15 @@ VPS Servers - - + +