Compare commits

...

10 Commits

Author SHA1 Message Date
Your Name (aider)
3e1418fc8f fix: revert description change in __manifest__.py 2024-08-06 22:22:09 +02:00
Your Name (aider)
0959988310 feat: Update author and website in manifest 2024-08-06 22:20:51 +02:00
Your Name (aider)
384218612a feat: add dutch translation 2024-08-06 22:11:12 +02:00
Your Name (aider)
53e0f7a467 feat: Add 'Managed Server' boolean field to VPS Server model and form 2024-08-06 22:08:27 +02:00
Mario
e0bad00f0d Minor fixes 2024-08-05 23:45:20 +02:00
Mario
eaf0c775ac Round of storage (GB) 2024-08-05 00:05:35 +02:00
Mario
44a91358ff Prevent URL manipulation in portal for vps 2024-08-05 00:01:31 +02:00
Mario
da325f0c79 Add restart vps in portal 2024-08-04 23:45:11 +02:00
Mario
b79dbe4708 Add stop/start and state for vps 2024-08-04 23:24:04 +02:00
Mario
00724077e1 Add proxmox hostname to vps name 2024-08-04 23:05:56 +02:00
9 changed files with 426 additions and 23 deletions

View File

@@ -7,8 +7,8 @@
This module allows you to record and manage VPS servers for customers. This module allows you to record and manage VPS servers for customers.
Customers can view their VPS server information in the portal. Customers can view their VPS server information in the portal.
""", """,
'author': 'Your Company', 'author': 'Openworx',
'website': 'https://www.yourcompany.com', 'website': 'https://www.openworx.nl',
'depends': [ 'depends': [
'base', 'base',
'portal', 'portal',
@@ -16,6 +16,7 @@
], ],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'security/ir.rule.xml',
'views/vps_server_views.xml', 'views/vps_server_views.xml',
'views/res_partner_views.xml', 'views/res_partner_views.xml',
'views/portal_templates.xml', 'views/portal_templates.xml',

View File

@@ -1,27 +1,30 @@
from odoo import http, _ from odoo import http, _
from odoo.exceptions import AccessError, MissingError from odoo.exceptions import AccessError
from odoo.http import request from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
from odoo.osv.expression import OR from odoo.osv.expression import OR
class CustomerPortalVPS(CustomerPortal): class VPSPortal(CustomerPortal):
def _prepare_home_portal_values(self, counters): def _prepare_home_portal_values(self, counters):
values = super()._prepare_home_portal_values(counters) values = super()._prepare_home_portal_values(counters)
partner = request.env.user.partner_id
VPSServer = request.env['vps.server']
if 'vps_server_count' in counters: if 'vps_server_count' in counters:
values['vps_server_count'] = request.env['vps.server'].search_count([('customer_id', '=', request.env.user.partner_id.id)]) values['vps_server_count'] = VPSServer.search_count([('customer_id', '=', partner.id)])
return values return values
@http.route(['/my/vps-servers', '/my/vps-servers/page/<int:page>'], type='http', auth="user", website=True) @http.route(['/my/vps-servers', '/my/vps-servers/page/<int:page>'], type='http', auth="user", website=True)
def portal_my_vps_servers(self, page=1, date_begin=None, date_end=None, sortby=None, **kw): def portal_my_vps_servers(self, page=1, date_begin=None, date_end=None, sortby=None, **kw):
values = self._prepare_portal_layout_values() values = self._prepare_portal_layout_values()
partner = request.env.user.partner_id
VPSServer = request.env['vps.server'] VPSServer = request.env['vps.server']
domain = [('customer_id', '=', request.env.user.partner_id.id)] domain = [('customer_id', '=', partner.id)]
searchbar_sortings = { searchbar_sortings = {
'name': {'label': _('Name'), 'order': 'name'}, 'name': {'label': _('Name'), 'order': 'name'},
'ip_address': {'label': _('IP Address'), 'order': 'ip_address'}, 'state': {'label': _('State'), 'order': 'state'},
} }
if not sortby: if not sortby:
@@ -62,19 +65,41 @@ class CustomerPortalVPS(CustomerPortal):
}) })
return request.render("ow_vm_management.portal_my_vps_servers", values) return request.render("ow_vm_management.portal_my_vps_servers", values)
@http.route(['/my/vps-servers/<int:vps_server_id>'], type='http', auth="user", website=True) @http.route(['/my/vps-servers/<int:vps_id>'], type='http', auth="user", website=True)
def portal_my_vps_server(self, vps_server_id=None, access_token=None, **kw): def portal_my_vps_server(self, vps_id, **kw):
try: try:
vps_server_sudo = self._document_check_access('vps.server', vps_server_id, access_token) vps_sudo = self._document_check_access('vps.server', vps_id)
except (AccessError, MissingError): if vps_sudo.customer_id != request.env.user.partner_id:
return request.redirect('/my') return request.redirect('/my/vps-servers')
values = self._vps_server_get_page_view_values(vps_sudo, **kw)
return request.render("ow_vm_management.portal_vps_server_page", values)
except AccessError:
return request.redirect('/my/vps-servers')
values = self._vps_server_get_page_view_values(vps_server_sudo, access_token, **kw) def _vps_server_get_page_view_values(self, vps_server, access_token=None, **kwargs):
return request.render("ow_vm_management.portal_vps_server_page", values)
def _vps_server_get_page_view_values(self, vps_server, access_token, **kwargs):
values = { values = {
'page_name': 'vps_server', 'page_name': 'vps_server',
'vps_server': vps_server, 'vps_server': vps_server,
} }
return self._get_page_view_values(vps_server, access_token, values, 'my_vps_servers_history', False, **kwargs) return self._get_page_view_values(vps_server, access_token, values, 'my_vps_servers_history', False, **kwargs)
@http.route(['/my/vps/<int:vps_id>/restart'], type='http', auth="user", website=True)
def portal_restart_vps(self, vps_id, **kw):
try:
vps_sudo = self._document_check_access('vps.server', vps_id)
if vps_sudo.customer_id != request.env.user.partner_id:
return request.redirect('/my/vps-servers')
vps_sudo.action_restart_from_portal()
return request.redirect(f'/my/vps-servers/{vps_id}')
except AccessError:
return request.redirect('/my/vps-servers')
def _document_check_access(self, model_name, document_id, access_token=None):
document = request.env[model_name].sudo().browse(document_id)
document_sudo = document.with_user(request.env.user).sudo()
try:
document_sudo.check_access_rights('read')
document_sudo.check_access_rule('read')
except AccessError:
raise
return document_sudo

