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