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.
Customers can view their VPS server information in the portal.
""",
'author': 'Your Company',
'website': 'https://www.yourcompany.com',
'author': 'Openworx',
'website': 'https://www.openworx.nl',
'depends': [
'base',
'portal',
@@ -16,6 +16,7 @@
],
'data': [
'security/ir.model.access.csv',
'security/ir.rule.xml',
'views/vps_server_views.xml',
'views/res_partner_views.xml',
'views/portal_templates.xml',
@@ -25,4 +26,4 @@
'application': False,
'auto_install': False,
'license': 'LGPL-3',
}
}

View File

@@ -1,27 +1,30 @@
from odoo import http, _
from odoo.exceptions import AccessError, MissingError
from odoo.exceptions import AccessError
from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
from odoo.osv.expression import OR
class CustomerPortalVPS(CustomerPortal):
class VPSPortal(CustomerPortal):
def _prepare_home_portal_values(self, 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:
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
@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):
values = self._prepare_portal_layout_values()
partner = request.env.user.partner_id
VPSServer = request.env['vps.server']
domain = [('customer_id', '=', request.env.user.partner_id.id)]
domain = [('customer_id', '=', partner.id)]
searchbar_sortings = {
'name': {'label': _('Name'), 'order': 'name'},
'ip_address': {'label': _('IP Address'), 'order': 'ip_address'},
'state': {'label': _('State'), 'order': 'state'},
}
if not sortby:
@@ -62,19 +65,41 @@ class CustomerPortalVPS(CustomerPortal):
})
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)
def portal_my_vps_server(self, vps_server_id=None, access_token=None, **kw):
@http.route(['/my/vps-servers/<int:vps_id>'], type='http', auth="user", website=True)
def portal_my_vps_server(self, vps_id, **kw):
try:
vps_server_sudo = self._document_check_access('vps.server', vps_server_id, access_token)
except (AccessError, MissingError):
return request.redirect('/my')
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')
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)
return request.render("ow_vm_management.portal_vps_server_page", values)
def _vps_server_get_page_view_values(self, vps_server, access_token, **kwargs):
def _vps_server_get_page_view_values(self, vps_server, access_token=None, **kwargs):
values = {
'page_name': '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.exceptions import UserError
from odoo.exceptions import UserError, AccessError
import requests
import urllib3
import ipaddress
@@ -20,6 +20,12 @@ class VPSServer(models.Model):
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)
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):
super()._compute_access_url()
@@ -31,6 +37,71 @@ class VPSServer(models.Model):
if self.ct_id and self.vm_id:
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):
self.ensure_one()
if not self.proxmox_server_id:
@@ -75,10 +146,12 @@ class VPSServer(models.Model):
config_data = config_response.json()['data']
# 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.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
ipv4_addresses = []
@@ -127,6 +200,8 @@ class VPSServer(models.Model):
else: # QEMU
self.os = config_data.get('ostype', 'Unknown')
self.fetch_state()
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
@@ -156,4 +231,4 @@ class VPSServer(models.Model):
def unlink(self):
for record in self:
record.message_unsubscribe(partner_ids=[record.customer_id.id])
return super(VPSServer, self).unlink()
return super(VPSServer, self).unlink()

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='text-right'>CPU</th>
<th class='text-right'>RAM (GB)</th>
<th class='text-right'>Storage (GB)</th>
</tr>
</thead>
<tbody>
@@ -48,6 +49,9 @@
<td class='text-right'>
<span t-field="server.ram"/>
</td>
<td class='text-right'>
<span t-field="server.storage"/>
</td>
</tr>
</t>
</tbody>
@@ -70,6 +74,15 @@
</h5>
</t>
<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="col-12 col-md-6">
<strong>IPv4 Address:</strong> <span t-field="vps_server.ipv4_address"/>
@@ -89,6 +102,9 @@
<div class="col-12 col-md-6">
<strong>Operating System:</strong> <span t-field="vps_server.os"/>
</div>
<div class="col-12 col-md-6">
<strong>State:</strong> <span t-field="vps_server.state"/>
</div>
</div>
</t>
</t>

View File

@@ -6,7 +6,12 @@
<field name="arch" type="xml">
<form>
<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"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
@@ -24,6 +29,7 @@
<field name="proxmox_server_id"/>
<field name="ct_id"/>
<field name="vm_id"/>
<field name="managed_server"/>
</group>
</group>
</sheet>
@@ -83,4 +89,4 @@
action="menu_action_vps_server"
parent="menu_ow_vm_management"
sequence="10"/>
</odoo>
</odoo>