265
ow_vm_management/i18n/nl.po Normal file
View File

@@ -0,0 +1,265 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * ow_vm_management
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-08-06 20:10+0000\n"
"PO-Revision-Date: 2023-08-06 20:10+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_vps_server__cpu
msgid "CPU Cores"
msgstr "CPU Kernen"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_vps_server__customer_id
msgid "Customer"
msgstr "Klant"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_vps_server__ipv4_address
msgid "IPv4 Addresses"
msgstr "IPv4 Adressen"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_vps_server__ipv6_address
msgid "IPv6 Addresses"
msgstr "IPv6 Adressen"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_vps_server__managed_server
msgid "Managed Server"
msgstr "Beheerde Server"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_vps_server__name
msgid "Server Name"
msgstr "Servernaam"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_vps_server__os
msgid "Operating System"
msgstr "Besturingssysteem"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_vps_server__ram
msgid "RAM (GB)"
msgstr "RAM (GB)"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_vps_server__storage
msgid "Storage (GB)"
msgstr "Opslag (GB)"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_vps_server__state
msgid "State"
msgstr "Status"
#. module: ow_vm_management
#: model:ir.model.fields.selection,name:ow_vm_management.selection__vps_server__state__running
msgid "Running"
msgstr "Actief"
#. module: ow_vm_management
#: model:ir.model.fields.selection,name:ow_vm_management.selection__vps_server__state__stopped
msgid "Stopped"
msgstr "Gestopt"
#. module: ow_vm_management
#: model:ir.model.fields.selection,name:ow_vm_management.selection__vps_server__state__unknown
msgid "Unknown"
msgstr "Onbekend"
#. module: ow_vm_management
#: model:ir.actions.act_window,name:ow_vm_management.action_vps_server
#: model:ir.ui.menu,name:ow_vm_management.menu_vps_server
msgid "VPS Servers"
msgstr "VPS Servers"
#. module: ow_vm_management
#: model:ir.model,name:ow_vm_management.model_vps_server
msgid "VPS Server"
msgstr "VPS Server"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.view_vps_server_form
msgid "Start"
msgstr "Starten"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.view_vps_server_form
msgid "Shutdown"
msgstr "Afsluiten"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.view_vps_server_form
msgid "Stop"
msgstr "Stoppen"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.view_vps_server_form
msgid "Reboot"
msgstr "Herstarten"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.view_vps_server_form
msgid "Fetch Proxmox Data"
msgstr "Proxmox Gegevens Ophalen"
#. module: ow_vm_management
#: model:ir.model,name:ow_vm_management.model_proxmox_server
msgid "Proxmox Server"
msgstr "Proxmox Server"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_proxmox_server__name
msgid "Server Name"
msgstr "Servernaam"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_proxmox_server__url
msgid "Proxmox URL"
msgstr "Proxmox URL"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_proxmox_server__api_token_id
msgid "API Token ID"
msgstr "API Token ID"
#. module: ow_vm_management
#: model:ir.model.fields,field_description:ow_vm_management.field_proxmox_server__api_token_secret
msgid "API Token Secret"
msgstr "API Token Geheim"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.view_proxmox_server_form
msgid "Test Connection"
msgstr "Verbinding Testen"
#. module: ow_vm_management
#: model:ir.actions.act_window,name:ow_vm_management.action_proxmox_server
#: model:ir.ui.menu,name:ow_vm_management.menu_proxmox_server
msgid "Proxmox Servers"
msgstr "Proxmox Servers"
#. module: ow_vm_management
#: model:ir.ui.menu,name:ow_vm_management.menu_ow_vm_management_root
#: model:ir.ui.menu,name:ow_vm_management.menu_ow_vm_management
msgid "VPS Management"
msgstr "VPS Beheer"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.portal_my_home_vps_servers
#: model_terms:ir.ui.view,arch_db:ow_vm_management.portal_my_vps_servers
msgid "VPS Servers"
msgstr "VPS Servers"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.portal_my_vps_servers
msgid "Server Name"
msgstr "Servernaam"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.portal_my_vps_servers
msgid "IPv4 Address"
msgstr "IPv4 Adres"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.portal_my_vps_servers
msgid "IPv6 Address"
msgstr "IPv6 Adres"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.portal_my_vps_servers
msgid "CPU"
msgstr "CPU"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.portal_my_vps_servers
msgid "RAM (GB)"
msgstr "RAM (GB)"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.portal_my_vps_servers
msgid "Storage (GB)"
msgstr "Opslag (GB)"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.portal_vps_server_page
msgid "Restart VPS"
msgstr "VPS Herstarten"
#. module: ow_vm_management
#: model_terms:ir.ui.view,arch_db:ow_vm_management.portal_vps_server_page
msgid "Are you sure you want to restart this VPS?"
msgstr "Weet u zeker dat u deze VPS wilt herstarten?"
#. module: ow_vm_management
#: code:addons/ow_vm_management/controllers/portal.py:0
#, python-format
msgid "You don't have access to this VPS server."
msgstr "U heeft geen toegang tot deze VPS server."
#. module: ow_vm_management
#: code:addons/ow_vm_management/controllers/portal.py:0
#, python-format
msgid "You don't have permission to restart this VPS."
msgstr "U heeft geen toestemming om deze VPS te herstarten."
#. module: ow_vm_management
#: code:addons/ow_vm_management/models/vps_server.py:0
#, python-format
msgid "Please provide either CT ID or VM ID, not both."
msgstr "Geef alstublieft ofwel een CT ID of een VM ID op, niet beide."
#. module: ow_vm_management
#: code:addons/ow_vm_management/models/vps_server.py:0
#, python-format
msgid "Proxmox API request failed: %s"
msgstr "Proxmox API-verzoek mislukt: %s"
#. module: ow_vm_management
#: code:addons/ow_vm_management/models/vps_server.py:0
#, python-format
msgid "Data Fetched"
msgstr "Gegevens Opgehaald"
#. module: ow_vm_management
#: code:addons/ow_vm_management/models/vps_server.py:0
#, python-format
msgid "Successfully fetched data from Proxmox server."
msgstr "Gegevens succesvol opgehaald van Proxmox server."
#. module: ow_vm_management
#: code:addons/ow_vm_management/models/vps_server.py:0
#, python-format
msgid "Failed to fetch data from Proxmox server: %s"
msgstr "Ophalen van gegevens van Proxmox server mislukt: %s"
#. module: ow_vm_management
#: code:addons/ow_vm_management/models/proxmox_server.py:0
#, python-format
msgid "Connection Successful"
msgstr "Verbinding Succesvol"
#. module: ow_vm_management
#: code:addons/ow_vm_management/models/proxmox_server.py:0
#, python-format
msgid "Successfully connected to the Proxmox server."
msgstr "Succesvol verbonden met de Proxmox server."
#. module: ow_vm_management
#: code:addons/ow_vm_management/models/proxmox_server.py:0
#, python-format
msgid "Failed to connect to Proxmox server: %s"
msgstr "Verbinding maken met Proxmox server mislukt: %s"

