@ -0,0 +1,55 @@
|
|||||||
|
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg
|
||||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
|
:alt: License: AGPL-3
|
||||||
|
|
||||||
|
Database Restore Manager
|
||||||
|
========================
|
||||||
|
All the database backups that are stored by module auto_database_backup can be
|
||||||
|
restored using this module
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
The auto_database_backup module should be installed
|
||||||
|
( Here is the link: https://apps.odoo.com/apps/modules/17.0/auto_database_backup/ )
|
||||||
|
All the requirements for auto_database_backup module should be installed.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
- www.odoo.com/documentation/17.0/setup/install.html
|
||||||
|
- Install our custom addon
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
General Public License, Version 3 (AGPL v3).
|
||||||
|
(http://www.gnu.org/licenses/agpl-3.0-standalone.html)
|
||||||
|
|
||||||
|
Company
|
||||||
|
-------
|
||||||
|
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
* Developer: (V16) Aslam A K,
|
||||||
|
(V17) Sruthi Ranjith
|
||||||
|
Contact : odoo@cybrosys.com
|
||||||
|
|
||||||
|
Contacts
|
||||||
|
--------
|
||||||
|
* Mail Contact : odoo@cybrosys.com
|
||||||
|
* Website : https://cybrosys.com
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
-----------
|
||||||
|
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported.
|
||||||
|
|
||||||
|
Maintainer
|
||||||
|
==========
|
||||||
|
.. image:: https://cybrosys.com/images/logo.png
|
||||||
|
:target: https://cybrosys.com
|
||||||
|
|
||||||
|
This module is maintained by Cybrosys Technologies.
|
||||||
|
For support and more information, please visit https://www.cybrosys.com
|
||||||
|
|
||||||
|
Further information
|
||||||
|
===================
|
||||||
|
HTML Description: `<static/description/index.html>`__
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
from . import models
|
||||||
|
from . import wizard
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
{
|
||||||
|
'name': "Database Restore Manager",
|
||||||
|
'version': "17.0.1.0.0",
|
||||||
|
'category': "Extra Tools",
|
||||||
|
'summary': """Database Restore Manager,Restore Manager,Odoo Database Backup, Automatic Backup, Database Backup, Automatic Backup,Database auto-backup, odoo backup'
|
||||||
|
'google drive, dropbox, nextcloud, amazon S3, onedrive or '
|
||||||
|
'remote server, Odoo17""",
|
||||||
|
'description': """The Database Restore Manager allows users to easily
|
||||||
|
restore and download backups uploaded by the auto_database_backup module,
|
||||||
|
providing convenient backup management within the platform""",
|
||||||
|
'author': 'Cybrosys Techno Solutions',
|
||||||
|
'company': 'Cybrosys Techno Solutions',
|
||||||
|
'maintainer': 'Cybrosys Techno Solutions',
|
||||||
|
'website': "https://www.cybrosys.com",
|
||||||
|
'depends': ['base_setup', 'auto_database_backup'],
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/database_manager_views.xml',
|
||||||
|
'views/res_config_settings_views.xml',
|
||||||
|
'wizard/database_restore_views.xml'
|
||||||
|
],
|
||||||
|
'assets': {
|
||||||
|
'web.assets_backend': [
|
||||||
|
'/odoo_database_restore_manager/static/src/js/restore.js',
|
||||||
|
'/odoo_database_restore_manager/static/src/xml/db_restore_dashboard_templates.xml',
|
||||||
|
'/odoo_database_restore_manager/static/src/scss/db_restore.scss'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'external_dependencies': {'python': ['dropbox', 'gdown']},
|
||||||
|
'images': ['static/description/banner.jpg'],
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'installable': True,
|
||||||
|
'auto_install': False,
|
||||||
|
'application': False,
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
## Module <odoo_database_restore_manager>
|
||||||
|
|
||||||
|
#### 20.03.2024
|
||||||
|
#### Version 17.0.1.0.0
|
||||||
|
#### ADD
|
||||||
|
- Initial commit for Database Restore Manager
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
from . import database_manager
|
||||||
|
from . import res_config_settings
|
||||||
@ -0,0 +1,279 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
import boto3
|
||||||
|
import dropbox
|
||||||
|
import ftplib
|
||||||
|
import nextcloud_client
|
||||||
|
import os
|
||||||
|
import paramiko
|
||||||
|
import requests
|
||||||
|
from odoo.http import request
|
||||||
|
from datetime import datetime
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseManager(models.Model):
|
||||||
|
""" Dashboard model to view all database backups """
|
||||||
|
_name = 'database.manager'
|
||||||
|
_description = 'Database Manager'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def action_import_files(self):
|
||||||
|
""" Import latest backups from the storages configured """
|
||||||
|
return_data = {}
|
||||||
|
backup_count = int(self.env['ir.config_parameter'].get_param(
|
||||||
|
'odoo_database_restore_manager.backup_count'))
|
||||||
|
current_company = request.httprequest.cookies.get('cids')[0]
|
||||||
|
# Check if backup_count is less than or equal to 0
|
||||||
|
if backup_count <= 0:
|
||||||
|
return ['error', 'Please set a backup count', 'Storages',
|
||||||
|
current_company]
|
||||||
|
# Check if any backups are configured in the database
|
||||||
|
if not self.env['db.backup.configure'].search([]):
|
||||||
|
return ['error', 'No Backups Found', 'auto_database_backup',
|
||||||
|
current_company]
|
||||||
|
# Loop through each configured backup source
|
||||||
|
for rec in self.env['db.backup.configure'].search([]):
|
||||||
|
# For Dropbox
|
||||||
|
if rec.backup_destination == 'dropbox':
|
||||||
|
try:
|
||||||
|
# Retrieve backups from Dropbox and update the return_data
|
||||||
|
# dictionary with the latest backups
|
||||||
|
dbx_dict = {}
|
||||||
|
dbx = dropbox.Dropbox(app_key=rec.dropbox_client_key,
|
||||||
|
app_secret=rec.dropbox_client_secret,
|
||||||
|
oauth2_refresh_token=
|
||||||
|
rec.dropbox_refresh_token)
|
||||||
|
response = dbx.files_list_folder(path=rec.dropbox_folder)
|
||||||
|
for files in response.entries:
|
||||||
|
file = dbx.files_get_temporary_link(
|
||||||
|
path=files.path_lower)
|
||||||
|
dbx_dict[file.metadata.name] = file.link, 'Dropbox', \
|
||||||
|
files.client_modified
|
||||||
|
return_data.update(dict(list(sorted(dbx_dict.items(),
|
||||||
|
key=lambda x: x[1][2],
|
||||||
|
reverse=True))[
|
||||||
|
:backup_count]))
|
||||||
|
except Exception as e:
|
||||||
|
# Handle any exceptions that occur during Dropbox backup
|
||||||
|
# retrieval
|
||||||
|
return ['error', e, 'Dropbox', current_company]
|
||||||
|
# For Onedrive
|
||||||
|
if rec.backup_destination == 'onedrive':
|
||||||
|
try:
|
||||||
|
# Retrieve backups from OneDrive and update the return_data
|
||||||
|
# dictionary with the latest backups
|
||||||
|
onedrive_dict = {}
|
||||||
|
if rec.onedrive_token_validity <= fields.Datetime.now():
|
||||||
|
rec.generate_onedrive_refresh_token()
|
||||||
|
url = "https://graph.microsoft.com/v1.0/me/drive/items/" \
|
||||||
|
"%s/children?Content-Type=application/json" \
|
||||||
|
% rec.onedrive_folder_key
|
||||||
|
response = requests.request("GET", url, headers={
|
||||||
|
'Authorization': 'Bearer "' + rec.onedrive_access_token + '"'}, data={})
|
||||||
|
for file in response.json().get('value'):
|
||||||
|
if list(file.keys())[
|
||||||
|
0] == '@microsoft.graph.downloadUrl':
|
||||||
|
onedrive_dict[file['name']] = file[
|
||||||
|
'@microsoft.graph.downloadUrl'], 'OneDrive', \
|
||||||
|
datetime.strptime(
|
||||||
|
file['createdDateTime'],
|
||||||
|
"%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S")
|
||||||
|
return_data.update(dict(list(sorted(onedrive_dict.items(),
|
||||||
|
key=lambda x: x[1][2],
|
||||||
|
reverse=True))[
|
||||||
|
:backup_count]))
|
||||||
|
except Exception as e:
|
||||||
|
# Handle any exceptions that occur during OneDrive backup
|
||||||
|
# retrieval
|
||||||
|
return ['error', e, 'OneDrive', current_company]
|
||||||
|
# For Google Drive
|
||||||
|
if rec.backup_destination == 'google_drive':
|
||||||
|
try:
|
||||||
|
# Retrieve backups from Google Drive and update the
|
||||||
|
# return_data dictionary with the latest backups
|
||||||
|
gdrive_dict = {}
|
||||||
|
if rec.gdrive_token_validity <= fields.Datetime.now():
|
||||||
|
rec.generate_gdrive_refresh_token()
|
||||||
|
response = requests.get(
|
||||||
|
f"https://www.googleapis.com/drive/v3/files",
|
||||||
|
headers={
|
||||||
|
"Authorization": "Bearer %s" % rec.gdrive_access_token
|
||||||
|
},
|
||||||
|
params={
|
||||||
|
"q": f"'{rec.google_drive_folder_key}' in parents",
|
||||||
|
"fields": "files(name, webContentLink, createdTime)",
|
||||||
|
})
|
||||||
|
for file_data in response.json().get("files", []):
|
||||||
|
gdrive_dict[file_data.get("name")] = file_data.get(
|
||||||
|
"webContentLink"), 'Google Drive', \
|
||||||
|
datetime.strptime(file_data.get("createdTime"),
|
||||||
|
"%Y-%m-%dT%H:%M:%S.%fZ").strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S")
|
||||||
|
return_data.update(dict(list(sorted(gdrive_dict.items(),
|
||||||
|
key=lambda x: x[1][2],
|
||||||
|
reverse=True))[
|
||||||
|
:backup_count]))
|
||||||
|
except Exception as e:
|
||||||
|
# Handle any exceptions that occur during Google Drive
|
||||||
|
# backup retrieval
|
||||||
|
return ['error', e, 'Google Drive', current_company]
|
||||||
|
# For Local Storage
|
||||||
|
if rec.backup_destination == 'local':
|
||||||
|
try:
|
||||||
|
# Retrieve backups from Local Storage and update the
|
||||||
|
# return_data dictionary with the latest backups
|
||||||
|
local_dict = {}
|
||||||
|
for root, dirs, files in os.walk(rec.backup_path):
|
||||||
|
for file in files:
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
create_date = datetime.fromtimestamp(
|
||||||
|
os.path.getctime(file_path)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S")
|
||||||
|
local_dict[file] = file_path, 'Local Storage', \
|
||||||
|
create_date
|
||||||
|
return_data.update(dict(list(sorted(local_dict.items(),
|
||||||
|
key=lambda x: x[1][2],
|
||||||
|
reverse=True))[
|
||||||
|
:backup_count]))
|
||||||
|
except Exception as e:
|
||||||
|
# Handle any exceptions that occur during Local Storage
|
||||||
|
# backup retrieval
|
||||||
|
return ['error', e, 'Local', current_company]
|
||||||
|
# For FTP
|
||||||
|
if rec.backup_destination == 'ftp':
|
||||||
|
try:
|
||||||
|
# Retrieve backups from FTP Storage and update the
|
||||||
|
# return_data dictionary with the latest backups
|
||||||
|
ftp_dict = {}
|
||||||
|
ftp_server = ftplib.FTP()
|
||||||
|
ftp_server.connect(rec.ftp_host, int(rec.ftp_port))
|
||||||
|
ftp_server.login(rec.ftp_user, rec.ftp_password)
|
||||||
|
for file in ftp_server.nlst(rec.ftp_path):
|
||||||
|
file_details = ftp_server.voidcmd("MDTM " + file)
|
||||||
|
ftp_dict[os.path.basename(
|
||||||
|
file)] = file, 'FTP Storage', datetime.strptime(
|
||||||
|
file_details[4:].strip(), "%Y%m%d%H%M%S")
|
||||||
|
ftp_server.quit()
|
||||||
|
return_data.update(dict(list(sorted(ftp_dict.items(),
|
||||||
|
key=lambda x: x[1][2],
|
||||||
|
reverse=True))[
|
||||||
|
:backup_count]))
|
||||||
|
except Exception as e:
|
||||||
|
# Handle any exceptions that occur during FTP Storage
|
||||||
|
# backup retrieval
|
||||||
|
return ['error', e, 'FTP server', current_company]
|
||||||
|
# For SFTP
|
||||||
|
if rec.backup_destination == 'sftp':
|
||||||
|
sftp_client = paramiko.SSHClient()
|
||||||
|
sftp_client.set_missing_host_key_policy(
|
||||||
|
paramiko.AutoAddPolicy())
|
||||||
|
try:
|
||||||
|
# Retrieve backups from SFTP Storage and update the
|
||||||
|
# return_data dictionary with the latest backups
|
||||||
|
sftp_dict = {}
|
||||||
|
sftp_client.connect(hostname=rec.sftp_host,
|
||||||
|
username=rec.sftp_user,
|
||||||
|
password=rec.sftp_password,
|
||||||
|
port=rec.sftp_port)
|
||||||
|
sftp_server = sftp_client.open_sftp()
|
||||||
|
sftp_server.chdir(rec.sftp_path)
|
||||||
|
file_list = sftp_server.listdir()
|
||||||
|
for file_name in file_list:
|
||||||
|
sftp_dict[file_name] = os.path.join(rec.sftp_path,
|
||||||
|
file_name), \
|
||||||
|
'SFTP Storage', datetime.fromtimestamp(
|
||||||
|
sftp_server.stat(file_name).st_mtime)
|
||||||
|
sftp_server.close()
|
||||||
|
return_data.update(dict(list(sorted(sftp_dict.items(),
|
||||||
|
key=lambda x: x[1][2],
|
||||||
|
reverse=True))[
|
||||||
|
:backup_count]))
|
||||||
|
except Exception as e:
|
||||||
|
# Handle any exceptions that occur during SFTP Storage
|
||||||
|
# backup retrieval
|
||||||
|
return ['error', e, 'SFTP server', current_company]
|
||||||
|
finally:
|
||||||
|
sftp_client.close()
|
||||||
|
# For Next Cloud
|
||||||
|
if rec.backup_destination == 'next_cloud':
|
||||||
|
try:
|
||||||
|
nxt_dixt = {}
|
||||||
|
nc_access = nextcloud_client.Client(rec.domain)
|
||||||
|
nc_access.login(rec.next_cloud_user_name,
|
||||||
|
rec.next_cloud_password)
|
||||||
|
for file_name in [file.name for file in
|
||||||
|
nc_access.list(
|
||||||
|
'/' + rec.nextcloud_folder_key)]:
|
||||||
|
link_info = nc_access.share_file_with_link(
|
||||||
|
'/' + rec.nextcloud_folder_key + '/' + file_name,
|
||||||
|
publicUpload=False)
|
||||||
|
file_info = nc_access.file_info(
|
||||||
|
'/' + rec.nextcloud_folder_key + '/' + file_name)
|
||||||
|
input_datetime = datetime.strptime(
|
||||||
|
file_info.attributes['{DAV:}getlastmodified'],
|
||||||
|
"%a, %d %b %Y %H:%M:%S %Z")
|
||||||
|
output_date_str = input_datetime.strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S")
|
||||||
|
nxt_dixt[
|
||||||
|
file_name] = link_info.get_link() + '/download', 'Nextcloud', output_date_str
|
||||||
|
return_data.update(dict(list(sorted(nxt_dixt.items(),
|
||||||
|
key=lambda x: x[1][2],
|
||||||
|
reverse=True))[
|
||||||
|
:backup_count]))
|
||||||
|
except Exception as e:
|
||||||
|
# Handle any exceptions that occur during SFTP Storage
|
||||||
|
# backup retrieval
|
||||||
|
return ['error', e, 'Nextcloud', current_company]
|
||||||
|
# For Amazon S3
|
||||||
|
if rec.backup_destination == 'amazon_s3':
|
||||||
|
try:
|
||||||
|
s3_dixt = {}
|
||||||
|
client = boto3.client('s3', aws_access_key_id=rec.aws_access_key,
|
||||||
|
aws_secret_access_key=rec.aws_secret_access_key)
|
||||||
|
region = client.get_bucket_location(Bucket=rec.bucket_file_name)
|
||||||
|
client = boto3.client(
|
||||||
|
's3', region_name=region['LocationConstraint'],
|
||||||
|
aws_access_key_id=rec.aws_access_key,
|
||||||
|
aws_secret_access_key=rec.aws_secret_access_key
|
||||||
|
)
|
||||||
|
response = client.list_objects(Bucket=rec.bucket_file_name, Prefix=rec.aws_folder_name)
|
||||||
|
for data in response['Contents']:
|
||||||
|
if data['Size'] != 0:
|
||||||
|
url = client.generate_presigned_url(
|
||||||
|
ClientMethod='get_object',
|
||||||
|
Params={'Bucket': rec.bucket_file_name,
|
||||||
|
'Key': data['Key']},ExpiresIn=3600)
|
||||||
|
s3_dixt[data['Key']] = url, 'AmazonS3', data['LastModified']
|
||||||
|
return_data.update(dict(list(sorted(s3_dixt.items(),
|
||||||
|
key=lambda x: x[1][2],
|
||||||
|
reverse=True))[
|
||||||
|
:backup_count]))
|
||||||
|
except Exception as e:
|
||||||
|
# Handle any exceptions that occur during amazon_s3 Storage
|
||||||
|
# backup retrieval
|
||||||
|
return ['error', e, 'Amazon S3', current_company]
|
||||||
|
|
||||||
|
# Return the dictionary containing the latest backups from all
|
||||||
|
# configured sources
|
||||||
|
return [return_data, current_company]
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ResConfigSettings(models.TransientModel):
|
||||||
|
""" Configure the number of backups for restore """
|
||||||
|
_inherit = 'res.config.settings'
|
||||||
|
|
||||||
|
backup_count = fields.Integer(string='Backup Count',
|
||||||
|
help='Number of backups to list for restore',
|
||||||
|
config_parameter=
|
||||||
|
'odoo_database_restore_manager.backup_count')
|
||||||
|
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 310 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 542 B |
|
After Width: | Height: | Size: 576 B |
|
After Width: | Height: | Size: 733 B |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 911 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 673 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 878 B |
|
After Width: | Height: | Size: 653 B |
|
After Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 839 B |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 427 B |
|
After Width: | Height: | Size: 627 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 988 B |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 565 B |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 912 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 35 KiB |
@ -0,0 +1,79 @@
|
|||||||
|
/* @odoo-module */
|
||||||
|
import { useService } from "@web/core/utils/hooks";
|
||||||
|
import { Component, onWillStart, useState } from "@odoo/owl";
|
||||||
|
import {registry} from '@web/core/registry';
|
||||||
|
|
||||||
|
|
||||||
|
export class DbRestoreDashboard extends Component {
|
||||||
|
setup() {
|
||||||
|
super.setup(...arguments);
|
||||||
|
this.dbDashboard = useState({ data: [] })
|
||||||
|
this.orm = useService("orm");
|
||||||
|
this.action = useService("action");
|
||||||
|
onWillStart(async () => {
|
||||||
|
await this.loadDashboardData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async loadDashboardData() {
|
||||||
|
const database_file = await this.orm.call(
|
||||||
|
'database.manager',
|
||||||
|
'action_import_files',
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
if (database_file[0] == 'error'){
|
||||||
|
this.action.doAction({
|
||||||
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'display_notification',
|
||||||
|
'params': {
|
||||||
|
'message': 'Failed to Load Files from ' + database_file[2] + ' [ ' + database_file[1] + ' ]',
|
||||||
|
'type': 'warning',
|
||||||
|
'sticky': false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.dbDashboard.data = Object.entries(database_file[0]).map(([file_name, values]) => {
|
||||||
|
return {
|
||||||
|
'file_name': file_name,
|
||||||
|
'values': values
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Function for restore the database
|
||||||
|
_onClick_restore(ev) {
|
||||||
|
this.action.doAction({
|
||||||
|
name: "Restore Database",
|
||||||
|
type: 'ir.actions.act_window',
|
||||||
|
res_model: 'database.restore',
|
||||||
|
view_mode: 'form',
|
||||||
|
view_type: 'form',
|
||||||
|
views: [[false, 'form']],
|
||||||
|
context: {
|
||||||
|
default_db_file: ev.target.value,
|
||||||
|
default_backup_location: ev.target.dataset.location
|
||||||
|
},
|
||||||
|
target: 'new',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isValidBackupName(name) {
|
||||||
|
return ['Dropbox', 'OneDrive', 'Google Drive' , 'Nextcloud', 'AmazonS3'].includes(name)
|
||||||
|
}
|
||||||
|
// Filter for location
|
||||||
|
_onchange_location(ev) {
|
||||||
|
var e = ev.target.value
|
||||||
|
var self = this;
|
||||||
|
$('.table_row').show();
|
||||||
|
$('.table_row').each(function(index, element) {
|
||||||
|
if (e == 'all_backups') {
|
||||||
|
$('.table_row').show();
|
||||||
|
}
|
||||||
|
else if ($(element)[0].children[2].innerHTML != e){
|
||||||
|
$(element).hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
registry.category("actions").add("database_manager_dashboard", DbRestoreDashboard);
|
||||||
|
DbRestoreDashboard.components = { DbRestoreDashboard };
|
||||||
|
DbRestoreDashboard.template = 'database_manager_dashboard.DbRestoreDashboard';
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
#db_location {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 15px;
|
||||||
|
box-shadow: none;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 4px 24px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.db_restore_content {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#db_restore {
|
||||||
|
color: #ffffff;
|
||||||
|
width: 100px;
|
||||||
|
background-color: #b1b1b1;
|
||||||
|
height: 30px;
|
||||||
|
border: none;
|
||||||
|
text-transform: capitalize;
|
||||||
|
margin-right: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#db_restore:hover {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.backup_download {
|
||||||
|
color: #ffffff;
|
||||||
|
width: 30px;
|
||||||
|
background-color: #b1b1b1;
|
||||||
|
height: 30px;
|
||||||
|
border: none;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.backup_download:hover {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.table-head {
|
||||||
|
background-color: #2e2e2e;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
}
|
||||||
|
.table-data {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.file_row {
|
||||||
|
--table-bg: #ffffff;
|
||||||
|
color: #000;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
}
|
||||||
|
.scrollable-table {
|
||||||
|
height: 75vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.company_image {
|
||||||
|
max-width: 200px;
|
||||||
|
max-height: 100px;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
.option {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!-- Dashboard template -->
|
||||||
|
<template id="db_restore">
|
||||||
|
<t t-name="database_manager_dashboard.DbRestoreDashboard">
|
||||||
|
<div class="container">
|
||||||
|
<section class="dashboard_main_section db_restore_section"
|
||||||
|
id="main_section_manager">
|
||||||
|
<center>
|
||||||
|
<div class="company_image"/>
|
||||||
|
</center>
|
||||||
|
<!-- Selection for filtering on basis of storage types -->
|
||||||
|
<div class="filter_location"
|
||||||
|
style="float:right; margin-right: 15px; margin-bottom: 10px;">
|
||||||
|
<select id="db_location"
|
||||||
|
t-on-change="_onchange_location"
|
||||||
|
class="form-select db_location">
|
||||||
|
<option class="option" value="all_backups">All
|
||||||
|
Backups
|
||||||
|
</option>
|
||||||
|
<option class="option" value="OneDrive">OneDrive
|
||||||
|
</option>
|
||||||
|
<option class="option" value="Dropbox">Dropbox</option>
|
||||||
|
<option class="option" value="AmazonS3">Amazon</option>
|
||||||
|
<option class="option" value="Google Drive">Google
|
||||||
|
Drive
|
||||||
|
</option>
|
||||||
|
<option class="option" value="Local Storage">Local
|
||||||
|
Storage
|
||||||
|
</option>
|
||||||
|
<option class="option" value="FTP Storage">FTP
|
||||||
|
Storage
|
||||||
|
</option>
|
||||||
|
<option class="option" value="SFTP Storage">SFTP
|
||||||
|
Storage
|
||||||
|
</option>
|
||||||
|
<option class="option" value="Nextcloud">Nextcloud
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<!-- Table to show all Backup Files -->
|
||||||
|
<div class="db_restore_content scrollable-table"
|
||||||
|
style="margin-top: 46px">
|
||||||
|
<table class="table" id="db_restore_table">
|
||||||
|
<thead class="table-head">
|
||||||
|
<tr style="text-align:center;">
|
||||||
|
<th style="text-align:center;" scope="col">SL
|
||||||
|
NO:
|
||||||
|
</th>
|
||||||
|
<th scope="col">Backup Files</th>
|
||||||
|
<th scope="col">Backup Location</th>
|
||||||
|
<th scope="col">Time (UTC)</th>
|
||||||
|
<th scope="col" style="width:190px"/>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="db_restore_files">
|
||||||
|
<tr class="table_row" t-foreach="dbDashboard.data" t-as="data" t-key="data_index">
|
||||||
|
<td class="table-data" t-out="data_index + 1"/>
|
||||||
|
<td class="table-data" t-out="data.file_name"/>
|
||||||
|
<td class="table-data" t-out="data.values[1]"/>
|
||||||
|
<td class="table-data" t-out="data.values[2]"/>
|
||||||
|
<td>
|
||||||
|
<button type="button" id="db_restore"
|
||||||
|
t-att-data-location="data.values[1]"
|
||||||
|
t-on-click="_onClick_restore"
|
||||||
|
t-att-value="data.values[0]"
|
||||||
|
class="btn btn-primary">
|
||||||
|
<i class="fa fa-floppy-o fa-fw"/>
|
||||||
|
Restore
|
||||||
|
</button>
|
||||||
|
<a t-if="isValidBackupName(data.values[1])" t-att-href="data.values[0]">
|
||||||
|
<button type="button" class="backup_download btn btn-primary">
|
||||||
|
<i class="fa fa-download o_pivot_download"/>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Database manager views -->
|
||||||
|
<record id="database_manager_action" model="ir.actions.client">
|
||||||
|
<field name="name">Database Restore Manager</field>
|
||||||
|
<field name="tag">database_manager_dashboard</field>
|
||||||
|
<field name="target">current</field>
|
||||||
|
</record>
|
||||||
|
<menuitem id="menu_db_restore_view"
|
||||||
|
parent="auto_database_backup.db_backup_menu_root"
|
||||||
|
name="Restore Manager"
|
||||||
|
action="database_manager_action"/>
|
||||||
|
</odoo>
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Set backup count view on settings -->
|
||||||
|
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">res.config.settings.view.form.inherit.odoo.database.restore.manager</field>
|
||||||
|
<field name="model">res.config.settings</field>
|
||||||
|
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//block[@id='user_default_rights']" position="inside">
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box">
|
||||||
|
<div class="o_setting_left_pane"/>
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<label string="Backup Count" for="backup_count"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
Configure the number of backups to restore
|
||||||
|
</div>
|
||||||
|
<field name="backup_count"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
from . import database_restore
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Cybrosys Technologies Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
|
||||||
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||||
|
#
|
||||||
|
# You can modify it under the terms of the GNU AFFERO
|
||||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
# (AGPL v3) along with this program.
|
||||||
|
# If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
import ftplib
|
||||||
|
import gdown
|
||||||
|
import os
|
||||||
|
import paramiko
|
||||||
|
import requests
|
||||||
|
import tempfile
|
||||||
|
import odoo
|
||||||
|
import odoo.modules.registry
|
||||||
|
from odoo import fields, models
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.http import dispatch_rpc
|
||||||
|
from odoo.service import db
|
||||||
|
from odoo.tools.misc import str2bool
|
||||||
|
|
||||||
|
|
||||||
|
class DataBaseRestore(models.TransientModel):
|
||||||
|
""" Database Restore Model """
|
||||||
|
_name = "database.restore"
|
||||||
|
_description = "Database Restore"
|
||||||
|
|
||||||
|
db_file = fields.Char(string="File", help="Restore database file")
|
||||||
|
db_name = fields.Char(string="Database Name", help="Name of the database")
|
||||||
|
db_master_pwd = fields.Char(string="Database Master Password",
|
||||||
|
help="Master Password to restore database")
|
||||||
|
backup_location = fields.Char(string="Backup Location",
|
||||||
|
help="Database backup location")
|
||||||
|
|
||||||
|
def action_restore_database(self, copy=False):
|
||||||
|
""" Function to restore the database Backup """
|
||||||
|
# Check if the admin password is insecure and update it if provided
|
||||||
|
insecure = odoo.tools.config.verify_admin_password('admin')
|
||||||
|
if insecure and self.db_master_pwd:
|
||||||
|
dispatch_rpc('db', 'change_admin_password',
|
||||||
|
["admin", self.db_master_pwd])
|
||||||
|
try:
|
||||||
|
# Check if the admin password is correct and proceed with the
|
||||||
|
# restore process
|
||||||
|
db.check_super(self.db_master_pwd)
|
||||||
|
# Create a temporary file to store the downloaded backup data
|
||||||
|
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
||||||
|
if self.backup_location == 'Google Drive':
|
||||||
|
# Retrieve backup from Google Drive using gdown library
|
||||||
|
gdown.download(self.db_file, temp_file.name, quiet=False)
|
||||||
|
elif self.backup_location in ['Dropbox', 'OneDrive', 'Nextcloud', 'AmazonS3']:
|
||||||
|
# Retrieve backup from Dropbox or OneDrive using requests
|
||||||
|
# library
|
||||||
|
response = requests.get(self.db_file, stream=True)
|
||||||
|
temp_file.write(response.content)
|
||||||
|
elif self.backup_location == 'FTP Storage':
|
||||||
|
# Retrieve backup from FTP Storage using ftplib
|
||||||
|
for rec in self.env['db.backup.configure'].search([]):
|
||||||
|
if rec.backup_destination == 'ftp' and rec.ftp_path == \
|
||||||
|
os.path.dirname(self.db_file):
|
||||||
|
ftp_server = ftplib.FTP()
|
||||||
|
ftp_server.connect(rec.ftp_host, int(rec.ftp_port))
|
||||||
|
ftp_server.login(rec.ftp_user, rec.ftp_password)
|
||||||
|
ftp_server.retrbinary("RETR " + self.db_file,
|
||||||
|
temp_file.write)
|
||||||
|
temp_file.seek(0)
|
||||||
|
ftp_server.quit()
|
||||||
|
elif self.backup_location == 'SFTP Storage':
|
||||||
|
# Retrieve backup from SFTP Storage using paramiko
|
||||||
|
for rec in self.env['db.backup.configure'].search([]):
|
||||||
|
if rec.backup_destination == 'sftp' and rec.sftp_path == \
|
||||||
|
os.path.dirname(self.db_file):
|
||||||
|
sftp_client = paramiko.SSHClient()
|
||||||
|
sftp_client.set_missing_host_key_policy(
|
||||||
|
paramiko.AutoAddPolicy())
|
||||||
|
sftp_client.connect(hostname=rec.sftp_host,
|
||||||
|
username=rec.sftp_user,
|
||||||
|
password=rec.sftp_password,
|
||||||
|
port=rec.sftp_port)
|
||||||
|
sftp_server = sftp_client.open_sftp()
|
||||||
|
sftp_server.getfo(self.db_file, temp_file)
|
||||||
|
sftp_server.close()
|
||||||
|
sftp_client.close()
|
||||||
|
elif self.backup_location == 'Local Storage':
|
||||||
|
# If the backup is stored in the local storage, set the temp
|
||||||
|
# file's name accordingly
|
||||||
|
temp_file.name = self.db_file
|
||||||
|
# Restore the database using Odoo's 'restore_db' method
|
||||||
|
db.restore_db(self.db_name, temp_file.name, str2bool(copy))
|
||||||
|
temp_file.close()
|
||||||
|
# Redirect the user to the Database Manager after successful
|
||||||
|
# restore
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_url',
|
||||||
|
'url': '/web/database/manager'
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
# Raise a UserError if any error occurs during the database
|
||||||
|
# restore process
|
||||||
|
raise UserError(
|
||||||
|
"Database restore error: %s" % (str(e) or repr(e)))
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Restore database wizard view -->
|
||||||
|
<record id="database_restore_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">database.restore.view.form</field>
|
||||||
|
<field name="model">database.restore</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="database restore wizard">
|
||||||
|
<group class="oe_title">
|
||||||
|
<field name="db_name"/>
|
||||||
|
<field name="db_master_pwd" password="True"/>
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button string="Restore" name="action_restore_database"
|
||||||
|
type="object"
|
||||||
|
class="oe_highlight" data-hotkey="q"
|
||||||
|
help="Confirm Upload"/>
|
||||||
|
<button string="Cancel" class="btn btn-secondary"
|
||||||
|
special="cancel" help="Cancel Upload"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||