Tower: upload cetmix_tower_server 16.0.2.2.9 (via marketplace)
This commit is contained in:
199
addons/cetmix_tower_server/models/cx_tower_server_log.py
Normal file
199
addons/cetmix_tower_server/models/cx_tower_server_log.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# Copyright (C) 2022 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ansi2html import Ansi2HTMLConverter
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
from odoo.exceptions import AccessError
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
html_converter = Ansi2HTMLConverter(inline=True)
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerServerLog(models.Model):
|
||||||
|
"""Server log management.
|
||||||
|
Used to track various server logs.
|
||||||
|
N.B. Do not mistake for command of flight plan log!
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cx.tower.server.log"
|
||||||
|
_inherit = ["cx.tower.access.mixin", "cx.tower.reference.mixin"]
|
||||||
|
_description = "Cetmix Tower Server Log"
|
||||||
|
|
||||||
|
NO_LOG_FETCHED_MESSAGE = _("<log is empty>")
|
||||||
|
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
server_id = fields.Many2one("cx.tower.server", ondelete="cascade")
|
||||||
|
log_type = fields.Selection(
|
||||||
|
selection=lambda self: self._selection_log_type(),
|
||||||
|
required=True,
|
||||||
|
groups="cetmix_tower_server.group_root,cetmix_tower_server.group_manager",
|
||||||
|
default=lambda self: self._selection_log_type()[0][0],
|
||||||
|
)
|
||||||
|
command_id = fields.Many2one(
|
||||||
|
"cx.tower.command",
|
||||||
|
domain="[('action', 'in', ['ssh_command', 'python_code']), "
|
||||||
|
"'|', ('server_ids', 'in', [server_id]), ('server_ids', '=', False)]",
|
||||||
|
groups="cetmix_tower_server.group_root,cetmix_tower_server.group_manager",
|
||||||
|
help="Command that will be executed to get the log data.\n"
|
||||||
|
"Be careful with commands that don't support parallel execution!",
|
||||||
|
)
|
||||||
|
use_sudo = fields.Boolean(
|
||||||
|
groups="cetmix_tower_server.group_root,cetmix_tower_server.group_manager",
|
||||||
|
help="Will use sudo based on server settings."
|
||||||
|
"If no sudo is configured will run without sudo",
|
||||||
|
)
|
||||||
|
file_id = fields.Many2one(
|
||||||
|
"cx.tower.file",
|
||||||
|
domain="[('server_id', '=', server_id)]",
|
||||||
|
groups="cetmix_tower_server.group_root,cetmix_tower_server.group_manager",
|
||||||
|
help="File that will be executed to get the log data",
|
||||||
|
)
|
||||||
|
log_text = fields.Text(readonly=True, copy=False)
|
||||||
|
log_html = fields.Html(compute="_compute_log_html")
|
||||||
|
|
||||||
|
# --- Server template related
|
||||||
|
server_template_id = fields.Many2one("cx.tower.server.template", ondelete="cascade")
|
||||||
|
file_template_id = fields.Many2one(
|
||||||
|
"cx.tower.file.template",
|
||||||
|
ondelete="cascade",
|
||||||
|
groups="cetmix_tower_server.group_root,cetmix_tower_server.group_manager",
|
||||||
|
help="This file template will be used to create log files"
|
||||||
|
" when server is created from a template",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _selection_log_type(self):
|
||||||
|
"""Actions that can be run by a command.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of tuples: available options.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
("command", "Command"),
|
||||||
|
("file", "File"),
|
||||||
|
]
|
||||||
|
|
||||||
|
@api.depends("log_text")
|
||||||
|
def _compute_log_html(self):
|
||||||
|
for record in self:
|
||||||
|
if record.log_text:
|
||||||
|
try:
|
||||||
|
record.log_html = html_converter.convert(record.log_text)
|
||||||
|
# We catch all exceptions to avoid breaking the log display
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error("Error converting log text to HTML: %s", e)
|
||||||
|
record.log_html = False
|
||||||
|
else:
|
||||||
|
record.log_html = False
|
||||||
|
|
||||||
|
def copy(self, default=None):
|
||||||
|
return super(
|
||||||
|
CxTowerServerLog, self.with_context(reference_mixin_skip_self=True)
|
||||||
|
).copy(default)
|
||||||
|
|
||||||
|
def action_open_log(self):
|
||||||
|
"""
|
||||||
|
Open log record in current window
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
self.action_update_log()
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"name": self.name,
|
||||||
|
"res_model": "cx.tower.server.log",
|
||||||
|
"res_id": self.id, # pylint: disable=no-member
|
||||||
|
"view_mode": "form",
|
||||||
|
"target": "current",
|
||||||
|
}
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
"""Override to protect log_text from direct modifications.
|
||||||
|
Bypass with context key 'cx_allow_log_text_update' for internal updates.
|
||||||
|
"""
|
||||||
|
if "log_text" in vals and not self.env.context.get("cx_allow_log_text_update"):
|
||||||
|
raise AccessError(_("You are not allowed to modify the server log output."))
|
||||||
|
return super().write(vals)
|
||||||
|
|
||||||
|
def action_update_log(self):
|
||||||
|
"""Update log text from source"""
|
||||||
|
|
||||||
|
# We are using `sudo` to override command/file access limitations
|
||||||
|
for rec in self.sudo().with_context(cx_allow_log_text_update=True):
|
||||||
|
rec.log_text = rec._get_formatted_log_text()
|
||||||
|
|
||||||
|
def _get_log_text(self):
|
||||||
|
"""
|
||||||
|
Get log text from source
|
||||||
|
Use this function to get pure log text from source.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Text: log text
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
if self.log_type == "file" and self.file_id:
|
||||||
|
return self._get_log_from_file()
|
||||||
|
elif self.log_type == "command" and self.command_id:
|
||||||
|
return self._get_log_from_command()
|
||||||
|
|
||||||
|
def _get_formatted_log_text(self):
|
||||||
|
"""
|
||||||
|
Get formatted log text.
|
||||||
|
Use this function to get formatted log text.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Text: formatted log text
|
||||||
|
"""
|
||||||
|
log_text = self._get_log_text()
|
||||||
|
if log_text:
|
||||||
|
return self._format_log_text(log_text)
|
||||||
|
return self.NO_LOG_FETCHED_MESSAGE
|
||||||
|
|
||||||
|
def _format_log_text(self, log_text):
|
||||||
|
"""
|
||||||
|
Format log text.
|
||||||
|
Use this function to format log text.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Text: formatted log text
|
||||||
|
"""
|
||||||
|
return log_text
|
||||||
|
|
||||||
|
def _get_log_from_file(self):
|
||||||
|
"""Get log from a file.
|
||||||
|
Override this function to implement custom log handler
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Text: log text
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
if self.file_id.source == "server":
|
||||||
|
return self.file_id.code
|
||||||
|
if self.file_id.source == "tower":
|
||||||
|
return self.file_id.code_on_server
|
||||||
|
|
||||||
|
def _get_log_from_command(self):
|
||||||
|
"""Get log from a command.
|
||||||
|
Returns:
|
||||||
|
Text: log text
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
use_sudo = self.use_sudo and self.server_id.use_sudo
|
||||||
|
command_result = self.server_id.with_context(no_command_log=True).run_command(
|
||||||
|
self.command_id, sudo=use_sudo
|
||||||
|
)
|
||||||
|
log_text = self.NO_LOG_FETCHED_MESSAGE
|
||||||
|
if command_result:
|
||||||
|
response = command_result["response"]
|
||||||
|
error = command_result["error"]
|
||||||
|
if response:
|
||||||
|
log_text = response
|
||||||
|
elif error:
|
||||||
|
log_text = error
|
||||||
|
return log_text
|
||||||
|
|
||||||
|
def _get_copied_name(self, force_name=None):
|
||||||
|
# Original name is preserved when log is duplicated
|
||||||
|
return force_name or self.name
|
||||||
Reference in New Issue
Block a user