Delete wk_backup_restore directory

pull/441/head
by-kuzey 1 year ago
parent c06c1473d4
commit 3de71ecd5d

@ -1,398 +0,0 @@
SOFTWARE LICENCE AGREEMENT
==========================
This AGREEMENT is made effective on the date of the purchase of the software
between Webkul Software Pvt. Ltd.,Company incorporated under the Companies
Act, 1956 (hereinafter referred to as “Licensor"), and the purchaser of the
software/ product (hereinafter referred to as "Licensee").
Preamble
--------
Licensor is a web and mobile product based organization engaged in the
business of developing and marketing software for enterprise level e-commerce
businesses. It is an ISO and NSR (NASSCOM) certified organization having a
team of more than 150 creative engineers which come from different
backgrounds. It has developed more than 700 web extensions and apps in the
past few years for open source platforms which are used and trusted globally.
Licensee now wishes to obtain license, and Licensor wishes to grant a license,
to allow use of the software so purchased in developing the e-commerce
business website/ mobile app of the Licensee, subject to the terms and
conditions set forth herein.
THEREFORE, with the intent to be legally bound, the parties hereby agree as
follows:
Agreement
---------
1.DEFINITIONS.
As used in this Agreement, the following capitalized terms
shall have the definitions set forth below:
"Derivative Works" are works developed by Licensee, its officers, agents,
contractors or employees, which are based upon, in whole or in part, the
Source Code and/or the Documentation and may also be based upon and/or
incorporate one or more other preexisting works of the Licensor. Derivative
Works may be any improvement, revision, modification, translation (including
compilation or recapitulation by computer), abridgment, condensation,
expansion, or any other form in which such a preexisting work may be recast,
transformed, or adapted. For purposes hereof, a Derivative Work shall also
include any compilation that incorporates such a preexisting work.
"Documentation" is written, printed or otherwise recorded or stored (digital
or paper) material relating to the Software and/or Source Code, including
technical specifications and instructions for its use including Software/
Source Code annotations and other descriptions of the principles of its
operation and instructions for its use.
"Improvements" shall mean, with respect to the Software, all modifications and
changes made, developed, acquired or conceived after the date hereof and
during the entire term of this Agreement.
"Source Code" is the computer programming source code form of the Software in
the form maintained by the Licensor, and includes all non-third-party
executables, libraries, components, and Documentation created or used in the
creation, development, maintenance, and support of the Software as well as all
updates, error corrections and revisions thereto provided by Licensor, in
whole or in part.
2.SOFTWARE LICENSE.
(a)Grant of License. For the consideration set forth below, Licensor hereby
grants to Licensee, and Licensee hereby accepts the worldwide, non-exclusive,
perpetual, royalty-free rights and licenses set forth below:
(i)The right and license to use and incorporate the software, in whole or in
part, to develop its website/ mobile app (including the integration of all or
part of the Licensors software into Licensee's own software) on one domain (
Except Joomla modules , listed on store are entitled to be used on unlimited
domain as per the standard guidelines ) only, solely for the own personal or
business use of the Licensee. However, the License does not authorize the
Licensee to compile, copy or distribute the said Software or its Derivative
Works.
(ii)The right and license does not authorize the Licensee to share any backup
or archival copies of the Software and / or the Source Code and Documentation
on any public internet space including github , stackoverflow etc . The
Licensee must ensure that the backup are not accessible to any other person
and the Licensee must prevent copying / use of source code by any unauthorized
persons.
(iii)The right and license does not authorize the Licensee to migrate the
domain license to another domain.
(iv)Our Joomla extensions are published under the GNU/GPL.
(b)Scope; Rights and Responsibilities.
(i)Licensor shall enable the Licensee to download one complete copy of the
Software.
(ii)The Software is intended for the sole use of the Licensee in development
of its own website/ mobile app.
(iii)Licensee does not have the right to hand over, sell, distribute,
sub-license, rent, lease or lend any portion of the Software or Documentation,
whether modified or unmodified, to anyone. Licensee should not place the
Software on a server so that it becomes accessible via a public network such
as the Internet for distribution purposes. In case the Licensee is using any
source code management system like github, it can use the code there only when
it has paid subscription from such management system.
(iv) In case the Licensee purchases the module and allow the third party
development agency to customize as per its need, it is at liberty to do so
subject to the condition that the Licensee as well as the Agency are not
authorized to sell the modified version of the extension. Except for the
required customization purposes, Licensee is not authorized to release the
Source Code, Derivative Work source code and/or Documentation to any third
party, which shall be considered as violation of the Agreement, inter-alia
entailing forthwith termination and legal action.
(c)Ownership.
(i)Software and Source Code. All right, title, copyright, and interest in the
Software, Source Code, Software Modifications and Error corrections will be
and remain the property of Licensor.
(ii)Derivative Works. As creation of Derivative Works by the Licensee is
prohibited, thus, all right, title, copyright, and interest in any and/or all
Derivative Works and Improvements created by, or on behalf of, Licensee will
also be deemed to the property of Licensor. Licensor shall be entitled to
protect copyright / intellectual property in all such Derivative Works and
Improvements also in any country as it may deem fit including without
limitation seeking copyright and/or patent protection.
3.CONSIDERATION.
(a)Licensee shall pay to Licensor the amount as mentioned on the website from
where the order is placed, as one-time, upfront fees in consideration for the
licenses and rights granted hereunder (hereinafter referred to as the "License
Fee"). The License Fee to be paid by Licensee shall be paid upfront at the
time of placing the order, and no credit will be allowed under any
circumstances.
(b)Once paid, the License Fees shall be non-refundable. The Licensee has fully
satisfied itself about the Software and has seen the demonstration, and only
thereafter has placed the order. Thus, the License Fees or any part thereof is
non-refundable. No claim for refund of the Licence Fees shall be entertained
under any circumstances.
4.REPRESENTATIONS AND WARRANTIES.
(a)Mutual. Each of the parties represents and warrants to the other as
follows.
(i)such party is a legal entity duly organized, validly existing and in good
standing;
(ii)such party has the power and authority to conduct its business as
presently conducted and to enter into, execute, deliver and perform this
Agreement.
(iii)This Agreement has been duly and validly accepted by such party and
constitutes the legal, valid and binding obligations of such party
respectively, enforceable against such party in accordance with their
respective terms;
(iv)the acceptance, execution, delivery and performance of this Agreement does
not and will not violate such party's charter or by-laws; nor require any
consent, authorization, approval, exemption or other action by any third party
or governmental entity.
(b)Licensor warrants that, at the time of purchase of the Software:
the Software will function materially as set forth in the website or published
functionality provided by Licensor to customers and potential customers
describing the Software; and
Software add-ons, if purchased by the Licensee from the Licensor, will not
materially diminish the features or functions of or the specifications of the
Software as they existed as of the execution of this Agreement.
(c)Title. Licensor represents and warrants that it is the exclusive owner of
all copyright/ intellectual property in the Software (including the Source
Code) and has good and marketable title to the Software (including the Source
Code) free and clear of all liens, claims and encumbrances of any nature
whatsoever (collectively, "Liens"). Licensor's grant of license and rights to
Licensee hereunder does not, and will not infringe any third party's property,
intellectual property or personal rights.
5.TERM.
(a)Subject to Licensee's payment obligations, this Agreement shall commence as
on the date of making payment of the Software by the Licensee to the Licensor,
and shall continue until terminated by either party.
(b)The Licensor retains the right to terminate the license at any time, if the
Licensee is not abiding by any of the terms of the Agreement. The Licensee may
terminate the Agreement at any time at its own discretion by uninstalling the
Software and /or by destroying the said Software (or any copies thereof).
However, the Licensee shall not be entitled to seek any refund of the amount
paid by it to the Licensor, under any circumstances.
(c)Survival. In the event this Agreement is terminated for any reason, the
provisions set forth in Sections 2(a), 2(b), and 2(c) shall survive.
6.INDEMNIFICATION.
The Licensee release the Licensor from, and agree to indemnify, defend and
hold harmless the Licensor (and its officers, directors, employees, agents and
Affiliates) against, any claim, loss, damage, settlement, cost, taxes, expense
or other liability (including, without limitation, attorneys' fees) (each, a
"Claim") arising from or related to: (a) any actual or alleged breach of any
obligations in this Agreement; (b) any refund, adjustment, or return of
Software,(c) any claim for actual or alleged infringement of any Intellectual
Property Rights made by any third party or damages related thereto; or (d)
Taxes.
7.LIMITATION OF LIABILITY.
The Licensor will not be liable for any direct, indirect, incidental, special,
consequential or exemplary damages, including but not limited to, damages for
loss of profits, goodwill, use, data or other intangible losses arising out of
or in connection with the Software, whether in contract, warranty, tort etc. (
including negligence, software liability, any type of civil responsibility or
other theory or otherwise) to the Licensee or any other person for cost of
software, cover, recovery or recoupment of any investment made by the Licensee
or its affiliates in connection with this Agreement, or for any other loss of
profit, revenue, business, or data or punitive or consequential damages
arising out of or relating to this Agreement. Further, the aggregate liability
of the Licensor, arising out of or in connection with this Agreement or the
transactions contemplated hereby will not exceed at any time, or under any
circumstances, the total amounts received by the Licensor from the Licensee in
connection with the particular software giving rise to the claim.
8.FORCE MAJEURE.
The Licensor will not be liable for any delay or failure to perform any of its
obligations under this Agreement by reasons, events or other matters beyond
its reasonable control.
9.RELATIONSHIP OF PARTIES.
The Licensor and Licensee are independent legal entities, and nothing in this
Agreement will be construed to create a partnership, joint venture,
association of persons, agency, franchise, sales representative, or employment
relationship between the parties. The Licensee will have no authority to make
or accept any offers or representations on behalf of the Licensor. The
relationship between the parties is that of Licensor and Licensee only, and
the rights, duties, liabilities of each party shall be governed by this
Agreement.
10.MODIFICATION.
The Licensor may amend any of the terms and conditions contained in this
Agreement at any time and solely at its discretion. Any changes will be
effective upon the posting of such changes on the Portal/ website, and the
Licensee is responsible for reviewing these changes and informing itself of
all applicable changes or notices. The continued use of a software by the
Licensee after posting of any changes by the Licensor, will constitute the
acceptance of such changes or modifications by the Licensee.
11.MISCELLANEOUS.
(a)General Provisions. This Agreement: (i) may be amended only by a writing
signed by each of the parties; (ii) may be executed in several counterparts,
each of which shall be deemed an original but all of which shall constitute
one and the same instrument; (iii) contains the entire agreement of the
parties with respect to the transactions contemplated hereby and supersedes
all prior written and oral agreements, and all contemporaneous oral
agreements, relating to such transactions; (iv) shall be governed by, and
construed and enforced in accordance with, the laws of India; and (v) shall be
binding upon, and inure to the benefit of, the parties and their respective
successors and permitted assigns. Each of the parties hereby irrevocably
submits to the jurisdiction of the Courts at Delhi, India, for the purposes of
any action or proceeding arising out of or relating to this Agreement or the
subject matter hereof and brought by any other party.
(b)Assignment. Except for the purpose of customization as mentioned in clause
2(b)(iv) above, Licensee cannot assign, pledge or otherwise transfer, whether
by operation of law or otherwise, this Agreement, or any of its obligations
hereunder, without the prior written consent of Licensor, which consent shall
not be unreasonably withheld.
(c)Notices. Unless otherwise specifically provided herein, all notices,
consents, requests, demands and other communications required or permitted
hereunder:
(i)shall be in writing;
(ii)shall be sent by messenger, certified or registered mail/email, or
reliable express delivery service, to the appropriate address(es) set forth
below; and
(iii)shall be deemed to have been given on the date of receipt by the
addressee, as evidenced by a receipt executed by the addressee (or a
responsible person in his or her office), the records of the Party delivering
such communication or a notice to the effect that such addressee refused to
claim or accept such communication, if sent by messenger, mail or express
delivery service.
All such communications shall be sent to the following addresses or numbers,
or to such other addresses or numbers as any party may inform the others by
giving five days' prior notice:
If to Webkul Software Pvt. Ltd.:
Webkul Software Pvt. Ltd.
A-67, Sector 63, NOIDA 201301,
Uttar Pradesh, India
If to Licensee:
At the address mentioned by the Licensee
(at the time of placing order of generating Invoice)
(d)Severability. It is the intent of the parties that the provisions of this
Agreement be enforced to the fullest extent permissible under the laws and
public policies of India in which enforcement hereof is sought. In
furtherance of the foregoing, each provision hereof shall be severable from
each other provision, and any provision hereof which is/ becomes unenforceable
shall be subject to the following: (i) if such provision is contrary to or
conflicts with any requirement of any statute, rule or regulation in effect,
then such requirement shall be incorporated into, or substituted for, such
unenforceable provision to the minimum extent necessary to make such provision
enforceable; (ii) the court, agency or arbitrator considering the matter is
hereby authorized to (or, if such court, agency or arbitrator is unwilling or
fails to do so, then the parties shall) amend such provision to the minimum
extent necessary to make such provision enforceable, and the parties hereby
consent to the entry of an order so amending such provision; and (iii) if
any such provision cannot be or is not reformed and made enforceable pursuant
to clause (i) or (ii) above, then such provision shall be ineffective to the
minimum extent necessary to make the remainder of this Agreement enforceable.
Any application of the foregoing provisions to any provision hereof shall not
effect the validity or enforceability of any other provision hereof.
(e)By purchasing the Software, the Licensee acknowledge that it has read this
Agreement, and that it agrees to the content of the Agreement, its terms and
agree to use the Software in compliance with this Agreement.
(f)The Licensor holds the sole copyright of the Software. The Software or any
portion thereof is a copyrightable matter and is liable to be protected by the
applicable laws. Copyright infringement in any manner can lead to prosecution
according to the current law. The Licensor reserves the right to revoke the
license of any user who is not holding any license or is holding an invalid
license.
(g)This Agreement gives the right to use only one copy of the Software on one
domain solely for the own personal or business use of the Licensee, subject to
all the terms and conditions of this Agreement. A separate License has to be
purchased for each new Software installation. Any distribution of the Software
without the written consent of the Licensor (including non-commercial
distribution) is regarded as violation of this Agreement, and will entail
immediate termination of the Agreement and may invite liability, both civil
and criminal, as per applicable laws.
(h)The Licensor reserves the rights to publish a selected list of users/
Licensees of its Software, and no permission of any Licensee is needed in this
regard. The Licensee agrees that the Licensor may, in its sole discretion,
disclose or make available any information provided or submitted by the
Licensee or related to it under this Agreement to any judicial,
quasi-judicial, governmental, regulatory or any other authority as may be
required by the Licensor to co-operate and / or comply with any of their
orders, instructions or directions or to fulfill any requirements under
applicable Laws.
(i)If the Licensee continues to use the Software even after the sending of the
notice by the Licensor for termination, the Licensee agree to accept an
injunction to restrain itself from its further use, and to pay all costs (
including but not limited to reasonable attorney fees) to enforce injunction
or to revoke the License, and any damages suffered by the Licensor because of
the misuse of the Software by the Licensee.
12.ARBITRATION.
If any dispute arises between the Licensor and the Licensee at any time, in
connection with the validity, interpretation, implementation or alleged breach
of any provision of this Agreement, the same shall be referred to a sole
Arbitrator who shall be an independent and neutral third party appointed
exclusively by the Licensor. The Licensee shall not object to the appointment
of the Arbitrator so appointed by the Licensor. The place of arbitration shall
be Delhi, India. The Arbitration & Conciliation Act, 1996 as amended by The
Arbitration & Conciliation (Amendment) Act, 2015, shall govern the
arbitration proceedings. The arbitration proceedings shall be held in the
English language.
This document is an electronic record in terms of Information Technology Act,
2000 and the amended provisions pertaining to electronic records in various
statutes as amended by the Information Technology Act, 2000. This electronic
record is generated by a computer system and does not require any physical or
digital signatures.

@ -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))