View File

@@ -1,5 +1,5 @@
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from odoo.exceptions import UserError from odoo.exceptions import UserError, AccessError
import requests import requests
import urllib3 import urllib3
import ipaddress import ipaddress
@@ -20,6 +20,12 @@ class VPSServer(models.Model):
proxmox_server_id = fields.Many2one('proxmox.server', string='Proxmox Server', tracking=True) proxmox_server_id = fields.Many2one('proxmox.server', string='Proxmox Server', tracking=True)
ct_id = fields.Integer(string='CT ID (LXC)', tracking=True) ct_id = fields.Integer(string='CT ID (LXC)', tracking=True)
vm_id = fields.Integer(string='VM ID (QEMU)', tracking=True) vm_id = fields.Integer(string='VM ID (QEMU)', tracking=True)
state = fields.Selection([
('running', 'Running'),
('stopped', 'Stopped'),
('unknown', 'Unknown')
], string='State', default='unknown', tracking=True)
managed_server = fields.Boolean(string='Managed Server', default=False, tracking=True)
def _compute_access_url(self): def _compute_access_url(self):
super()._compute_access_url() super()._compute_access_url()
@@ -31,6 +37,71 @@ class VPSServer(models.Model):
if self.ct_id and self.vm_id: if self.ct_id and self.vm_id:
raise UserError(_("Please provide either CT ID or VM ID, not both.")) raise UserError(_("Please provide either CT ID or VM ID, not both."))
def _proxmox_request(self, method, endpoint, data=None):
self.ensure_one()
proxmox = self.proxmox_server_id
base_url = proxmox.url.rstrip('/')
headers = {
"Authorization": f"PVEAPIToken={proxmox.api_token_id}={proxmox.api_token_secret}"
}
url = f"{base_url}/api2/json/{endpoint}"
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
try:
response = requests.request(method, url, headers=headers, json=data, verify=False)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise UserError(_("Proxmox API request failed: %s") % str(e))
def action_start(self):
for server in self:
vm_type = 'lxc' if server.ct_id else 'qemu'
vm_id = server.ct_id or server.vm_id
self._proxmox_request('POST', f'nodes/pve/{vm_type}/{vm_id}/status/start')
self.fetch_state()
def action_shutdown(self):
for server in self:
vm_type = 'lxc' if server.ct_id else 'qemu'
vm_id = server.ct_id or server.vm_id
self._proxmox_request('POST', f'nodes/pve/{vm_type}/{vm_id}/status/shutdown')
self.fetch_state()
def action_stop(self):
for server in self:
vm_type = 'lxc' if server.ct_id else 'qemu'
vm_id = server.ct_id or server.vm_id
self._proxmox_request('POST', f'nodes/pve/{vm_type}/{vm_id}/status/stop')
self.fetch_state()
def action_reboot(self):
for server in self:
vm_type = 'lxc' if server.ct_id else 'qemu'
vm_id = server.ct_id or server.vm_id
self._proxmox_request('POST', f'nodes/pve/{vm_type}/{vm_id}/status/reboot')
self.fetch_state()
def fetch_state(self):
for server in self:
vm_type = 'lxc' if server.ct_id else 'qemu'
vm_id = server.ct_id or server.vm_id
status = self._proxmox_request('GET', f'nodes/pve/{vm_type}/{vm_id}/status/current')
server.state = 'running' if status['data']['status'] == 'running' else 'stopped'
def check_access_rule(self, operation):
if self.env.user.has_group('base.group_portal'):
if operation != 'read' or self.customer_id != self.env.user.partner_id:
raise AccessError(_("You don't have access to this VPS server."))
return super(VPSServer, self).check_access_rule(operation)
def action_restart_from_portal(self):
self.ensure_one()
if self.env.user.partner_id != self.customer_id:
raise AccessError(_("You don't have permission to restart this VPS."))
return self.action_reboot()
def action_fetch_proxmox_data(self): def action_fetch_proxmox_data(self):
self.ensure_one() self.ensure_one()
if not self.proxmox_server_id: if not self.proxmox_server_id:
@@ -75,10 +146,12 @@ class VPSServer(models.Model):
config_data = config_response.json()['data'] config_data = config_response.json()['data']
# Update fields # Update fields
self.name = config_data.get('name', f"{vm_type}-{vm_id}") hostname = config_data.get('hostname', '')
vm_name = f"{vm_type}-{vm_id}"
self.name = f"{vm_name} ({hostname})" if hostname else vm_name
self.cpu = status_data.get('cpus', 0) self.cpu = status_data.get('cpus', 0)
self.ram = status_data.get('maxmem', 0) / (1024 * 1024 * 1024) # Convert to GB 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 self.storage = round(status_data.get('maxdisk', 0) / (1024 * 1024 * 1024)) # Convert to GB
# Try to get IP addresses # Try to get IP addresses
ipv4_addresses = [] ipv4_addresses = []
@@ -127,6 +200,8 @@ class VPSServer(models.Model):
else: # QEMU else: # QEMU
self.os = config_data.get('ostype', 'Unknown') self.os = config_data.get('ostype', 'Unknown')
self.fetch_state()
return { return {
'type': 'ir.actions.client', 'type': 'ir.actions.client',
'tag': 'display_notification', 'tag': 'display_notification',

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="vps_server_portal_rule" model="ir.rule">
<field name="name">Portal user can only see own VPS servers</field>
<field name="model_id" ref="model_vps_server"/>
<field name="domain_force">[('customer_id', '=', user.partner_id.id)]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -24,6 +24,7 @@
<th class='d-none d-md-table-cell'>IPv6 Address</th> <th class='d-none d-md-table-cell'>IPv6 Address</th>
<th class='text-right'>CPU</th> <th class='text-right'>CPU</th>
<th class='text-right'>RAM (GB)</th> <th class='text-right'>RAM (GB)</th>
<th class='text-right'>Storage (GB)</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -48,6 +49,9 @@
<td class='text-right'> <td class='text-right'>
<span t-field="server.ram"/> <span t-field="server.ram"/>
</td> </td>
<td class='text-right'>
<span t-field="server.storage"/>
</td>
</tr> </tr>
</t> </t>
</tbody> </tbody>
@@ -70,6 +74,15 @@
</h5> </h5>
</t> </t>
<t t-set="card_body"> <t t-set="card_body">
<div class="row mb-4">
<div class="col-12 text-right">
<a t-att-href="'/my/vps/%s/restart' % vps_server.id"
class="btn btn-primary"
onclick="return confirm('Are you sure you want to restart this VPS?');">
<i class="fa fa-refresh"/> Restart VPS
</a>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<strong>IPv4 Address:</strong> <span t-field="vps_server.ipv4_address"/> <strong>IPv4 Address:</strong> <span t-field="vps_server.ipv4_address"/>
@@ -89,6 +102,9 @@
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<strong>Operating System:</strong> <span t-field="vps_server.os"/> <strong>Operating System:</strong> <span t-field="vps_server.os"/>
</div> </div>
<div class="col-12 col-md-6">
<strong>State:</strong> <span t-field="vps_server.state"/>
</div>
</div> </div>
</t> </t>
</t> </t>

View File

@@ -6,7 +6,12 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<header> <header>
<button name="action_start" string="Start" type="object" icon="fa-play" attrs="{'invisible': [('state', '=', 'running')]}" class="oe_highlight"/>
<button name="action_shutdown" string="Shutdown" type="object" icon="fa-power-off" attrs="{'invisible': [('state', '!=', 'running')]}" class="oe_highlight"/>
<button name="action_stop" string="Stop" type="object" icon="fa-stop" attrs="{'invisible': [('state', '!=', 'running')]}" class="text-danger"/>
<button name="action_reboot" string="Reboot" type="object" icon="fa-refresh" attrs="{'invisible': [('state', '!=', 'running')]}" class="oe_highlight"/>
<button name="action_fetch_proxmox_data" string="Fetch Proxmox Data" type="object" class="oe_highlight"/> <button name="action_fetch_proxmox_data" string="Fetch Proxmox Data" type="object" class="oe_highlight"/>
<field name="state" widget="statusbar"/>
</header> </header>
<sheet> <sheet>
<group> <group>
@@ -24,6 +29,7 @@
<field name="proxmox_server_id"/> <field name="proxmox_server_id"/>
<field name="ct_id"/> <field name="ct_id"/>
<field name="vm_id"/> <field name="vm_id"/>
<field name="managed_server"/>
</group> </group>
</group> </group>
</sheet> </sheet>