@ -1,20 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#################################################################################
|
|
||||||
#
|
|
||||||
# Copyright (c) 2017-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>)
|
|
||||||
# You should have received a copy of the License along with this program.
|
|
||||||
# If not, see <https://store.webkul.com/license.html/>
|
|
||||||
#################################################################################
|
|
||||||
|
|
||||||
from . import models
|
|
||||||
from . import wizards
|
|
||||||
from . import controllers
|
|
||||||
|
|
||||||
def pre_init_check(cr):
|
|
||||||
from odoo.service import common
|
|
||||||
from odoo.exceptions import UserError
|
|
||||||
version_info = common.exp_version()
|
|
||||||
server_serie =version_info.get('server_serie')
|
|
||||||
if server_serie != '18.0':
|
|
||||||
raise UserError('Module support Odoo series 18.0 found {}.'.format(server_serie))
|
|
||||||
return True
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#################################################################################
|
|
||||||
# Author : Webkul Software Pvt. Ltd. (<https://webkul.com/>)
|
|
||||||
# Copyright(c): 2015-Present Webkul Software Pvt. Ltd.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# This program is copyright property of the author mentioned above.
|
|
||||||
# You can`t redistribute it and/or modify it.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# You should have received a copy of the License along with this program.
|
|
||||||
# If not, see <https://store.webkul.com/license.html/>
|
|
||||||
#################################################################################
|
|
||||||
{
|
|
||||||
"name" : "Odoo Database Backup",
|
|
||||||
"summary" : """Module provide feature to admin to take backups of his instance's database and later download them.""",
|
|
||||||
"category" : "Extra Tools",
|
|
||||||
"version" : "1.0.0",
|
|
||||||
"author" : "Webkul Software Pvt. Ltd.",
|
|
||||||
"license" : "Other proprietary",
|
|
||||||
"website" : "https://webkul.com/blog/odoo-data-backup-how-to-create-and-restore-data-in-odoo/",
|
|
||||||
"description" : """Module provide feature to admin to take backups of his instance's database and later download them.""",
|
|
||||||
"live_test_url" : "http://odoodemo.webkul.com/demo_feedback?module=wk_backup_restore",
|
|
||||||
"depends" : [
|
|
||||||
'base',
|
|
||||||
'mail'
|
|
||||||
],
|
|
||||||
"data" : [
|
|
||||||
'security/ir.model.access.csv',
|
|
||||||
'wizards/backup_custom_message_wizard_view.xml',
|
|
||||||
'wizards/backup_deletion_confirmation_view.xml',
|
|
||||||
'views/backup_remote_server.xml',
|
|
||||||
'data/backup_process_sequence.xml',
|
|
||||||
'views/backup_process.xml',
|
|
||||||
'data/backup_ignite_crone.xml',
|
|
||||||
'views/menuitems.xml',
|
|
||||||
],
|
|
||||||
"images" : ['static/description/Banner.png'],
|
|
||||||
"application" : True,
|
|
||||||
"installable" : True,
|
|
||||||
"currency" : "USD",
|
|
||||||
"pre_init_hook" : "pre_init_check",
|
|
||||||
"external_dependencies": {'python': ['python-crontab', 'paramiko']},
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
from . import controllers
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#################################################################################
|
|
||||||
#
|
|
||||||
# Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>)
|
|
||||||
# See LICENSE file for full copyright and licensing details.
|
|
||||||
# License URL : <https://store.webkul.com/license.html/>
|
|
||||||
#
|
|
||||||
#################################################################################
|
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
import datetime
|
|
||||||
import pytz
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import json
|
|
||||||
|
|
||||||
import odoo
|
|
||||||
from odoo import http, _
|
|
||||||
from odoo.http import request
|
|
||||||
from odoo.exceptions import AccessError, UserError
|
|
||||||
from odoo.tools.misc import exec_pg_environ, find_pg_tool
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class BackupController(http.Controller):
|
|
||||||
|
|
||||||
|
|
||||||
@http.route('/backupfile/download', type='http', auth='user')
|
|
||||||
def file_download(self, **kwargs):
|
|
||||||
file_path = request.httprequest.args.get('path') # The actual file path
|
|
||||||
backup_location = request.httprequest.args.get('backup_location') or 'local'
|
|
||||||
_logger.info(f"=====backup_location========= {backup_location} ====== file_path ====== {file_path}")
|
|
||||||
try:
|
|
||||||
# Read the file and return it as a response
|
|
||||||
file_data = None
|
|
||||||
with open(file_path, 'rb') as file:
|
|
||||||
file_data = file.read()
|
|
||||||
|
|
||||||
# Set the response headers for file download
|
|
||||||
response = request.make_response(file_data)
|
|
||||||
response.headers['Content-Disposition'] = f"attachment; filename={file_path.split('/')[-1]}"
|
|
||||||
response.mimetype = 'application/octet-stream'
|
|
||||||
|
|
||||||
# Delete the remote backup file from Main Server
|
|
||||||
if backup_location == 'remote':
|
|
||||||
os.remove(file_path)
|
|
||||||
|
|
||||||
return response
|
|
||||||
except Exception as e:
|
|
||||||
_logger.info(f"======= Backup File Download Error ======= {e} ========")
|
|
||||||
raise UserError(e)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@http.route('/saas/database/backup', type='http', auth="none", methods=['POST'], csrf=False)
|
|
||||||
def db_backup(self, **kwargs):
|
|
||||||
master_pwd = kwargs.get('master_pwd')
|
|
||||||
dbname = kwargs.get('name')
|
|
||||||
backup_format = kwargs.get('backup_format') or 'zip'
|
|
||||||
response = None
|
|
||||||
user = request.env['res.users'].sudo().browse([2])
|
|
||||||
tz = pytz.timezone(user.tz) if user.tz else pytz.utc
|
|
||||||
time_now = pytz.utc.localize(datetime.datetime.now()).astimezone(tz)
|
|
||||||
ts = time_now.strftime("%m-%d-%Y-%H.%M.%S")
|
|
||||||
filename = "%s_%s.%s" % (dbname, ts, backup_format)
|
|
||||||
try:
|
|
||||||
odoo.service.db.check_super(master_pwd)
|
|
||||||
dump_stream = self.dump_db(dbname, None, backup_format)
|
|
||||||
response = request.make_response(dump_stream)
|
|
||||||
response.headers['Content-Disposition'] = f"attachment; filename={filename}"
|
|
||||||
response.mimetype = 'application/octet-stream'
|
|
||||||
except Exception as e:
|
|
||||||
error = "Database backup error: %s" % (str(e) or repr(e))
|
|
||||||
_logger.exception('Database.backup --- %r', error)
|
|
||||||
response = request.make_response(error)
|
|
||||||
response.mimetype = 'text/html'
|
|
||||||
|
|
||||||
response.headers['Backup-Filename'] = filename
|
|
||||||
response.headers['Backup-Time'] = time_now.strftime("%m-%d-%Y-%H:%M:%S")
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def dump_db_manifest(self, cr):
|
|
||||||
pg_version = "%d.%d" % divmod(cr._obj.connection.server_version / 100, 100)
|
|
||||||
cr.execute("SELECT name, latest_version FROM ir_module_module WHERE state = 'installed'")
|
|
||||||
modules = dict(cr.fetchall())
|
|
||||||
manifest = {
|
|
||||||
'odoo_dump': '1',
|
|
||||||
'db_name': cr.dbname,
|
|
||||||
'version': odoo.release.version,
|
|
||||||
'version_info': odoo.release.version_info,
|
|
||||||
'major_version': odoo.release.major_version,
|
|
||||||
'pg_version': pg_version,
|
|
||||||
'modules': modules,
|
|
||||||
}
|
|
||||||
return manifest
|
|
||||||
|
|
||||||
def dump_db(self, db_name, stream, backup_format='zip'):
|
|
||||||
"""Dump database `db` into file-like object `stream` if stream is None
|
|
||||||
return a file object with the dump """
|
|
||||||
|
|
||||||
_logger.info('DUMP DB: %s format %s', db_name, backup_format)
|
|
||||||
|
|
||||||
cmd = [find_pg_tool('pg_dump'), '--no-owner', db_name]
|
|
||||||
env = exec_pg_environ()
|
|
||||||
|
|
||||||
if backup_format == 'zip':
|
|
||||||
with tempfile.TemporaryDirectory() as dump_dir:
|
|
||||||
filestore = odoo.tools.config.filestore(db_name)
|
|
||||||
if os.path.exists(filestore):
|
|
||||||
shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
|
|
||||||
with open(os.path.join(dump_dir, 'manifest.json'), 'w') as fh:
|
|
||||||
db = odoo.sql_db.db_connect(db_name)
|
|
||||||
with db.cursor() as cr:
|
|
||||||
json.dump(self.dump_db_manifest(cr), fh, indent=4)
|
|
||||||
cmd.insert(-1, '--file=' + os.path.join(dump_dir, 'dump.sql'))
|
|
||||||
subprocess.run(cmd, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=True)
|
|
||||||
if stream:
|
|
||||||
odoo.tools.osutil.zip_dir(dump_dir, stream, include_dir=False, fnct_sort=lambda file_name: file_name != 'dump.sql')
|
|
||||||
else:
|
|
||||||
t=tempfile.TemporaryFile()
|
|
||||||
odoo.tools.osutil.zip_dir(dump_dir, t, include_dir=False, fnct_sort=lambda file_name: file_name != 'dump.sql')
|
|
||||||
t.seek(0)
|
|
||||||
return t
|
|
||||||
else:
|
|
||||||
cmd.insert(-1, '--format=c')
|
|
||||||
stdout = subprocess.Popen(cmd, env=env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE).stdout
|
|
||||||
if stream:
|
|
||||||
shutil.copyfileobj(stdout, stream)
|
|
||||||
else:
|
|
||||||
return stdout
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<!-- Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>) -->
|
|
||||||
<!-- See LICENSE file for full copyright and licensing details. -->
|
|
||||||
<!-- License URL : <https://store.webkul.com/license.html/> -->
|
|
||||||
|
|
||||||
<odoo>
|
|
||||||
<data>
|
|
||||||
<record id="backup_process_ignite_crone" model="ir.cron">
|
|
||||||
<field name="name">Backup Process Ignite Cron</field>
|
|
||||||
<field name="model_id" ref="model_backup_process"/>
|
|
||||||
<field name="state">code</field>
|
|
||||||
<field name="code">model.ignite_backup_server_crone()</field>
|
|
||||||
<field name="interval_number">1</field>
|
|
||||||
<field name="interval_type">hours</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="backup_file_remove_cron" model="ir.cron">
|
|
||||||
<field name="name">Backup Process File Remove Cron</field>
|
|
||||||
<field name="model_id" ref="model_backup_process"/>
|
|
||||||
<field name="state">code</field>
|
|
||||||
<field name="code">model.remove_old_backups()</field>
|
|
||||||
<field name="interval_number">1</field>
|
|
||||||
<field name="interval_type">days</field>
|
|
||||||
</record>
|
|
||||||
</data>
|
|
||||||
</odoo>
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>) -->
|
|
||||||
<!-- See LICENSE file for full copyright and licensing details. -->
|
|
||||||
<!-- License URL : <https://store.webkul.com/license.html/> -->
|
|
||||||
|
|
||||||
<odoo>
|
|
||||||
<data noupdate='1'>
|
|
||||||
<record id="back_process_sequence" model="ir.sequence">
|
|
||||||
<field name="name">Backup Process Name</field>
|
|
||||||
<field name="code">backup.process</field>
|
|
||||||
<field name="prefix">PROCESS</field>
|
|
||||||
<field name="padding">3</field>
|
|
||||||
</record>
|
|
||||||
</data>
|
|
||||||
</odoo>
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#################################################################################
|
|
||||||
#
|
|
||||||
# Copyright (c) 2017-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>)
|
|
||||||
# You should have received a copy of the License along with this program.
|
|
||||||
# If not, see <https://store.webkul.com/license.html/>
|
|
||||||
#################################################################################
|
|
||||||
|
|
||||||
from . import backup_process
|
|
||||||
from . import backup_process_details
|
|
||||||
from . import backup_remote_server
|
|
||||||
@ -1,441 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#################################################################################
|
|
||||||
#
|
|
||||||
# Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>)
|
|
||||||
# See LICENSE file for full copyright and licensing details.
|
|
||||||
# License URL : <https://store.webkul.com/license.html/>
|
|
||||||
#
|
|
||||||
#################################################################################
|
|
||||||
|
|
||||||
from odoo import fields, api, models, tools
|
|
||||||
from odoo.exceptions import UserError
|
|
||||||
from odoo.tools.config import config
|
|
||||||
|
|
||||||
from odoo.addons.wk_backup_restore.models.lib import manage_backup_crons, saas_client_backup
|
|
||||||
from datetime import datetime
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
import paramiko
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
LOCATION = [
|
|
||||||
('local', 'Local'),
|
|
||||||
('remote', 'Remote Server'),
|
|
||||||
]
|
|
||||||
|
|
||||||
CYCLE = [
|
|
||||||
('half_day', 'Twice a day'),
|
|
||||||
('daily', 'Daily'),
|
|
||||||
('weekly', 'Weekly'),
|
|
||||||
('monthly', 'Monthly'),
|
|
||||||
('yearly', 'Yearly'),
|
|
||||||
]
|
|
||||||
|
|
||||||
STATE = [
|
|
||||||
('draft', 'Draft'),
|
|
||||||
('confirm', 'Confirm'),
|
|
||||||
('running', 'Running'),
|
|
||||||
('cancel', 'Cancel')
|
|
||||||
]
|
|
||||||
|
|
||||||
class BackupProcess(models.Model):
|
|
||||||
_name = "backup.process"
|
|
||||||
_description="Backup Process"
|
|
||||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
|
||||||
_order = "id desc"
|
|
||||||
|
|
||||||
def _default_db_name(self):
|
|
||||||
return self._cr.dbname
|
|
||||||
|
|
||||||
name = fields.Char(string="Process Name", default='New', help="Display name for the backup process.")
|
|
||||||
frequency = fields.Integer(string="Frequency", default=1, help="Frequency for backuping the database.")
|
|
||||||
frequency_cycle = fields.Selection(selection=CYCLE, string="Frequency Cycle", help="Select frequency cycle of Database Backup.", tracking=True)
|
|
||||||
storage_path = fields.Char(string="Storage Path", help="The directory path where the backup files will be stored on server.", tracking=True)
|
|
||||||
backup_location = fields.Selection(selection=LOCATION, string="Backup Location", default="local", help="Server where the backup file will be stored.")
|
|
||||||
retention = fields.Integer(string="Backup Retention Count", default=7, help="Count of recent backups that will be retained after dropping old backups on server.")
|
|
||||||
# start_time = fields.Datetime(string="Backup Starting Time", help="Time from when the database backup can be started.")
|
|
||||||
db_name = fields.Char(string="Database Name", default=_default_db_name, help="Database used for the creating the backup.", tracking=True)
|
|
||||||
backup_starting_time = fields.Datetime(string="Backup Starting Time", help="Set Database Backup start date and time.")
|
|
||||||
state = fields.Selection(selection=STATE, default='draft', help="Current state of the backup process.")
|
|
||||||
update_requested = fields.Boolean(string="Update Requested", default=False, help="Checked if any backup is requested in the database backup.")
|
|
||||||
# master_pass = fields.Char(string="Master Password")
|
|
||||||
backup_details_ids = fields.One2many(comodel_name="backup.process.detail", inverse_name="backup_process_id", string="Backup Details", help="Details of the database backups that has been created.")
|
|
||||||
backup_format = fields.Selection([('zip', 'zip (includes filestore)'), ('dump', 'pg_dump custom format (without filestore)')], string="Backup Format", default="zip", help="Select the file format of the data backup file.", tracking=True)
|
|
||||||
enable_retention = fields.Boolean(string="Drop Old Backups", default=False, help="Check if you want to drop old backups stored on the server.")
|
|
||||||
remote_server_id = fields.Many2one(comodel_name="backup.remote.server", string="Backup Remote Server", domain=[('state', '=', 'validated')])
|
|
||||||
|
|
||||||
|
|
||||||
@api.onchange('frequency_cycle')
|
|
||||||
def change_frequency_value(self):
|
|
||||||
"""
|
|
||||||
Method to change the value of frequency for Twice a day
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.frequency_cycle == 'half_day':
|
|
||||||
self.frequency = 2
|
|
||||||
else:
|
|
||||||
self.frequency = 1
|
|
||||||
|
|
||||||
@api.onchange('backup_location')
|
|
||||||
def change_backup_location(self):
|
|
||||||
"""
|
|
||||||
Method to check the validated remote servers
|
|
||||||
"""
|
|
||||||
if self.backup_location == 'remote':
|
|
||||||
backup_servers = self.env['backup.remote.server'].sudo().search([('state', '=', 'validated')])
|
|
||||||
if not backup_servers:
|
|
||||||
raise UserError("No validated remote servers found. Please configure a remote server first!!")
|
|
||||||
self.remote_server_id = None
|
|
||||||
|
|
||||||
|
|
||||||
@api.constrains('retention')
|
|
||||||
def check_retention_value(self):
|
|
||||||
"""
|
|
||||||
Method to check the value of retention field
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.enable_retention:
|
|
||||||
if self.retention < 1:
|
|
||||||
raise UserError("Backup Retention Count should be at least 1.")
|
|
||||||
|
|
||||||
def call_backup_script(self, master_pass=None, port_number=None, url=None, db_user=None, db_password=None, kwargs={}):
|
|
||||||
"""
|
|
||||||
Called by create_backup_request method, defined below
|
|
||||||
Method to call script to create a cron for manage backups,
|
|
||||||
calling script require few arguments, some are passed in this method same are prepared below
|
|
||||||
"""
|
|
||||||
|
|
||||||
db_user = db_user or config.get('db_user')
|
|
||||||
db_password = db_password or config.get('db_password')
|
|
||||||
module_path = tools.misc.file_path('wk_backup_restore')
|
|
||||||
module_path = module_path + '/models/lib/saas_client_backup.py'
|
|
||||||
backup_format = self.backup_format or "zip"
|
|
||||||
backup_location = self.backup_location
|
|
||||||
res = None
|
|
||||||
if hasattr(self,'_call_%s_backup_script'%backup_location):## if you want to update dictionary then you can define this function _call_{backup_location}_backup_script
|
|
||||||
res = getattr(self,'_call_%s_backup_script'%backup_location)(master_pass,port_number,url,db_user,db_password,backup_format, kwargs)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def _call_local_backup_script(self, master_pass=None, port_number=None, url=None, db_user=None, db_password=None, backup_format="zip", kwargs={}):
|
|
||||||
"""
|
|
||||||
Called by call_backup_script method, defined above
|
|
||||||
Method to call script to create a cron for manage backups,
|
|
||||||
calling script require few arguments, some are passed in this method same are prepared below
|
|
||||||
"""
|
|
||||||
res = None
|
|
||||||
if self.backup_location == "local":
|
|
||||||
module_path = tools.misc.file_path('wk_backup_restore')
|
|
||||||
module_path = module_path + '/models/lib/saas_client_backup.py'
|
|
||||||
res = manage_backup_crons.add_cron(master_pass=master_pass, main_db=self._cr.dbname, db_name=self.db_name, backup_location=self.backup_location, frequency=self.frequency, frequency_cycle=self.frequency_cycle, storage_path=self.storage_path, url=url, db_user=db_user, db_password=db_password, process_id=self.id, module_path=module_path, backup_format=backup_format, backup_starting_time=self.backup_starting_time, kwargs=kwargs)
|
|
||||||
|
|
||||||
if res.get('success'):
|
|
||||||
self.state = 'running'
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def _call_remote_backup_script(self, master_pass=None, port_number=None, url=None, db_user=None, db_password=None, backup_format="zip", kwargs=dict()):
|
|
||||||
"""
|
|
||||||
Called by call_backup_script method, defined above
|
|
||||||
Method to call script to create a cron for manage remote database backups,
|
|
||||||
calling script require few arguments, some are passed in this method same are prepared below
|
|
||||||
"""
|
|
||||||
res = None
|
|
||||||
if self.backup_location == "remote":
|
|
||||||
module_path = tools.misc.file_path('wk_backup_restore')
|
|
||||||
module_path = module_path + '/models/lib/saas_client_backup.py'
|
|
||||||
kwargs.update(
|
|
||||||
rhost = self.remote_server_id.sftp_host,
|
|
||||||
rport = self.remote_server_id.sftp_port,
|
|
||||||
ruser = self.remote_server_id.sftp_user,
|
|
||||||
rpass = self.remote_server_id.sftp_password,
|
|
||||||
temp_bkp_path = self.remote_server_id.temp_backup_dir,
|
|
||||||
)
|
|
||||||
res = manage_backup_crons.add_cron(master_pass=master_pass, main_db=self._cr.dbname, db_name=self.db_name, backup_location=self.backup_location, frequency=self.frequency, frequency_cycle=self.frequency_cycle, storage_path=self.storage_path, url=url, db_user=db_user, db_password=db_password, process_id=self.id, module_path=module_path, backup_format=backup_format,backup_starting_time=self.backup_starting_time, kwargs=kwargs)
|
|
||||||
|
|
||||||
if res.get('success'):
|
|
||||||
self.state = 'running'
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def update_backup_request(self):
|
|
||||||
"""
|
|
||||||
Method called from Cron,
|
|
||||||
Method called the script to update already created cron.
|
|
||||||
"""
|
|
||||||
|
|
||||||
res = manage_backup_crons.update_cron(db_name=self.db_name, process_id=str(self.id), frequency=self.frequency, frequency_cycle=self.frequency_cycle)
|
|
||||||
if res.get('success'):
|
|
||||||
self.update_requested = False
|
|
||||||
|
|
||||||
def create_backup_request(self):
|
|
||||||
"""
|
|
||||||
Called from the crone:
|
|
||||||
Method called the method to which call the crone script
|
|
||||||
Add 'master_passwd' in odoo conf file
|
|
||||||
"""
|
|
||||||
|
|
||||||
master_pass = config.get('master_passwd')
|
|
||||||
if master_pass:
|
|
||||||
url = "localhost:"+str(config.get('http_port', '8069'))
|
|
||||||
return self.call_backup_script(master_pass=master_pass, url=url)
|
|
||||||
else:
|
|
||||||
_logger.info("------Error While Creating Backup Request--Master Password(master_passwd) is not set in conf file!!----------------")
|
|
||||||
|
|
||||||
def remove_attached_cron(self):
|
|
||||||
"""
|
|
||||||
Called by the button over backup process page,
|
|
||||||
To cancel the Backup Process record and to call the delete cron script
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.state == 'running':
|
|
||||||
res = manage_backup_crons.remove_cron(db_name=self.db_name, process_id=str(self.id), frequency=self.frequency, frequency_cycle=self.frequency_cycle)
|
|
||||||
else:
|
|
||||||
res = dict(
|
|
||||||
success = True
|
|
||||||
)
|
|
||||||
if res.get('success'):
|
|
||||||
self.state = 'cancel'
|
|
||||||
return res
|
|
||||||
|
|
||||||
@api.model
|
|
||||||
def ignite_backup_server_crone(self):
|
|
||||||
"""
|
|
||||||
Crone method to call functions either to create a new cron, or to update a existing one
|
|
||||||
"""
|
|
||||||
|
|
||||||
current_time = datetime.now()
|
|
||||||
processes = self.env['backup.process'].sudo().search([('backup_starting_time', '<=', current_time), ('state', '=', 'confirm')])
|
|
||||||
for process in processes:
|
|
||||||
process.create_backup_request()
|
|
||||||
upt_processes = self.env['backup.process'].sudo().search([('backup_starting_time', '<=', current_time), ('state', '=', 'running'), ('update_requested', '=', True)])
|
|
||||||
for upt_process in upt_processes:
|
|
||||||
if upt_process.update_requested:
|
|
||||||
upt_process.update_backup_request()
|
|
||||||
|
|
||||||
@api.model_create_multi
|
|
||||||
def create(self, vals_list):
|
|
||||||
for vals in vals_list:
|
|
||||||
vals['name'] = self.env['ir.sequence'].next_by_code('backup.process')
|
|
||||||
res = super(BackupProcess, self).create(vals)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def write(self, vals):
|
|
||||||
if self.state not in ['draft','cancel','confirm'] and self.backup_starting_time <= datetime.now() and not vals.get('update_requested') == False:
|
|
||||||
vals['update_requested'] = True
|
|
||||||
return super(BackupProcess, self).write(vals)
|
|
||||||
|
|
||||||
|
|
||||||
def unlink(self):
|
|
||||||
if self.state not in ['draft','cancel','confirm']:
|
|
||||||
raise UserError("Not allowed")
|
|
||||||
return super(BackupProcess, self).unlink()
|
|
||||||
|
|
||||||
|
|
||||||
def confirm_process(self):
|
|
||||||
"""
|
|
||||||
Called by the Confirm button over the backup process record
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.state == 'draft':
|
|
||||||
# Raise error if master password is not set in odoo conf file
|
|
||||||
if not config.get('master_passwd', False):
|
|
||||||
raise UserError("Master password parameter(master_passwd) not set in Odoo conf file!!")
|
|
||||||
|
|
||||||
# Creating the backup log file if doesn't exists
|
|
||||||
if not os.path.exists(manage_backup_crons.LOG_FILE_PATH):
|
|
||||||
_logger.info("========== Creating Backup Log File ==========")
|
|
||||||
fp = open(manage_backup_crons.LOG_FILE_PATH, 'x')
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
if self.backup_location == 'remote':
|
|
||||||
self.validate_remote_backup()
|
|
||||||
self.state ="confirm"
|
|
||||||
|
|
||||||
def cancel_process(self):
|
|
||||||
"""
|
|
||||||
Called by the Cancel button over the backup process record
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.state in ['draft','confirm']:
|
|
||||||
self.state ="cancel"
|
|
||||||
|
|
||||||
@api.model
|
|
||||||
def remove_old_backups(self):
|
|
||||||
"""
|
|
||||||
Cron method to call functions to remove the backup file of the backup processes
|
|
||||||
"""
|
|
||||||
|
|
||||||
processes = self.env['backup.process'].sudo().search([('state', '=', 'running'),('enable_retention', '=', True)])
|
|
||||||
for rec in processes:
|
|
||||||
details_ids = rec.backup_details_ids.filtered(lambda d: d.status == "Success").sorted(key=lambda p:p.id)
|
|
||||||
if details_ids:
|
|
||||||
end_index = len(details_ids) - rec.retention
|
|
||||||
if end_index>0:
|
|
||||||
updated_details_ids = details_ids[:end_index]
|
|
||||||
rec.remove_backup_files(updated_details_ids)
|
|
||||||
|
|
||||||
def remove_backup_files(self, bkp_details_ids):
|
|
||||||
"""
|
|
||||||
Method to check if the backup file exist, and if exist then remove that backup file.
|
|
||||||
Also, updates the status and the message of the backup process details.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
bkp_details_ids ([object]): [all the backup process ids whose backup file needs to be deleted.]
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
msg = None
|
|
||||||
for bkp in bkp_details_ids:
|
|
||||||
backup_location = self.backup_location
|
|
||||||
if hasattr(self,'_remove_%s_backup_files'%backup_location):## if you want to update dictionary then you can define this function _remove_{backup_location}_backup_files
|
|
||||||
msg = getattr(self,'_remove_%s_backup_files'%backup_location)(bkp)
|
|
||||||
_logger.info("---- %r -- ", msg)
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
_logger.error("Database backup remove error: " + str(e))
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _remove_local_backup_files(self, bkp_details_id):
|
|
||||||
"""
|
|
||||||
Method to check if the backup file exist on the main server,
|
|
||||||
and if exist then remove that backup file.
|
|
||||||
"""
|
|
||||||
msg = None
|
|
||||||
if os.path.exists(bkp_details_id.url):
|
|
||||||
res = os.remove(bkp_details_id.url)
|
|
||||||
msg = 'Database backup dropped successfully at ' + datetime.now().strftime("%m-%d-%Y-%H:%M:%S") + " after retention."
|
|
||||||
bkp_details_id.message = msg
|
|
||||||
bkp_details_id.status = "Dropped"
|
|
||||||
else:
|
|
||||||
msg = "Database backup file doesn't exists."
|
|
||||||
bkp_details_id.message = msg
|
|
||||||
bkp_details_id.status = "Failure"
|
|
||||||
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
def _remove_remote_backup_files(self, bkp_details_id):
|
|
||||||
"""
|
|
||||||
Method to check if the backup file exist on the remote backup server,
|
|
||||||
and if exist then remove that backup file.
|
|
||||||
"""
|
|
||||||
msg = None
|
|
||||||
ssh_obj = self.login_remote()
|
|
||||||
if self.check_remote_backup_existance(ssh_obj, bkp_details_id.url):
|
|
||||||
sftp = ssh_obj.open_sftp()
|
|
||||||
sftp.remove(bkp_details_id.url)
|
|
||||||
sftp.close()
|
|
||||||
msg = 'Database backup dropped successfully at ' + datetime.now().strftime("%m-%d-%Y-%H:%M:%S") + " after retention from remote server."
|
|
||||||
bkp_details_id.message = msg
|
|
||||||
bkp_details_id.status = "Dropped"
|
|
||||||
else:
|
|
||||||
msg = "Database backup file doesn't exists on remote server."
|
|
||||||
bkp_details_id.message = msg
|
|
||||||
bkp_details_id.status = "Failure"
|
|
||||||
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def login_remote(self):
|
|
||||||
"""
|
|
||||||
Method to login to the remote backup server using SSH.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
[Object]: [Returns SSh object if connected successfully to the remote server.]
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
ssh_obj = paramiko.SSHClient()
|
|
||||||
ssh_obj.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh_obj.connect(hostname=self.remote_server_id.sftp_host, username=self.remote_server_id.sftp_user, password=self.remote_server_id.sftp_password, port=self.remote_server_id.sftp_port)
|
|
||||||
return ssh_obj
|
|
||||||
except Exception as e:
|
|
||||||
_logger.info(f"==== Exception while connecting to remote server ==== {e} ===")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_host_connection(self):
|
|
||||||
if self.remote_server_id:
|
|
||||||
response = self.validate_remote_backup()
|
|
||||||
if response:
|
|
||||||
message = self.env['backup.custom.message.wizard'].create({'message':"Connection successful!"})
|
|
||||||
action = self.env.ref('wk_backup_restore.action_backup_custom_message_wizard').read()[0]
|
|
||||||
action['res_id'] = message.id
|
|
||||||
return action
|
|
||||||
|
|
||||||
|
|
||||||
def validate_remote_backup(self):
|
|
||||||
"""
|
|
||||||
Method to validate the remote backup process.
|
|
||||||
It checks the connection to remote server along with the existance of backup
|
|
||||||
storage path on the remote server.
|
|
||||||
"""
|
|
||||||
ssh_obj = self.login_remote()
|
|
||||||
if ssh_obj:
|
|
||||||
backup_dir = self.storage_path
|
|
||||||
cmd = "ls %s"%(backup_dir)
|
|
||||||
check_path = self.execute_on_remote_shell(ssh_obj,cmd)
|
|
||||||
if check_path and not check_path.get('status'):
|
|
||||||
raise UserError(f"Storage path doesn't exist on remote server. Please create the mentioned backup path on the remote server. Error: {check_path.get('message')}")
|
|
||||||
|
|
||||||
cmd = f"touch {backup_dir}/test.txt"
|
|
||||||
create_file = self.execute_on_remote_shell(ssh_obj,cmd)
|
|
||||||
if create_file and not create_file.get('status'):
|
|
||||||
raise UserError(f"The mentioned ssh user doesn't have rights to create file. Please provide required permissions on the default backup path. Error: {create_file.get('message')}")
|
|
||||||
else:
|
|
||||||
cmd = f"rm {backup_dir}/test.txt"
|
|
||||||
delete_file = self.execute_on_remote_shell(ssh_obj,cmd)
|
|
||||||
if delete_file and delete_file.get('status'):
|
|
||||||
_logger.info("======== Backup Directory Permissions Checked Successfully =========")
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise UserError("Couldn't connect to the remote server.")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def check_remote_backup_existance(self, ssh_obj, bkp_path):
|
|
||||||
"""
|
|
||||||
Method to check the existance of the backup file on the remote server.
|
|
||||||
Args:
|
|
||||||
ssh_obj ([object]): [SSH Object of the remote server.]
|
|
||||||
bkp_path ([object]): [Path of the backup file on the remote server.]
|
|
||||||
"""
|
|
||||||
cmd = "ls -f %s"%(bkp_path)
|
|
||||||
check_path = self.execute_on_remote_shell(ssh_obj,cmd)
|
|
||||||
if check_path and not check_path.get('status'):
|
|
||||||
_logger.error(f"-----------Database Backup file '{bkp_path}' doesn't exist on remote server.--------")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def execute_on_remote_shell(self, ssh_obj,command):
|
|
||||||
"""
|
|
||||||
Method to execute the command on the remote server.
|
|
||||||
"""
|
|
||||||
_logger.info(command)
|
|
||||||
response = dict()
|
|
||||||
try:
|
|
||||||
ssh_stdin, ssh_stdout, ssh_stderr = ssh_obj.exec_command(command)
|
|
||||||
# _logger.info(ssh_stdout.readlines())
|
|
||||||
res = ssh_stdout.readlines()
|
|
||||||
_logger.info("execute_on_remote_shell res: %r", res)
|
|
||||||
_logger.info("execute_on_remote_shell err: ")
|
|
||||||
err = ssh_stderr.readlines()
|
|
||||||
_logger.info(err)
|
|
||||||
if err:
|
|
||||||
response['status'] = False
|
|
||||||
response['message'] = err
|
|
||||||
return response
|
|
||||||
response['status'] = True
|
|
||||||
response['result'] = res
|
|
||||||
return response
|
|
||||||
except Exception as e:
|
|
||||||
_logger.info("+++ERROR++",command)
|
|
||||||
_logger.info("++++++++++ERROR++++",e)
|
|
||||||
response['status'] = False
|
|
||||||
response['message'] = e
|
|
||||||
return response
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#################################################################################
|
|
||||||
#
|
|
||||||
# Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>)
|
|
||||||
# See LICENSE file for full copyright and licensing details.
|
|
||||||
# License URL : <https://store.webkul.com/license.html/>
|
|
||||||
#
|
|
||||||
#################################################################################
|
|
||||||
|
|
||||||
from odoo import models, fields, api, _
|
|
||||||
from odoo.exceptions import UserError
|
|
||||||
from . lib import check_connectivity
|
|
||||||
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import base64
|
|
||||||
import os
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessBackupDetail(models.Model):
|
|
||||||
_name = 'backup.process.detail'
|
|
||||||
_description = "Backup Process Details"
|
|
||||||
_order = "id desc"
|
|
||||||
|
|
||||||
name = fields.Char(string="Name")
|
|
||||||
file_name = fields.Char(string="File Name")
|
|
||||||
backup_process_id = fields.Many2one(string="Backup Process Id", comodel_name="backup.process")
|
|
||||||
file_path = fields.Char(string="File Path")
|
|
||||||
url = fields.Char(string="Url")
|
|
||||||
backup_date_time = fields.Datetime(string="Backup Time")
|
|
||||||
status = fields.Char(string="Status")
|
|
||||||
message = fields.Char(string="Message")
|
|
||||||
backup_location = fields.Selection(string="Backup Location", related="backup_process_id.backup_location", help="Server where the backup file will be stored.")
|
|
||||||
|
|
||||||
def download_db_file(self):
|
|
||||||
"""
|
|
||||||
Call by the download button over every backup detail record.
|
|
||||||
Method download the zip file of backup,
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
backup_file_path = None
|
|
||||||
download_url = None
|
|
||||||
if self.backup_location == 'local':
|
|
||||||
backup_file_path = self.url
|
|
||||||
download_url = f"/backupfile/download?path={backup_file_path}&backup_location=local"
|
|
||||||
else:
|
|
||||||
backup_copy_status = self.get_remote_backup_file()
|
|
||||||
if backup_copy_status:
|
|
||||||
backup_file_path = self.backup_process_id.remote_server_id.temp_backup_dir+"/"+self.file_name
|
|
||||||
download_url = f"/backupfile/download?path={backup_file_path}&backup_location=remote"
|
|
||||||
else:
|
|
||||||
raise UserError("Cannot download backup file from remote server. Follow logs for more details.")
|
|
||||||
|
|
||||||
if self.status == "Success" and os.path.exists(backup_file_path):
|
|
||||||
return {
|
|
||||||
'type': 'ir.actions.act_url',
|
|
||||||
'url': download_url,
|
|
||||||
'target': 'new',
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise UserError("Backup doesn't exists.")
|
|
||||||
except Exception as e:
|
|
||||||
raise UserError(f"Error Occured: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def get_remote_backup_file(self):
|
|
||||||
"""
|
|
||||||
Method to copy the backup file from the remote server to the main server
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
[Boolean]: True in case file is successfully copied or False
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
|
|
||||||
host_server = self.backup_process_id.remote_server_id.get_server_details()
|
|
||||||
temp_path = self.backup_process_id.remote_server_id.temp_backup_dir
|
|
||||||
response = check_connectivity.ishostaccessible(host_server)
|
|
||||||
|
|
||||||
if not response.get('status'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
ssh_obj = response.get('result')
|
|
||||||
sftp = ssh_obj.open_sftp()
|
|
||||||
sftp.get(self.url, temp_path+'/'+self.file_name)
|
|
||||||
sftp.close()
|
|
||||||
_logger.info("======== Backup file successfully copied to the local server. ===========")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
_logger.info(f"======= Exception while copying the backup file from the remote server ======= {e} ")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def unlink_confirmation(self):
|
|
||||||
for rec in self:
|
|
||||||
if rec.status=="Success":
|
|
||||||
msg = """ <span class="text-warning"><strong>Warning:</strong> After Deleting this record you will no longer be able to download the backup file associated with this record. However, after deletion the backup will still remain on server.
|
|
||||||
Are you sure you want to delete this backup record?<span>
|
|
||||||
"""
|
|
||||||
partial_id = self.env['backup.deletion.confirmation'].create({'backup_id': rec.id, 'message': msg})
|
|
||||||
return {
|
|
||||||
'type': 'ir.actions.act_window',
|
|
||||||
'name': 'Deletion Confirmation',
|
|
||||||
'view_mode': 'form',
|
|
||||||
'res_model': 'backup.deletion.confirmation',
|
|
||||||
'res_id': partial_id.id,
|
|
||||||
'target': 'new',
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
rec.unlink()
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#################################################################################
|
|
||||||
#
|
|
||||||
# Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>)
|
|
||||||
# See LICENSE file for full copyright and licensing details.
|
|
||||||
# License URL : <https://store.webkul.com/license.html/>
|
|
||||||
#
|
|
||||||
#################################################################################
|
|
||||||
|
|
||||||
from odoo import models, fields, api, _
|
|
||||||
from odoo.exceptions import UserError
|
|
||||||
from . lib import check_connectivity
|
|
||||||
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import base64
|
|
||||||
import os
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
STATE = [
|
|
||||||
('draft', "Draft"),
|
|
||||||
('validated', 'Validated'),
|
|
||||||
]
|
|
||||||
|
|
||||||
class BackupRemoteServer(models.Model):
|
|
||||||
_name = 'backup.remote.server'
|
|
||||||
_description="Backup Remote Server"
|
|
||||||
|
|
||||||
name = fields.Char(string="Name", help="Name of the backup remote server")
|
|
||||||
sftp_host = fields.Char(string="Remote SFTP Host", help="SFTP host for establishing connection to the backup remote server")
|
|
||||||
sftp_port = fields.Char(string="Remote SFTP Port", default="22", help="SFTP port for establishing connection to the backup remote server")
|
|
||||||
sftp_user = fields.Char(string="SFTP User", help="SFTP user for establishing connection to the backup remote server")
|
|
||||||
sftp_password = fields.Char(string="SFTP Password", help="SFTP password for establishing connection to the backup remote server")
|
|
||||||
|
|
||||||
state = fields.Selection(selection=STATE, string="State", default="draft", help="State of the backup remote server")
|
|
||||||
active = fields.Boolean(string="Active", default=True)
|
|
||||||
temp_backup_dir = fields.Char(string="Temporary Backup Directory", help="The temporary backup path where the backups are stored before moving to the remote server. The temporary backup directory must be present on the main server along with the appropriate permissions.")
|
|
||||||
def_backup_dir = fields.Char(string="Default Remote Backup Directory", help="The default directory path on the remote server where the backups of the saas client instances will be stored. The directory must have appropriate permissions.")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_host_connection(self):
|
|
||||||
"""
|
|
||||||
Method to check Host connection: called by the button 'Test Connection'
|
|
||||||
"""
|
|
||||||
|
|
||||||
for obj in self:
|
|
||||||
response = obj.check_host_connected_call()
|
|
||||||
if response.get('status'):
|
|
||||||
message = self.env['backup.custom.message.wizard'].create({'message':"Connection successful!"})
|
|
||||||
action = self.env.ref('wk_backup_restore.action_backup_custom_message_wizard').read()[0]
|
|
||||||
action['res_id'] = message.id
|
|
||||||
return action
|
|
||||||
else:
|
|
||||||
raise UserError(response.get('message'))
|
|
||||||
|
|
||||||
|
|
||||||
def check_host_connected_call(self):
|
|
||||||
"""
|
|
||||||
Method to call the script to check host connectivity,
|
|
||||||
return response dict as per the output.
|
|
||||||
Called from 'test_host_connection' and 'set_validated'
|
|
||||||
"""
|
|
||||||
response = dict(
|
|
||||||
status=True,
|
|
||||||
message='Success'
|
|
||||||
)
|
|
||||||
host_server = self.get_server_details()
|
|
||||||
try:
|
|
||||||
response = check_connectivity.ishostaccessible(host_server)
|
|
||||||
if response and response.get('status'):
|
|
||||||
_logger.info("======= Remote Server Connection Successful ======")
|
|
||||||
ssh_obj = response.get('result')
|
|
||||||
backup_dir = self.def_backup_dir
|
|
||||||
cmd = "ls %s"%(backup_dir)
|
|
||||||
check_path = self.execute_on_remote_shell(ssh_obj,cmd)
|
|
||||||
if check_path and not check_path.get('status'):
|
|
||||||
raise UserError(f"Storage path doesn't exist on remote server. Please create the mentioned backup path on the remote server. Error: {check_path.get('message')}")
|
|
||||||
|
|
||||||
cmd = f"touch {backup_dir}/test.txt"
|
|
||||||
create_file = self.execute_on_remote_shell(ssh_obj,cmd)
|
|
||||||
if create_file and not create_file.get('status'):
|
|
||||||
raise UserError(f"The mentioned ssh user doesn't have rights to create file. Please provide required permissions on the default backup path. Error: {create_file.get('message')}")
|
|
||||||
else:
|
|
||||||
cmd = f"rm {backup_dir}/test.txt"
|
|
||||||
delete_file = self.execute_on_remote_shell(ssh_obj,cmd)
|
|
||||||
if delete_file and delete_file.get('status'):
|
|
||||||
_logger.info("======== Backup Directory Permissions Checked Successfully =========")
|
|
||||||
except Exception as e:
|
|
||||||
_logger.info(f"------ EXCEPTION WHILE TESTING THE REMOTE SERVER CONNECTION ---- {e} ------")
|
|
||||||
response['status'] = False
|
|
||||||
response['message'] = e
|
|
||||||
return response
|
|
||||||
|
|
||||||
@api.model
|
|
||||||
def get_server_details(self):
|
|
||||||
"""
|
|
||||||
Method created to return value of the host server as dict,
|
|
||||||
Called from check_host_connected_call method in the complete process
|
|
||||||
"""
|
|
||||||
host_server = dict(
|
|
||||||
host=self.sftp_host,
|
|
||||||
port=self.sftp_port,
|
|
||||||
user=self.sftp_user,
|
|
||||||
password=self.sftp_password,
|
|
||||||
)
|
|
||||||
return host_server
|
|
||||||
|
|
||||||
|
|
||||||
def set_validated(self):
|
|
||||||
for obj in self:
|
|
||||||
response = obj.check_host_connected_call()
|
|
||||||
if response.get('status'):
|
|
||||||
obj.state = 'validated'
|
|
||||||
else:
|
|
||||||
raise UserError(response.get('message'))
|
|
||||||
|
|
||||||
def reset_to_draft(self):
|
|
||||||
for obj in self:
|
|
||||||
bkp_processes = self.env['backup.process'].search([('remote_server_id', '=', obj.id), ('backup_location', '=', 'remote'), ('state', 'in', ['confirm', 'running'])])
|
|
||||||
if bkp_processes:
|
|
||||||
raise UserError("This Remote Server has some active Backup Process(es)!")
|
|
||||||
obj.state = 'draft'
|
|
||||||
|
|
||||||
def execute_on_remote_shell(self, ssh_obj,command):
|
|
||||||
"""
|
|
||||||
Method to execute the command on the remote server.
|
|
||||||
"""
|
|
||||||
_logger.info(command)
|
|
||||||
response = dict()
|
|
||||||
try:
|
|
||||||
ssh_stdin, ssh_stdout, ssh_stderr = ssh_obj.exec_command(command)
|
|
||||||
res = ssh_stdout.readlines()
|
|
||||||
_logger.info("execute_on_remote_shell res: %r", res)
|
|
||||||
err = ssh_stderr.readlines()
|
|
||||||
_logger.info("execute_on_remote_shell err: ")
|
|
||||||
_logger.info(err)
|
|
||||||
if err:
|
|
||||||
response['status'] = False
|
|
||||||
response['message'] = err
|
|
||||||
return response
|
|
||||||
response['status'] = True
|
|
||||||
response['result'] = res
|
|
||||||
return response
|
|
||||||
except Exception as e:
|
|
||||||
_logger.info("+++ERROR++",command)
|
|
||||||
_logger.info("++++++++++ERROR++++",e)
|
|
||||||
response['status'] = False
|
|
||||||
response['message'] = e
|
|
||||||
return response
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import os, time, sys
|
|
||||||
import re, shutil
|
|
||||||
import logging
|
|
||||||
import paramiko
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def ishostaccessible(details):
|
|
||||||
response = dict(
|
|
||||||
status=True,
|
|
||||||
message='Success'
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
ssh_obj = paramiko.SSHClient()
|
|
||||||
ssh_obj.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
_logger.info("Database Backup In check_connectivity scipt at Line {}".format(17))
|
|
||||||
ssh_obj.connect(hostname = details['host'], username = details['user'], password = details['password'], port = details['port'])
|
|
||||||
response['result'] = ssh_obj
|
|
||||||
return response
|
|
||||||
except Exception as e:
|
|
||||||
_logger.info("Couldn't connect remote %r"%e)
|
|
||||||
response['status'] = False
|
|
||||||
response['message'] = e
|
|
||||||
return response
|
|
||||||
@ -1,208 +0,0 @@
|
|||||||
from crontab import CronTab
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
#import getpass
|
|
||||||
import os
|
|
||||||
import pwd
|
|
||||||
|
|
||||||
PYTHON_ENV = "/usr/bin/python3"
|
|
||||||
LOG_FILE_PATH = "/var/log/odoo/backup_cron.log"
|
|
||||||
# BACKUP_SCRIPT_PATH = "/odoo/webkul_addons/wk_backup_restore/models/lib/saas_client_backup.py"
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class Cronjob:
|
|
||||||
def __init__(self, create_time, frequency, frequency_cycle):
|
|
||||||
self.command = ''
|
|
||||||
self.create_time = create_time
|
|
||||||
self.frequency = frequency
|
|
||||||
self.frequency_cycle = frequency_cycle
|
|
||||||
self.user = pwd.getpwuid(os.getuid())[0]
|
|
||||||
self.cron = CronTab(user=self.user)
|
|
||||||
|
|
||||||
def create_command(self, masterpswd, url, main_db, db_name, db_user, db_password, process_id, backup_location, storage_path, module_path, backup_format="zip",kwargs = {}):
|
|
||||||
_logger.info("===============CREATING BACKUP CMD=======================")
|
|
||||||
command = "{} {} --mpswd '{}' --url {} --dbname {} --maindb {} --dbuser {} --dbpassword '{}' --processid {} --bkploc {} --path {} --backup_format {} >> {} 2>&1".format(PYTHON_ENV, module_path, masterpswd, url, db_name, main_db, db_user, db_password, process_id, backup_location, storage_path, backup_format, LOG_FILE_PATH)
|
|
||||||
|
|
||||||
extra_args = ""
|
|
||||||
for key, val in kwargs.items():
|
|
||||||
extra_args += "--{} '{}' ".format(key, val)
|
|
||||||
|
|
||||||
if extra_args:
|
|
||||||
initial_command = command.split(">>")
|
|
||||||
command = f"{initial_command[0]}{extra_args} >>{initial_command[1]}"
|
|
||||||
|
|
||||||
self.command = command
|
|
||||||
|
|
||||||
def set_time_for_cron(self):
|
|
||||||
_logger.info(self.frequency_cycle)
|
|
||||||
self.job.minute.on(0)
|
|
||||||
date, time = self.create_time.split(',')
|
|
||||||
m, d, y = map(int, date.split('/'))
|
|
||||||
h, mi, s = map(int, time.split(':'))
|
|
||||||
if self.frequency_cycle == 'half_day':
|
|
||||||
h%=12
|
|
||||||
self.job.hour.during(h, 23).every(12)
|
|
||||||
self.job.minute.on(mi)
|
|
||||||
self.job.dow.on()
|
|
||||||
self.job.dom.on()
|
|
||||||
return True
|
|
||||||
elif self.frequency_cycle == 'daily':
|
|
||||||
if self.frequency == 1:
|
|
||||||
self.job.hour.on(h)
|
|
||||||
self.job.minute.on(mi)
|
|
||||||
self.job.dow.on()
|
|
||||||
self.job.dom.on()
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
elif self.frequency_cycle == 'weekly' or self.frequency_cycle == 'week':
|
|
||||||
_logger.info("time %r" %h)
|
|
||||||
day_of_week = datetime.datetime(y,m,d).weekday() + 1
|
|
||||||
if self.frequency == 1:
|
|
||||||
self.job.dow.on(day_of_week)
|
|
||||||
self.job.hour.on(h)
|
|
||||||
self.job.minute.on(0)
|
|
||||||
self.job.day.on()
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
elif self.frequency_cycle == 'monthly' or self.frequency_cycle == 'month':
|
|
||||||
if self.frequency == 1:
|
|
||||||
self.job.dom.on(d)
|
|
||||||
self.job.hour.on(h)
|
|
||||||
self.job.minute.on(mi)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
elif self.frequency_cycle == 'yearly' or self.frequency_cycle == 'year':
|
|
||||||
if self.frequency == 1:
|
|
||||||
self.job.dom.on(d)
|
|
||||||
self.job.hour.on(h)
|
|
||||||
self.job.minute.on(mi)
|
|
||||||
self.job.month.on(m)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def create_cronjob(self):
|
|
||||||
job = self.cron.new(
|
|
||||||
command=self.command)
|
|
||||||
self.job = job
|
|
||||||
self.set_time_for_cron()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def write_crontab(self):
|
|
||||||
try:
|
|
||||||
self.cron.write()
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"msg": None
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
return {
|
|
||||||
"success": False,
|
|
||||||
"msg": str(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
def remove_cron(self, process_id):
|
|
||||||
process_id = "--processid "+process_id+" "
|
|
||||||
jobs = list(self.list_cronjobs(process_id))
|
|
||||||
if len(jobs) == 1:
|
|
||||||
_logger.info("%s"%(jobs[0]))
|
|
||||||
self.cron.remove(jobs[0])
|
|
||||||
|
|
||||||
def list_cronjobs(self, search_keyword=None):
|
|
||||||
if search_keyword:
|
|
||||||
return self.cron.find_command(search_keyword)
|
|
||||||
return self.cron.lines
|
|
||||||
|
|
||||||
def update_cronjob(self, process_id):
|
|
||||||
_logger.info("Updating Job")
|
|
||||||
process_id = "--processid "+process_id+" "
|
|
||||||
jobs = list(self.list_cronjobs(process_id))
|
|
||||||
if len(jobs) == 1:
|
|
||||||
_logger.info("%s"%(jobs[0]))
|
|
||||||
self.job = jobs[0]
|
|
||||||
res = self.set_time_for_cron()
|
|
||||||
if res:
|
|
||||||
return {
|
|
||||||
'success': True,
|
|
||||||
'msg': self.job
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
'success': False,
|
|
||||||
'msg': 'Invalid Time.'
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# More than one cron for same client.
|
|
||||||
return {
|
|
||||||
'success': True,
|
|
||||||
'msg': jobs
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# set_cron_for_new_client(masterpwd, url, dbname, backup_location, interval, create_time, storage_path, cycle)
|
|
||||||
def add_cron(master_pass, url, main_db, db_name, db_user, db_password, process_id, backup_location, frequency, frequency_cycle, storage_path, module_path, backup_format="zip", backup_starting_time=None, kwargs={}):
|
|
||||||
_logger.info(locals())
|
|
||||||
create_time = backup_starting_time.strftime("%m/%d/%Y, %H:%M:%S")
|
|
||||||
cj = Cronjob(create_time, frequency, frequency_cycle)
|
|
||||||
cj.create_command(master_pass, url, main_db, db_name, db_user, db_password, process_id, backup_location, storage_path, module_path, backup_format, kwargs)
|
|
||||||
_logger.info("%s"%(cj.command))
|
|
||||||
cj.create_cronjob()
|
|
||||||
wc = cj.write_crontab()
|
|
||||||
if wc['success']:
|
|
||||||
_logger.info("Added Job Successfully")
|
|
||||||
else:
|
|
||||||
_logger.info("ERROR: %s"%(wc['msg']))
|
|
||||||
|
|
||||||
return wc
|
|
||||||
|
|
||||||
def update_cron(db_name, process_id, frequency, frequency_cycle):
|
|
||||||
create_time = datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S")
|
|
||||||
cj = Cronjob(create_time, frequency, frequency_cycle)
|
|
||||||
cj.command = db_name
|
|
||||||
uc = cj.update_cronjob(process_id)
|
|
||||||
if uc['success'] == False:
|
|
||||||
return uc
|
|
||||||
else:
|
|
||||||
wc = cj.write_crontab()
|
|
||||||
if wc['success']:
|
|
||||||
_logger.info("Updated Job Successfully")
|
|
||||||
else:
|
|
||||||
_logger.info("ERROR: %s"%(wc['msg']))
|
|
||||||
|
|
||||||
return wc
|
|
||||||
|
|
||||||
def remove_cron(db_name, process_id, frequency, frequency_cycle):
|
|
||||||
create_time = datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S")
|
|
||||||
cj = Cronjob(create_time, frequency, frequency_cycle)
|
|
||||||
cj.command = db_name
|
|
||||||
cj.remove_cron(process_id)
|
|
||||||
wc = cj.write_crontab()
|
|
||||||
if wc['success']:
|
|
||||||
_logger.info("Removed Job Successfully")
|
|
||||||
else:
|
|
||||||
_logger.info("ERROR: %s"%(wc['msg']))
|
|
||||||
|
|
||||||
return wc
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import os
|
|
||||||
master_pass = 'CnvvV46UGZb2=N'
|
|
||||||
url = 'http://192.168.5.125/'
|
|
||||||
main_db = 'postgres'
|
|
||||||
db_name = 'test_backup_crone.odoo-saas.webkul.com'
|
|
||||||
db_user = 'postgres'
|
|
||||||
db_password = 'postgres'
|
|
||||||
process_id = 1234
|
|
||||||
backup_location = 'local'
|
|
||||||
frequency_cycle = 'weekly' # monthly, weekly, yearly, half_day.
|
|
||||||
frequency = 2 if frequency_cycle == 'half_day' else 1
|
|
||||||
module_path = '/opt/webkul_addons/wk_backup_restore/models/lib/saas_client_backup.py'
|
|
||||||
storage_path = os.getcwd()
|
|
||||||
update_cron(db_name, frequency, frequency_cycle)
|
|
||||||
# add_cron(master_pass, url, main_db, db_name, db_user, db_password, process_id, backup_location, frequency, frequency_cycle, storage_path, module_path)
|
|
||||||
_logger.info(update_cron(db_name, frequency, frequency_cycle))
|
|
||||||
@ -1,384 +0,0 @@
|
|||||||
# curl -X POST -F 'master_pwd=abcd' -F 'name=xyz' -F 'backup_format=zip' -o /path/xyz.zip http://localhost:8069/web/database/backup
|
|
||||||
import requests
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import datetime
|
|
||||||
import psycopg2
|
|
||||||
import paramiko
|
|
||||||
import subprocess
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BackupStorage():
|
|
||||||
def __init__(self):
|
|
||||||
self.client_url = ""
|
|
||||||
self.ssh_obj = None
|
|
||||||
self.saas_ssh_obj = None
|
|
||||||
self.msg = ""
|
|
||||||
self.filename = ""
|
|
||||||
self.backup_time = None
|
|
||||||
self.backup_file_path = ""
|
|
||||||
self.remote_backup_file_path = ""
|
|
||||||
self.temp_backup_file_path = ""
|
|
||||||
|
|
||||||
def init_parser(self):
|
|
||||||
"""
|
|
||||||
Method to initialize parser for command line arguments,
|
|
||||||
and return parser object.
|
|
||||||
"""
|
|
||||||
parser = argparse.ArgumentParser(description='Process some arguments.')
|
|
||||||
parser.add_argument('--mpswd', action='store',
|
|
||||||
help='Master password Odoo')
|
|
||||||
parser.add_argument('--url', action='store',
|
|
||||||
help='saas client url')
|
|
||||||
parser.add_argument('--dbname', action='store',
|
|
||||||
help='name of database to backup')
|
|
||||||
parser.add_argument('--maindb', action='store',
|
|
||||||
help='name of main database')
|
|
||||||
parser.add_argument('--dbuser', action='store',
|
|
||||||
help='username of main database')
|
|
||||||
parser.add_argument('--dbpassword', action='store',
|
|
||||||
help='password of main database')
|
|
||||||
parser.add_argument('--processid', action='store',
|
|
||||||
help='process id')
|
|
||||||
parser.add_argument('--bkploc', action='store',
|
|
||||||
help='backup location local, dedicated, s3')
|
|
||||||
parser.add_argument('--path', action='store',
|
|
||||||
help='Backup Path')
|
|
||||||
parser.add_argument('--backup_format', action='store',
|
|
||||||
help='Backup Type')
|
|
||||||
|
|
||||||
parser.add_argument('--rhost', action='store',
|
|
||||||
help='Remote Hostname')
|
|
||||||
parser.add_argument('--rport', action='store',
|
|
||||||
help='Remote Port')
|
|
||||||
parser.add_argument('--ruser', action='store',
|
|
||||||
help='Remote User')
|
|
||||||
parser.add_argument('--rpass', action='store',
|
|
||||||
help='Remote Password')
|
|
||||||
|
|
||||||
parser.add_argument('--temp_bkp_path', action='store',
|
|
||||||
help='Temporary Backup Directory')
|
|
||||||
|
|
||||||
# Arguments related to SaaS Kit Backup module
|
|
||||||
parser.add_argument('--is_remote_client', action='store',
|
|
||||||
help='Is Remote SaaS Client')
|
|
||||||
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def database_entry(self, main_db, db_user, db_password, db_name, file_name, process_id, file_path, url, backup_date_time, status, message, kwargs={}):
|
|
||||||
"""
|
|
||||||
Method to insert created backup details in the database.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if db_user == "False" or db_password == "False":
|
|
||||||
connection = psycopg2.connect(database=main_db)
|
|
||||||
else:
|
|
||||||
connection = psycopg2.connect(user=db_user, password=db_password, host="127.0.0.1", port="5432", database=main_db)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print('Exited')
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
try:
|
|
||||||
file_path = file_path.replace('//', '/')
|
|
||||||
url = url.replace('//', '/')
|
|
||||||
# Connect to database
|
|
||||||
QUERY = "INSERT INTO backup_process_detail (name, file_name, backup_process_id, file_path, url, backup_date_time, status, message) VALUES (%s, %s, %s, %s, %s, %s, %s, %s);"
|
|
||||||
RECORD = (db_name, file_name, process_id, file_path, url, backup_date_time, status, message)
|
|
||||||
cursor = connection.cursor()
|
|
||||||
print("PostgreSQL server information")
|
|
||||||
print(connection.get_dsn_parameters(), "\n")
|
|
||||||
cursor.execute(QUERY, RECORD)
|
|
||||||
connection.commit()
|
|
||||||
count = cursor.rowcount
|
|
||||||
print(count, "Record inserted")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
finally:
|
|
||||||
if connection:
|
|
||||||
cursor.close()
|
|
||||||
connection.close()
|
|
||||||
print("Postgresql Connection Closed")
|
|
||||||
|
|
||||||
def login_backup_remote(self, args):
|
|
||||||
"""
|
|
||||||
Method to login to remote backup server.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
ssh_obj = paramiko.SSHClient()
|
|
||||||
ssh_obj.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh_obj.connect(hostname=args.rhost, username=args.ruser, password=args.rpass,port=args.rport)
|
|
||||||
self.ssh_obj = ssh_obj
|
|
||||||
except Exception as e:
|
|
||||||
print("Couldn't connect to remote backup server.", e)
|
|
||||||
raise Exception("Couldn't connect to remote backup server.")
|
|
||||||
|
|
||||||
def execute_on_remote_shell(self, ssh_obj,command):
|
|
||||||
"""
|
|
||||||
Method to execute commands on shell of remote server.
|
|
||||||
"""
|
|
||||||
response = dict()
|
|
||||||
try:
|
|
||||||
ssh_stdin, ssh_stdout, ssh_stderr = ssh_obj.exec_command(command)
|
|
||||||
print("execute_on_remote_shell out: ")
|
|
||||||
res = ssh_stdout.readlines()
|
|
||||||
print(res)
|
|
||||||
print("execute_on_remote_shell err: ")
|
|
||||||
err = ssh_stderr.readlines()
|
|
||||||
print(err)
|
|
||||||
if err:
|
|
||||||
raise Exception(err)
|
|
||||||
response['status'] = True
|
|
||||||
response['result'] = res
|
|
||||||
return response
|
|
||||||
except Exception as e:
|
|
||||||
print("+++ERROR++",command)
|
|
||||||
print("++++++++++ERROR++++",e)
|
|
||||||
response['status'] = False
|
|
||||||
response['message'] = e
|
|
||||||
return response
|
|
||||||
|
|
||||||
def check_remote_backup_path(self, args, backup_dir):
|
|
||||||
"""
|
|
||||||
Method to check remote backup path.
|
|
||||||
"""
|
|
||||||
response = dict(status=False)
|
|
||||||
try:
|
|
||||||
self.login_backup_remote(args)
|
|
||||||
cmd = "ls %s"%(backup_dir)
|
|
||||||
check_path = self.execute_on_remote_shell(self.ssh_obj ,cmd)
|
|
||||||
if check_path and not check_path.get('status'):
|
|
||||||
print("Error while checking the path of remote directory - ", check_path.get('message'))
|
|
||||||
raise Exception("Error while checking the path of remote directory - "+check_path.get('message'))
|
|
||||||
if check_path and not check_path.get('result'):
|
|
||||||
cmd = "mkdir -p %s; chmod -R 777 %s"%(backup_dir, backup_dir)
|
|
||||||
upd_permission = self.execute_on_remote_shell(self.ssh_obj,cmd)
|
|
||||||
if upd_permission and not upd_permission.get('status'):
|
|
||||||
print("Error while creating directory and updating permissions - ", check_path.get('message'))
|
|
||||||
raise Exception("Cannot create remote directory and update permissions.")
|
|
||||||
response.update(status=True)
|
|
||||||
except Exception as e:
|
|
||||||
print("Error: Creating Backup Directory")
|
|
||||||
response.update(message=e)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def create_client_url(self, url):
|
|
||||||
"""
|
|
||||||
Method to create client url for creating the backups.
|
|
||||||
"""
|
|
||||||
client_url = ""
|
|
||||||
if urlparse(url).scheme not in ['http','https']:
|
|
||||||
client_url = 'http://' + url + \
|
|
||||||
('/' if url[-1] != '/' else '')
|
|
||||||
else:
|
|
||||||
client_url = url + ('/' if url[-1] != '/' else '')
|
|
||||||
|
|
||||||
client_url += 'saas/database/backup'
|
|
||||||
return client_url
|
|
||||||
|
|
||||||
|
|
||||||
def store_backup_file(self, args, kwargs):
|
|
||||||
"""
|
|
||||||
Method to store backup file on the local server in the mentioned path.
|
|
||||||
"""
|
|
||||||
res = dict(status=False)
|
|
||||||
data = {
|
|
||||||
'master_pwd': args.mpswd,
|
|
||||||
'name': args.dbname,
|
|
||||||
'backup_format': args.backup_format or "zip"
|
|
||||||
}
|
|
||||||
|
|
||||||
client_url = self.client_url
|
|
||||||
backup_dir = kwargs.get('backup_dir')
|
|
||||||
try:
|
|
||||||
filename = None
|
|
||||||
backup_time = None
|
|
||||||
backup_file_path = None
|
|
||||||
with requests.post(client_url, data=data, stream=True) as response:
|
|
||||||
response.raise_for_status()
|
|
||||||
filename = response.headers.get('Backup-Filename', '')
|
|
||||||
backup_time = response.headers.get('Backup-Time', datetime.datetime.now().strftime("%m-%d-%Y-%H:%M:%S"))
|
|
||||||
backup_file_path = os.path.join(backup_dir, filename)
|
|
||||||
|
|
||||||
if response.headers.get('Content-Disposition'):
|
|
||||||
with open(backup_file_path, 'wb') as file:
|
|
||||||
for chunk in response.iter_content(chunk_size=1024):
|
|
||||||
if chunk:
|
|
||||||
file.write(chunk)
|
|
||||||
else:
|
|
||||||
raise Exception(response.content.decode())
|
|
||||||
|
|
||||||
msg = 'Database Backup Successful at ' + str(backup_time)
|
|
||||||
res.update(status=True, filename=filename, backup_time=backup_time, backup_file_path=backup_file_path)
|
|
||||||
except Exception as e:
|
|
||||||
res.update(message=e)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def manage_backup_files(self, args):
|
|
||||||
"""
|
|
||||||
Method to manage the backup files on either local server or remote server or any cloud server
|
|
||||||
"""
|
|
||||||
vals = dict()
|
|
||||||
backup_dir = os.path.join(args.path, 'backups')
|
|
||||||
response = dict(status=False)
|
|
||||||
self.client_url = self.create_client_url(args.url)
|
|
||||||
try:
|
|
||||||
vals.update(backup_dir=backup_dir)
|
|
||||||
backup_location = args.bkploc
|
|
||||||
if hasattr(self,'_create_%s_backup'%backup_location):## if you want to update dictionary then you can define this function _call_{backup_location}_backup_script
|
|
||||||
response = getattr(self,'_create_%s_backup'%backup_location)(args, vals)
|
|
||||||
|
|
||||||
msg = 'Database Backup Successful at ' + str(self.backup_time)
|
|
||||||
self.database_entry(args.maindb, args.dbuser, args.dbpassword, args.dbname, self.filename, args.processid, backup_dir+'/', self.backup_file_path, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), status="Success", message=msg)
|
|
||||||
response.update(status=True, message=msg)
|
|
||||||
except Exception as e:
|
|
||||||
msg = 'Failed at ' + str(self.backup_time or datetime.datetime.now()) + ' ' + str(e)
|
|
||||||
self.database_entry(args.maindb, args.dbuser, args.dbpassword, args.dbname, self.filename, args.processid, backup_dir+'/', self.backup_file_path if self.backup_file_path else self.remote_backup_file_path if self.remote_backup_file_path else '', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), status="Failure", message=msg)
|
|
||||||
response.update(status=False, message=msg)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def _create_local_backup(self, args, vals):
|
|
||||||
"""
|
|
||||||
Method to create backup on local server.
|
|
||||||
It copies the local backup file to remote saas server.
|
|
||||||
"""
|
|
||||||
response = dict(status=False)
|
|
||||||
temp_backup_dir = None
|
|
||||||
backup_dir = vals.get('backup_dir')
|
|
||||||
if not os.path.exists(backup_dir) and not eval(args.is_remote_client if args.is_remote_client else 'False'):
|
|
||||||
os.makedirs(backup_dir)
|
|
||||||
|
|
||||||
if args.is_remote_client and eval(args.is_remote_client):
|
|
||||||
temp_backup_dir = args.temp_bkp_path
|
|
||||||
vals.update(backup_dir=temp_backup_dir)
|
|
||||||
|
|
||||||
backup_store_res = self.store_backup_file(args, vals)
|
|
||||||
if backup_store_res and not backup_store_res.get('status'):
|
|
||||||
raise Exception(backup_store_res.get('message'))
|
|
||||||
|
|
||||||
self.filename = backup_store_res.get('filename')
|
|
||||||
self.backup_time = backup_store_res.get('backup_time')
|
|
||||||
self.backup_file_path = backup_store_res.get('backup_file_path')
|
|
||||||
|
|
||||||
if args.is_remote_client and eval(args.is_remote_client):
|
|
||||||
self.backup_file_path = os.path.join(backup_dir, self.filename)
|
|
||||||
self.temp_bkp_file_path = backup_store_res.get('backup_file_path')
|
|
||||||
response = self._create_saas_remote_backup(args)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def _create_remote_backup(self, args, vals):
|
|
||||||
"""
|
|
||||||
Method to create the temporary database backup on main server and store it on the remote server.
|
|
||||||
The temporary DB Backup file will be deleted after storing it on the remote server.
|
|
||||||
"""
|
|
||||||
response = dict(status=False)
|
|
||||||
backup_dir = vals.get('backup_dir')
|
|
||||||
temp_backup_dir = args.temp_bkp_path
|
|
||||||
vals.update(backup_dir=temp_backup_dir)
|
|
||||||
check_path_res = self.check_remote_backup_path(args, backup_dir)
|
|
||||||
if check_path_res and not check_path_res.get('status'):
|
|
||||||
raise Exception(check_path_res.get('message'))
|
|
||||||
backup_store_res = self.store_backup_file(args, vals)
|
|
||||||
if backup_store_res and not backup_store_res.get('status'):
|
|
||||||
raise Exception(backup_store_res.get('message'))
|
|
||||||
|
|
||||||
self.filename = backup_store_res.get('filename')
|
|
||||||
self.backup_time = backup_store_res.get('backup_time')
|
|
||||||
self.temp_backup_file_path = backup_store_res.get('backup_file_path')
|
|
||||||
self.remote_backup_file_path = os.path.join(backup_dir, self.filename)
|
|
||||||
self.backup_file_path = self.remote_backup_file_path
|
|
||||||
|
|
||||||
sftp = self.ssh_obj.open_sftp()
|
|
||||||
sftp.put(self.temp_backup_file_path, self.remote_backup_file_path)
|
|
||||||
sftp.close()
|
|
||||||
|
|
||||||
cmd = f"ls -f {self.remote_backup_file_path}"
|
|
||||||
|
|
||||||
# Checking if the backup file is successfully copied to remote server
|
|
||||||
check_file_exist = self.execute_on_remote_shell(self.ssh_obj,cmd)
|
|
||||||
if check_file_exist and check_file_exist.get("status"):
|
|
||||||
print("\nBackup file successfully copied to the remote server.")
|
|
||||||
print("remote backup_file_path --->", self.remote_backup_file_path)
|
|
||||||
|
|
||||||
# DELETE the temporary backup file from the Main Server
|
|
||||||
if os.path.exists(self.temp_backup_file_path):
|
|
||||||
os.remove(self.temp_backup_file_path)
|
|
||||||
print("\nBackup file successfully deleted from the Main Server.")
|
|
||||||
|
|
||||||
response.update(status=True)
|
|
||||||
return response
|
|
||||||
else:
|
|
||||||
print("\nBackup file doesn't successfully moved to the remote server.")
|
|
||||||
raise Exception("Backup file couldn't be moved to remote server.")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def login_saas_remote(self, remote):
|
|
||||||
"""
|
|
||||||
Method to login to the remote SaaS server using SSH.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
ssh_obj = paramiko.SSHClient()
|
|
||||||
ssh_obj.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh_obj.connect(hostname=remote.get('host'), username=remote.get('user'), password=remote.get('password'),port=remote.get('port'))
|
|
||||||
self.saas_ssh_obj = ssh_obj
|
|
||||||
except Exception as e:
|
|
||||||
print("Couldn't connect remote SaaS server: ", e)
|
|
||||||
raise Exception("Couldn't connect to remote SaaS server.")
|
|
||||||
|
|
||||||
|
|
||||||
def _create_saas_remote_backup(self, args):
|
|
||||||
"""
|
|
||||||
This method is crated to make the compatibility with SaaS Kit Backup module.
|
|
||||||
This method will copy the local backup file to the remote saas server.
|
|
||||||
The temporary backup file will be deleted after storing it on the remote saas server.
|
|
||||||
"""
|
|
||||||
saas_url = 'http://localhost:8069/remote/server/creds'
|
|
||||||
saas_data = {
|
|
||||||
'backup_process_id': int(args.processid)
|
|
||||||
}
|
|
||||||
response = dict(status=False)
|
|
||||||
try:
|
|
||||||
# Getting the host server creds of the remote saas server
|
|
||||||
with requests.post(saas_url, data=saas_data, stream=True) as saas_response:
|
|
||||||
saas_response.raise_for_status()
|
|
||||||
resp = json.loads((saas_response.content).decode())
|
|
||||||
|
|
||||||
# Uploading the backup file from the main server to remote saas server
|
|
||||||
self.login_saas_remote(resp.get('host_server'))
|
|
||||||
if self.saas_ssh_obj:
|
|
||||||
saas_sftp = self.saas_ssh_obj.open_sftp()
|
|
||||||
saas_sftp.put(self.temp_bkp_file_path, self.backup_file_path)
|
|
||||||
|
|
||||||
# Removing the temporary backup on the main server
|
|
||||||
if os.path.exists(self.temp_bkp_file_path):
|
|
||||||
saas_sftp.remove(self.temp_bkp_file_path)
|
|
||||||
saas_sftp.close()
|
|
||||||
print("Local Backup File Successfully Copied to the Remote SaaS Server")
|
|
||||||
|
|
||||||
response.update(status=True)
|
|
||||||
except Exception as e:
|
|
||||||
print("Exception while copying the local backup to the remote saas server")
|
|
||||||
raise Exception("Local Backup file couldn't be moved to remote saas server.")
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
backup_storage = BackupStorage()
|
|
||||||
parser = backup_storage.init_parser()
|
|
||||||
args = parser.parse_args()
|
|
||||||
print(backup_storage.manage_backup_files(args))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 727 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 241 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 323 B |
|
Before Width: | Height: | Size: 186 B |
|
Before Width: | Height: | Size: 3.5 KiB |
@ -1,358 +0,0 @@
|
|||||||
<!-- Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>) -->
|
|
||||||
<!-- See LICENSE file for full copyright and licensing details. -->
|
|
||||||
<!-- License URL : <https://store.webkul.com/license.html/> -->
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<div class="overflow-hidden">
|
|
||||||
<section class="container-fluid">
|
|
||||||
<div class="row shadow-sm justify-content-sm-center">
|
|
||||||
<div class="col-md-5 d-flex justify-content-md-between justify-content-center">
|
|
||||||
<div class="ms-2" style="margin-top: 10px; margin-bottom: 12px">
|
|
||||||
<img src="webkul_icon.png">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-7">
|
|
||||||
<div class="container-fluid ps-0">
|
|
||||||
<div class="row align-items-center" style="margin-top: 10px; margin-bottom: 12px">
|
|
||||||
<div class="col-sm-4 text-center" style="font-family: 'Open Sans',sans-serif; font-size: 16px; color: #46485E; font-weight: 600;">
|
|
||||||
Supported Editions :
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<div class="d-flex align-items-center mx-auto" style="font-family: 'Open Sans',sans-serif; width:8rem; font-weight: 400; background-color: #FFFFFF;border-radius: 2px;border: 2px solid #2149F3; color: #2149F3;">
|
|
||||||
<div style="padding-right: 5%; padding-left: 5%;">
|
|
||||||
<img src="check.png">
|
|
||||||
</div>
|
|
||||||
<div class="fw-bold mt-1">On Premise</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-2">
|
|
||||||
<div class="d-flex align-items-center mx-auto" style="font-family: 'Open Sans',sans-serif; width:6rem; font-weight: 400; background-color: #FFFFFF;border-radius: 2px;border: 2px solid #cc0000; color: #cc0000;">
|
|
||||||
<div style="padding-right: 5%; padding-left: 5%;">
|
|
||||||
<img src="wrong.png" width="14" height="14">
|
|
||||||
</div>
|
|
||||||
<div class="fw-bold mt-1">Odoo.sh</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<div class="d-flex align-items-center mx-auto" style="font-family: 'Open Sans',sans-serif; width:8.4rem; font-weight: 400; background-color: #FFFFFF;border-radius: 2px;border: 2px solid #cc0000; color: #cc0000;">
|
|
||||||
<div style="padding-right: 5%; padding-left: 5%;">
|
|
||||||
<img src="wrong.png" width="14" height="14">
|
|
||||||
</div>
|
|
||||||
<div class="fw-bold mt-1">Odoo Online</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="oe_container" id="wk_header">
|
|
||||||
<div class="mt32 mb32" style="width: 98%;">
|
|
||||||
<h1 class="text-center" style="color:#000">
|
|
||||||
Odoo Database Backup
|
|
||||||
</h1>
|
|
||||||
<h5 class="mt8 mb8 text-center" style="font-weight:500; font-size:18px; color:#484848; font-family:'Montserrat', sans-serif">
|
|
||||||
<i> Ease to Manage Backup Process for Odoo Database!</i>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-center">
|
|
||||||
<div class="row mt16 mb16 py-2 m-1" style="font-weight:500;font-size:18px; color:#484848; width: 98%; font-family:'Montserrat', sans-serif">
|
|
||||||
<div class="container col-md-6 border-right" style="font-weight:500;line-height: 24px;font-size:16px;color:#484848;font-family:'Montserrat', sans-serif; vertical-align: middle; display: grid;">
|
|
||||||
<p class="mb4 mt4 mr8">
|
|
||||||
Odoo Database Backup Module allows you to create a backup process for your instance to manage backup of database.You can configure a backup process to create backup as hourly, daily, weekly, monthly.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="container col-md-6 ">
|
|
||||||
<h4 class="ms-md-3">Information</h4>
|
|
||||||
<div class="mt-2 ms-md-3" style="display:flex; align-items: center;">
|
|
||||||
<span class="ms-1 me-2 my-auto"><img src="guideicon.png" alt="user-guide"></span>
|
|
||||||
<div style="font-weight: 600;font-size: 14px;line-height: 10px;">
|
|
||||||
User Guide
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ms-md-3" style="display:flex;">
|
|
||||||
<span style="font-size:15px; margin-top:0.2rem"> https://webkul.com/blog/odoo-data-backup-how-to-create-and-restore-data-in-odoo/</span>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="mt-2 ml-md-3" style="display:flex; align-items: center;"> -->
|
|
||||||
<!-- <span class="ml-1 mr-2 my-auto"><img src="youtube2.png" alt="user-guide"></span> -->
|
|
||||||
<!-- <div style="font-weight: 600;font-size: 14px;line-height: 10px;" class="mt-2"> -->
|
|
||||||
<!-- <span>YouTube</span> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
<!-- <div class="ml-md-3" style="display:flex;"> -->
|
|
||||||
<!-- <span style="font-size:15px; margin-top:0.2rem">https://www.youtube.com/watch?v=6NFWqUmq-zE</span> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="oe_container d-flex justify-content-center">
|
|
||||||
<div style="width: 98%;">
|
|
||||||
<div class="shadow-sm" style="margin-top: 20px;background-color: #FFFFFF;
|
|
||||||
border-radius: 2px;border: 1px solid #E0E0E0;">
|
|
||||||
<div class="d-flex justify-content-center align-items-center">
|
|
||||||
<div class="row" style="width: 100%; margin-top: 20px; padding-bottom: 2%;">
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 justify-content-center align-items-center">
|
|
||||||
<p style="font-family: 'Source Sans Pro', sans-serif; font-size: 16px;line-height: 28px; margin-bottom:1%;">
|
|
||||||
Odoo is a perfect software to manage your complete business. Moreover, it helps you to save and manage different business processes like product management, inventory management, order management, and more.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 justify-content-center align-items-center">
|
|
||||||
<p style="font-family: 'Source Sans Pro', sans-serif; font-size: 16px;line-height: 28px; margin-bottom:1%;">
|
|
||||||
When it comes to enhancing the protection of your data, you must include a backup solution that will cover all the parts of data protection that your Odoo misses. Moreover, backup software ensures critical data is protected properly through backup, and is always available via restore.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 justify-content-center align-items-center">
|
|
||||||
<p style="font-family: 'Source Sans Pro', sans-serif; font-size: 16px;line-height: 28px;">
|
|
||||||
Now, you can easily manage backup processes for your Odoo Instance. Odoo Database Backup Module allows you to create a backup process for your instance to manage backup of database. You can configure a backup process to create backup as hourly, daily, weekly, monthly.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="oe_container d-flex justify-content-center">
|
|
||||||
<div style="width: 98%;">
|
|
||||||
<div class="shadow-sm" style="margin-top: 20px;background-color: #FFFFFF;
|
|
||||||
border-radius: 2px;border: 1px solid #E0E0E0;">
|
|
||||||
<div class="d-flex justify-content-center align-items-center">
|
|
||||||
<div class="row" style="width: 100%; margin-top: 20px;">
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 d-flex align-items-center">
|
|
||||||
<img src="backup_db.png">
|
|
||||||
<span class="d-flex justify-content-center align-items-center" style="margin-left: 1%; font-family: 'Source Sans Pro', sans-serif; font-weight: 600;font-size: 20px;line-height: 38px;color: #333333;">Perks of Backup Solution in Odoo</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="justify-content-center align-items-center" style="padding-bottom: 2%;">
|
|
||||||
<div style="margin-top: 10px; margin-left:2%;">
|
|
||||||
<div style="font-family: 'Source Sans Pro', sans-serif; font-weight: normal;font-size: 16px;line-height: 25px;color: #555555;">
|
|
||||||
Odoo backup software refers to technology that stores and protects data that products create.
|
|
||||||
</div>
|
|
||||||
<div style="font-family: 'Source Sans Pro', sans-serif; font-weight: normal;font-size: 16px;line-height: 25px;color: #555555;">
|
|
||||||
<li>
|
|
||||||
Smart businesses use SaaS backup and restore to make sure that important data is safe.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Regular data backups help businesses know the problems with their data and find opportunities to fix them.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
You do not need to suffer the disruption and downtime of restoring whole data sets to earlier states when you can identify and restore only the problematic data.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
You'll be able to delete old backups after retention of some specific count of recent backups on server.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Now, you'll be able to backup on the local server as well as remote server.
|
|
||||||
</li>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3">
|
|
||||||
<strong>
|
|
||||||
Note: The module has been thoroughly tested on the Ubuntu operating system.
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="oe_container d-flex justify-content-center">
|
|
||||||
<div style="width: 98%; margin-top: 30px;">
|
|
||||||
<div style="font-family: 'Source Sans Pro', sans-serif; font-weight: normal;font-size: 24px;line-height: 40px;color: #333333;">
|
|
||||||
<p>Detailed Features List</p>
|
|
||||||
</div>
|
|
||||||
<div class="justify-content-center">
|
|
||||||
<div class="card h-100 ml" style="min-height:100px">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="card-text">
|
|
||||||
<ul style="font-family: 'Source Sans Pro', sans-serif; font-weight: normal;font-size: 16px;line-height: 25px;color: #555555;list-style-type: none !important;padding-left: 0px;">
|
|
||||||
<li><img src="point.svg" style="margin-right : 1%;"/>Admin can take backup of db on daily/Weekly/monthly/yearly by configuring the backup process.</li>
|
|
||||||
<li><img src="point.svg" style="margin-right : 1%;"/>Backup will store on the local server as well as on remote as zip/dump.</li>
|
|
||||||
<li><img src="point.svg" style="margin-right : 1%;"/>Admin can also download the backuped zip from the record.</li>
|
|
||||||
<li><img src="point.svg" style="margin-right : 1%;"/>You can also update/cancel the process easily.</li>
|
|
||||||
<li><img src="point.svg" style="margin-right : 1%;"/>Admin can set the retention period for the backup files.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="oe_spaced mw-100 card" style="margin-top:20px; margin-bottom:20px">
|
|
||||||
<!-- <div class="card-header shadow-sm p-0"
|
|
||||||
style="font-family:'Open Sans', sans-serif; font-weight:600; font-size:18px; line-height:28px; color:#333333;">
|
|
||||||
<ul class="nav nav-tabs justify-content-center pt-1" role="tablist">
|
|
||||||
<li class="nav-item" style="background-color:#2335D7; color:#ffffff; border-radius:2px">
|
|
||||||
<span href="#odoo_backup_module_tab1" class="nav-link active" role="tab" aria-selected="true"
|
|
||||||
data-bs-toggle="tab">
|
|
||||||
Configuration
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div> -->
|
|
||||||
<div class="card-body text-center p-0" style="font-weight:400">
|
|
||||||
<ul class="nav nav-tabs justify-content-center pt-1" role="tablist" data-tabs="tabs">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a href="#odoo_backup_module_tab1" class="nav-link active" style="border-radius:10px 10px 0 0;font-family:'Open Sans', sans-serif; font-weight:600; font-size:18px; line-height:28px; color:#333333;" role="tab" aria-selected="true"
|
|
||||||
data-bs-toggle="tab">
|
|
||||||
Configuration
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="tab-content">
|
|
||||||
<div class="tab-pane active show" id="odoo_backup_module_tab1" role="tabpanel">
|
|
||||||
<div class="container shadow-sm px-4">
|
|
||||||
<section class="oe_container my-4">
|
|
||||||
<div class="panel panel-primary">
|
|
||||||
<div class="panel-heading" style="font-size:16px; line-height:20px; color:#555555">
|
|
||||||
<div
|
|
||||||
style="margin-top:33px; font-family:'Open Sans', sans-serif; font-weight:600; font-size:18px; line-height:22px; text-align:center; color:#333333">
|
|
||||||
Create and Configure the Local Backup Process with correct and desired details.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body p-3">
|
|
||||||
<div style="padding:18px; background: #FFFFFF;box-shadow: 0px 0px 6px 0 rgba(0, 0, 0, 0.16);border-radius: 2px;">
|
|
||||||
<img style="width:100%" class="img-responsive border" src="backup_ss_1.png" alt="Not found">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel panel-primary">
|
|
||||||
<div class="panel-heading" style="font-size:16px; line-height:20px; color:#555555">
|
|
||||||
<div
|
|
||||||
style="margin-top:33px; font-family:'Open Sans', sans-serif; font-weight:600; font-size:18px; line-height:22px; text-align:center; color:#333333">
|
|
||||||
After configure the Backup Process, confirm it to start the backup operations.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body p-3">
|
|
||||||
<div style="padding:18px; background: #FFFFFF;box-shadow: 0px 0px 6px 0 rgba(0, 0, 0, 0.16);border-radius: 2px;">
|
|
||||||
<img style="width:100%" class="img-responsive border" src="backup_ss_2.png" alt="Not found">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel panel-primary">
|
|
||||||
<div class="panel-heading" style="font-size:16px; line-height:20px; color:#555555">
|
|
||||||
<div
|
|
||||||
style="margin-top:33px; font-family:'Open Sans', sans-serif; font-weight:600; font-size:18px; line-height:22px; text-align:center; color:#333333">
|
|
||||||
When module starts taking your database backup, it will listed in the record. You can download the zip file of any db backup by clicking on Download button.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body p-3">
|
|
||||||
<div style="padding:18px; background: #FFFFFF;box-shadow: 0px 0px 6px 0 rgba(0, 0, 0, 0.16);border-radius: 2px;">
|
|
||||||
<img style="width:100%" class="img-responsive border" src="backup_ss_3.png" alt="Not found">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel panel-primary">
|
|
||||||
<div class="panel-heading" style="font-size:16px; line-height:20px; color:#555555">
|
|
||||||
<div
|
|
||||||
style="margin-top:33px; font-family:'Open Sans', sans-serif; font-weight:600; font-size:18px; line-height:22px; text-align:center; color:#333333">
|
|
||||||
Later, you can also cancel the Backup Process to stop taking backups.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body p-3">
|
|
||||||
<div style="padding:18px; background: #FFFFFF;box-shadow: 0px 0px 6px 0 rgba(0, 0, 0, 0.16);border-radius: 2px;">
|
|
||||||
<img style="width:100%" class="img-responsive border" src="backup_ss_4.png" alt="Not found">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel panel-primary">
|
|
||||||
<div class="panel-heading" style="font-size:16px; line-height:20px; color:#555555">
|
|
||||||
<div
|
|
||||||
style="margin-top:33px; font-family:'Open Sans', sans-serif; font-weight:600; font-size:18px; line-height:22px; text-align:center; color:#333333">
|
|
||||||
Configure and validate the remote backup server with correct and desired details.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body p-3">
|
|
||||||
<div style="padding:18px; background: #FFFFFF;box-shadow: 0px 0px 6px 0 rgba(0, 0, 0, 0.16);border-radius: 2px;">
|
|
||||||
<img style="width:100%" class="img-responsive border" src="backup_ss_5.png" alt="Not found">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel panel-primary">
|
|
||||||
<div class="panel-heading" style="font-size:16px; line-height:20px; color:#555555">
|
|
||||||
<div
|
|
||||||
style="margin-top:33px; font-family:'Open Sans', sans-serif; font-weight:600; font-size:18px; line-height:22px; text-align:center; color:#333333">
|
|
||||||
Configure and Confirm the Remote Backup Process with correct and desired details.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body p-3">
|
|
||||||
<div style="padding:18px; background: #FFFFFF;box-shadow: 0px 0px 6px 0 rgba(0, 0, 0, 0.16);border-radius: 2px;">
|
|
||||||
<img style="width:100%" class="img-responsive border" src="backup_ss_6.png" alt="Not found">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel panel-primary">
|
|
||||||
<div class="panel-heading" style="font-size:16px; line-height:20px; color:#555555">
|
|
||||||
<div
|
|
||||||
style="margin-top:33px; font-family:'Open Sans', sans-serif; font-weight:600; font-size:18px; line-height:22px; text-align:center; color:#333333">
|
|
||||||
When module starts taking your database backup, it will listed in the record. You can download the zip file of any db backup by clicking on Download button.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body p-3">
|
|
||||||
<div style="padding:18px; background: #FFFFFF;box-shadow: 0px 0px 6px 0 rgba(0, 0, 0, 0.16);border-radius: 2px;">
|
|
||||||
<img style="width:100%" class="img-responsive border" src="backup_ss_7.png" alt="Not found">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="pt32" id="webkul_support">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<h2 class="text-center">Help and Support</h2>
|
|
||||||
<p class="mb-2 text-center" style="font-size:16px; color:#333; font-weight:400">Get Immediate support for any of your query</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<p class="text-center px-5" style="font-size:14px; color:#555; font-weight:normal">You will get 90 days free support for any doubt, queries, and bug fixing (excluding data recovery) or any type of issue related to this module.</p>
|
|
||||||
</div>
|
|
||||||
<div class="mx-auto col-lg-9 mb-4 oe_screenshot">
|
|
||||||
<div class="row align-items-center justify-content-center mx-0 p-3">
|
|
||||||
<div class="col-sm-2 text-center pr-0">
|
|
||||||
<img src="mail.png" alt="mail" class="img img-fluid">
|
|
||||||
</div>
|
|
||||||
<div class="col-xl-7 col-sm-10">
|
|
||||||
<p class="my-2" style="color:#555; font-weight:bold">Write a mail to us:</p>
|
|
||||||
<b class="text-primary" style="font-size:18px">support@webkul.com</b>
|
|
||||||
<p class="my-2" style="font-size:14px; color:#777; font-weight:normal">Any queries or want any extra features? Just drop a mail to our support.</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-xl-3 offset-xl-0 float-xl-right col-sm-10 offset-sm-2 float-left mt16">
|
|
||||||
<a href="mailto:support@webkul.com" style="padding:10px 22px; background-color:#2335D7; font-size:14px; color:#fff"><i class="fa fa-pencil-square-o" style="color:white; margin-right:4px"></i>Write To US</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mx-auto col-lg-9 oe_screenshot">
|
|
||||||
<div class="row align-items-center justify-content-center mx-0 p-3">
|
|
||||||
<div class="col-sm-2 text-center pr-0">
|
|
||||||
<img src="support-icon.png" alt="support-icon" class="img img-fluid">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-10 ">
|
|
||||||
<p class="my-2" style="font-weight:bold; color:#555">Get in touch with our Expert:</p>
|
|
||||||
<b class="text-primary text-break" style="font-size:18px">https://webkul.uvdesk.com/en/customer/create-ticket/</b>
|
|
||||||
<p class="my-2" style="font-weight:normal; font-size:14px; color:#777">Have any technical queries, want extra features, or anything else? Our team is here to answer all your questions. Just Raise A Support Ticket.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="oe_container">
|
|
||||||
<div class="oe_span12">
|
|
||||||
<!-- Piwik Image Tracker-->
|
|
||||||
<img alt=""
|
|
||||||
src="http://odooimg.webkul.com/analytics/piwik/piwik.php?idsite=3&rec=1&action_name=Odoo-Database-Backup-Module&url=https://apps.openerp.com/apps/modules/14.0/wk_backup_restore&uid=wk_backup_restore"
|
|
||||||
style="border:0" />
|
|
||||||
<!-- End Piwik -->
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 469 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 431 B |
@ -1,119 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>) -->
|
|
||||||
<!-- See LICENSE file for full copyright and licensing details. -->
|
|
||||||
<!-- License URL : <https://store.webkul.com/license.html/> -->
|
|
||||||
|
|
||||||
<odoo>
|
|
||||||
<data>
|
|
||||||
<record id="backup_process_form_view" model="ir.ui.view">
|
|
||||||
<field name="name">Backup Process Form View</field>
|
|
||||||
<field name="model">backup.process</field>
|
|
||||||
<field name="type">form</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form>
|
|
||||||
<header>
|
|
||||||
<button name="confirm_process" class="oe_highlight" string="Confirm" type="object" invisible="(state != 'draft')" />
|
|
||||||
<button name="cancel_process" class="oe_highlight" string="Cancel" type="object" invisible="(state == 'running') or (state == 'cancel')" />
|
|
||||||
<button name="remove_attached_cron" class="oe_highlight" string="Cancel Process" type="object" invisible="(state != 'running')" />
|
|
||||||
<field name="state" widget="statusbar" />
|
|
||||||
</header>
|
|
||||||
<sheet>
|
|
||||||
<div class="" style="width: 500px;">
|
|
||||||
<div class="oe_title" style="width: 500px;">
|
|
||||||
<label class="oe_edit_only" for="name" string="Name"/>
|
|
||||||
<h1><field name="name" class="oe_inline" readonly="1"/></h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<group>
|
|
||||||
|
|
||||||
</group>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<group>
|
|
||||||
<field name="frequency_cycle" required="1"/>
|
|
||||||
<field name="backup_starting_time" required="1" readonly="state != 'draft'"/>
|
|
||||||
<field name="db_name" required="1" readonly="state != 'draft'"/>
|
|
||||||
<field name="backup_format" required="1" readonly="(state != 'draft')"/>
|
|
||||||
</group>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<group>
|
|
||||||
<field name="backup_location" required="1" readonly="state != 'draft'" />
|
|
||||||
<label for="remote_server_id" invisible="backup_location != 'remote'" required="backup_location == 'remote'" readonly="state != 'draft'" />
|
|
||||||
<div class="mb-2" invisible="backup_location != 'remote'">
|
|
||||||
<field name="remote_server_id" invisible="backup_location != 'remote'" required="backup_location == 'remote'" readonly="state != 'draft'"/>
|
|
||||||
<br/>
|
|
||||||
<button string="Test Connection" name="test_host_connection" type="object" class="oe_highlight" invisible="not remote_server_id"/>
|
|
||||||
</div>
|
|
||||||
<field name="storage_path" required="1" readonly="state != 'draft'"/>
|
|
||||||
<field name="enable_retention" readonly="state != 'draft'"/>
|
|
||||||
<field name="retention" required="1" readonly="state != 'draft'" invisible="not enable_retention" />
|
|
||||||
</group>
|
|
||||||
<div class="text-muted mb-3" name="retention_note">
|
|
||||||
<strong invisible="not enable_retention and backup_location in ['local', 'remote']">Note:</strong>
|
|
||||||
<ul>
|
|
||||||
<li invisible="not enable_retention">After enabling Drop Database backup you need to set backup retention count.</li>
|
|
||||||
<li invisible="not enable_retention">The Backup Retention Count is the number of backups you wish to keep.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<notebook>
|
|
||||||
<page name="backup_details" string="Backup Details">
|
|
||||||
<field name="backup_details_ids" readonly="1">
|
|
||||||
<list editable="bottom" create='false'>
|
|
||||||
<field name="backup_date_time" readonly="1" />
|
|
||||||
<field name="file_name" readonly="1" />
|
|
||||||
<field name="message" readonly="1" />
|
|
||||||
<field name="status" readonly="1" widget="badge" decoration-success="status == 'Success'" decoration-danger="status =='Failure'" />
|
|
||||||
<field name="backup_location" column_invisible="1" />
|
|
||||||
<button name="unlink_confirmation" type="object" title="Delete" icon="fa-trash" class="btn-secondary me-2 text-muted" />
|
|
||||||
<button name="download_db_file" type="object" string="Download" class="oe_highlight" invisible="status != 'Success' or backup_location not in ['local', 'remote']"/>
|
|
||||||
</list>
|
|
||||||
</field>
|
|
||||||
</page>
|
|
||||||
</notebook>
|
|
||||||
</sheet>
|
|
||||||
<chatter />
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="backup_process_list_view" model="ir.ui.view">
|
|
||||||
<field name="name">Backup Process List View</field>
|
|
||||||
<field name="model">backup.process</field>
|
|
||||||
<field name="type">list</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<list>
|
|
||||||
<field name="name" />
|
|
||||||
<field name="frequency_cycle" />
|
|
||||||
<field name="db_name" />
|
|
||||||
<field name="storage_path" />
|
|
||||||
<field name="backup_location" />
|
|
||||||
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-warning="state =='confirm'" decoration-success="state =='running'" decoration-danger="state =='cancel'" />
|
|
||||||
</list>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
|
|
||||||
<record id="backup_process_action_view" model="ir.actions.act_window">
|
|
||||||
<field name="name">Backup Process</field>
|
|
||||||
<field name="path">backup-process</field>
|
|
||||||
<field name="res_model">backup.process</field>
|
|
||||||
<field name="view_mode">list,form</field>
|
|
||||||
<field name="view_id" ref="backup_process_list_view"/>
|
|
||||||
<field name="help" type="html">
|
|
||||||
<p class="o_view_nocontent_smiling_face">
|
|
||||||
Create new backup process
|
|
||||||
</p><p>
|
|
||||||
Configure backup processes to create the database backups.
|
|
||||||
</p>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</data>
|
|
||||||
</odoo>
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
<data>
|
|
||||||
<record id="backup_remote_server_form_view" model="ir.ui.view">
|
|
||||||
<field name="name">Backup Remote Server Form View</field>
|
|
||||||
<field name="model">backup.remote.server</field>
|
|
||||||
<field name="type">form</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form>
|
|
||||||
<header>
|
|
||||||
<button string="Reset to draft" name="reset_to_draft" type="object" class="oe_highlight" invisible="(state == 'draft')"/>
|
|
||||||
<button string="Validate" name="set_validated" type="object" class="oe_highlight" invisible="(state != 'draft')"/>
|
|
||||||
<field name="state" widget="statusbar"/>
|
|
||||||
</header>
|
|
||||||
<sheet>
|
|
||||||
<div class="" style="width: 500px;">
|
|
||||||
<div class="oe_title" style="width: 500px;">
|
|
||||||
<label class="oe_edit_only" for="name" string="Name"/>
|
|
||||||
<h1><field name="name" class="oe_inline" required="True" /></h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<group name="sftp_details" string="SFTP Details">
|
|
||||||
<group class="mt-2">
|
|
||||||
<field name="sftp_host" required="True" readonly="(state == 'validated')"/>
|
|
||||||
<field name="sftp_port" required="True" readonly="(state == 'validated')"/>
|
|
||||||
<field name="sftp_user" password="1" required="True" readonly="(state == 'validated')"/>
|
|
||||||
<label for="sftp_password"/>
|
|
||||||
<div>
|
|
||||||
<field name="sftp_password" password="1" required="True" readonly="(state == 'validated')"/>
|
|
||||||
<br/>
|
|
||||||
<button string="Test Connection" name="test_host_connection" type="object" class="oe_highlight"/>
|
|
||||||
</div>
|
|
||||||
</group>
|
|
||||||
<group class="mt-2">
|
|
||||||
<field name="def_backup_dir" required="True" readonly="(state == 'validated')"/>
|
|
||||||
<label for="temp_backup_dir"/>
|
|
||||||
<div>
|
|
||||||
<field name="temp_backup_dir" required="True" readonly="(state == 'validated')"/>
|
|
||||||
</div>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col-md-12 text-muted" style="text-align:justify">
|
|
||||||
<p><strong>NOTE:</strong> Kindly ensure the below mentioned points before configuring the backup remote server:</p>
|
|
||||||
<ol>
|
|
||||||
<li>The SFTP user must have the ssh access to the remote server.</li>
|
|
||||||
<li>The SFTP user must have the permissions and ownership of the default remote backup directory.</li>
|
|
||||||
<li>The temporary backup directory must be present on the main server along with the appropriate permissions for odoo user.</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</sheet>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="backup_remote_server_list_view" model="ir.ui.view">
|
|
||||||
<field name="name">Backup Remote Server List View</field>
|
|
||||||
<field name="model">backup.remote.server</field>
|
|
||||||
<field name="type">list</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<list>
|
|
||||||
<field name="name" />
|
|
||||||
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-success="state =='validated'"/>
|
|
||||||
</list>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="backup_remote_server_search_view" model="ir.ui.view">
|
|
||||||
<field name="name">Backup Remote Servers Search</field>
|
|
||||||
<field name="model">backup.remote.server</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<search string="Active Backup Remote Servers">
|
|
||||||
<filter name="archived" string="Archived" domain="[('active', '=', False)]" />
|
|
||||||
</search>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
|
|
||||||
<record id="backup_remote_server_action_view" model="ir.actions.act_window">
|
|
||||||
<field name="name">Backup Remote Server</field>
|
|
||||||
<field name="path">backup-remote-server</field>
|
|
||||||
<field name="res_model">backup.remote.server</field>
|
|
||||||
<field name="view_mode">list,form</field>
|
|
||||||
<field name="view_id" ref="backup_remote_server_list_view"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</data>
|
|
||||||
|
|
||||||
</odoo>
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>) -->
|
|
||||||
<!-- See LICENSE file for full copyright and licensing details. -->
|
|
||||||
<!-- License URL : <https://store.webkul.com/license.html/> -->
|
|
||||||
|
|
||||||
<odoo>
|
|
||||||
<data>
|
|
||||||
<menuitem name="Backup Operation" id="backup_root_menu" web_icon="wk_backup_restore,static/description/icon.png" groups="base.group_user" sequence="2" />
|
|
||||||
<menuitem name="Backup" id="restore_menu" parent="wk_backup_restore.backup_root_menu" sequence="1" />
|
|
||||||
<menuitem name="Backup Process" action="wk_backup_restore.backup_process_action_view" sequence="1" id="menu_backup_process" parent="wk_backup_restore.restore_menu"/>
|
|
||||||
<menuitem name="Configuration" id="backup_configuration_menu" sequence="2" parent="wk_backup_restore.backup_root_menu"/>
|
|
||||||
<menuitem name="Remote Servers" id="backup_remote_server_menu" parent="wk_backup_restore.backup_configuration_menu" sequence="1" action="wk_backup_restore.backup_remote_server_action_view"/>
|
|
||||||
</data>
|
|
||||||
</odoo>
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
from . import backup_deletion_confirmation
|
|
||||||
from . import backup_custom_message_wizard
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#################################################################################
|
|
||||||
#
|
|
||||||
# Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>)
|
|
||||||
# See LICENSE file for full copyright and licensing details.
|
|
||||||
# License URL : <https://store.webkul.com/license.html/>
|
|
||||||
#
|
|
||||||
#################################################################################
|
|
||||||
|
|
||||||
from odoo import api, fields, models
|
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
from odoo.exceptions import UserError
|
|
||||||
import logging
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class BackupCustomMessage(models.TransientModel):
|
|
||||||
_name = "backup.custom.message.wizard"
|
|
||||||
_description = 'Backup Custom Message Wizard'
|
|
||||||
|
|
||||||
message = fields.Html(string="Message", readonly=True)
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>) -->
|
|
||||||
<!-- See LICENSE file for full copyright and licensing details. -->
|
|
||||||
<!-- License URL : <https://store.webkul.com/license.html/> -->
|
|
||||||
|
|
||||||
<odoo>
|
|
||||||
<data>
|
|
||||||
<record id="backup_custom_message_wizard_view" model="ir.ui.view">
|
|
||||||
<field name="name">backup.custom.message.wizard.form</field>
|
|
||||||
<field name="model">backup.custom.message.wizard</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="Backup Custom Message">
|
|
||||||
<sheet>
|
|
||||||
<field name="message" />
|
|
||||||
</sheet>
|
|
||||||
<footer class="d-flex justify-content-end">
|
|
||||||
<button string="OK" class="btn btn-primary" special="cancel" />
|
|
||||||
</footer>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="action_backup_custom_message_wizard" model="ir.actions.act_window">
|
|
||||||
<field name="name">Message</field>
|
|
||||||
<field name="res_model">backup.custom.message.wizard</field>
|
|
||||||
<field name="view_mode">form</field>
|
|
||||||
<field name="view_id" ref="backup_custom_message_wizard_view"/>
|
|
||||||
<field name="target">new</field>
|
|
||||||
</record>
|
|
||||||
</data>
|
|
||||||
</odoo>
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#################################################################################
|
|
||||||
#
|
|
||||||
# Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>)
|
|
||||||
# See LICENSE file for full copyright and licensing details.
|
|
||||||
# License URL : <https://store.webkul.com/license.html/>
|
|
||||||
#
|
|
||||||
#################################################################################
|
|
||||||
|
|
||||||
from odoo import api, fields, models
|
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
from odoo.exceptions import UserError
|
|
||||||
import logging
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BackupDeletionConfirmation(models.TransientModel):
|
|
||||||
_name = "backup.deletion.confirmation"
|
|
||||||
_description = 'Backup Deletion Confirmation Wizard'
|
|
||||||
|
|
||||||
backup_id = fields.Many2one(comodel_name="backup.process.detail", string="Backup Process Detail")
|
|
||||||
message = fields.Html(string="Message")
|
|
||||||
|
|
||||||
|
|
||||||
def action_delete_backup_detail(self):
|
|
||||||
for rec in self:
|
|
||||||
rec.backup_id.unlink()
|
|
||||||
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (c) 2016-Present Webkul Software Pvt. Ltd. (<https://webkul.com/>) -->
|
|
||||||
<!-- See LICENSE file for full copyright and licensing details. -->
|
|
||||||
<!-- License URL : <https://store.webkul.com/license.html/> -->
|
|
||||||
|
|
||||||
<odoo>
|
|
||||||
<data>
|
|
||||||
<record id="backup_deletion_confirmation_wizard_view" model="ir.ui.view">
|
|
||||||
<field name="name">backup.deletion.confirmation.form</field>
|
|
||||||
<field name="model">backup.deletion.confirmation</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="Backup Process Deletion Confirmation">
|
|
||||||
<sheet>
|
|
||||||
<field name="message" readonly="1"/>
|
|
||||||
</sheet>
|
|
||||||
<footer class="d-flex justify-content-end">
|
|
||||||
<button string="Cancel" class="btn-default" special="cancel" />
|
|
||||||
<button string='Delete' name="action_delete_backup_detail" type="object" class="btn-primary me-2" widget="many2many_buttons"/>
|
|
||||||
</footer>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="action_backup_deletion_confirmation_wizard" model="ir.actions.act_window">
|
|
||||||
<field name="name">Backup Details Deletion Confirmation</field>
|
|
||||||
<field name="res_model">backup.deletion.confirmation</field>
|
|
||||||
<field name="view_mode">form</field>
|
|
||||||
<field name="view_id" ref="backup_deletion_confirmation_wizard_view"/>
|
|
||||||
<field name="context">{'default_backup_id': active_id}</field>
|
|
||||||
<field name="target">new</field>
|
|
||||||
</record>
|
|
||||||
</data>
|
|
||||||
</odoo>
|
|
||||||