@ -1,6 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_backup_process,backup.process,model_backup_process,,1,1,1,1
access_backup_process_detail,backup.process.detail,model_backup_process_detail,,1,1,1,1
access_backup_deletion_confirmation_wizard,backup.deletion.confirmation,model_backup_deletion_confirmation,,1,1,1,1
access_backup_remote_server,access.backup.remote.server,model_backup_remote_server,,1,1,1,1
access_backup_custom_message_wizard,access.backup.custom.message.wizard,model_backup_custom_message_wizard,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_backup_process backup.process model_backup_process 1 1 1 1
3 access_backup_process_detail backup.process.detail model_backup_process_detail 1 1 1 1
4 access_backup_deletion_confirmation_wizard backup.deletion.confirmation model_backup_deletion_confirmation 1 1 1 1
5 access_backup_remote_server access.backup.remote.server model_backup_remote_server 1 1 1 1
6 access_backup_custom_message_wizard access.backup.custom.message.wizard model_backup_custom_message_wizard 1 1 1 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 727 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

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&amp;rec=1&amp;action_name=Odoo-Database-Backup-Module&amp;url=https://apps.openerp.com/apps/modules/14.0/wk_backup_restore&amp;uid=wk_backup_restore"
style="border:0" />
<!-- End Piwik -->
</div>
</section>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

@ -1,3 +0,0 @@
<svg width="12" height="10" viewBox="0 0 12 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.2474 0.341509C10.6111 -0.0741275 11.2429 -0.116245 11.6585 0.247437C12.0741 0.611119 12.1163 1.24288 11.7526 1.65852L4.75258 9.65852C4.37166 10.0939 3.70193 10.1162 3.29289 9.70712L0.292893 6.70712C-0.0976311 6.3166 -0.0976311 5.68343 0.292893 5.29291C0.683418 4.90238 1.31658 4.90238 1.70711 5.29291L3.95129 7.53709L10.2474 0.341509Z" fill="#500FDA"/>
</svg>

Before

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

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>
Loading…
Cancel
Save