mirror of https://github.com/jumpserver/jumpserver
perf: Account backup report
parent
9598174745
commit
f9501840cd
|
@ -9,11 +9,11 @@ from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from .base import AutomationExecutionViewSet
|
from .base import AutomationExecutionViewSet
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AccountBackupPlanViewSet', 'BackupAccountExecutionViewSet'
|
'BackupAccountViewSet', 'BackupAccountExecutionViewSet'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
class BackupAccountViewSet(OrgBulkModelViewSet):
|
||||||
model = BackupAccountAutomation
|
model = BackupAccountAutomation
|
||||||
filterset_fields = ('name',)
|
filterset_fields = ('name',)
|
||||||
search_fields = filterset_fields
|
search_fields = filterset_fields
|
||||||
|
@ -21,8 +21,13 @@ class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
||||||
|
|
||||||
|
|
||||||
class BackupAccountExecutionViewSet(AutomationExecutionViewSet):
|
class BackupAccountExecutionViewSet(AutomationExecutionViewSet):
|
||||||
serializer_class = serializers.BackupAccountExecutionSerializer
|
rbac_perms = (
|
||||||
http_method_names = ['get', 'post', 'options']
|
("list", "accounts.view_backupaccountexecution"),
|
||||||
|
("retrieve", "accounts.view_backupaccountexecution"),
|
||||||
|
("create", "accounts.add_backupaccountexecution"),
|
||||||
|
("report", "accounts.view_backupaccountexecution"),
|
||||||
|
)
|
||||||
|
|
||||||
tp = AutomationTypes.backup_account
|
tp = AutomationTypes.backup_account
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
@ -3,15 +3,17 @@ import time
|
||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models import F
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from xlsxwriter import Workbook
|
from xlsxwriter import Workbook
|
||||||
|
|
||||||
from accounts.const import AccountBackupType
|
from accounts.const import AccountBackupType
|
||||||
from accounts.models.automations.backup_account import BackupAccountAutomation
|
from accounts.models import BackupAccountAutomation, Account
|
||||||
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
|
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
|
||||||
from accounts.serializers import AccountSecretSerializer
|
from accounts.serializers import AccountSecretSerializer
|
||||||
from assets.const import AllTypes
|
from assets.const import AllTypes
|
||||||
|
from common.const import Status
|
||||||
from common.utils.file import encrypt_and_compress_zip_file, zip_files
|
from common.utils.file import encrypt_and_compress_zip_file, zip_files
|
||||||
from common.utils.timezone import local_now_filename, local_now_display
|
from common.utils.timezone import local_now_filename, local_now_display
|
||||||
from terminal.models.component.storage import ReplayStorage
|
from terminal.models.component.storage import ReplayStorage
|
||||||
|
@ -74,9 +76,9 @@ class BaseAccountHandler:
|
||||||
|
|
||||||
class AssetAccountHandler(BaseAccountHandler):
|
class AssetAccountHandler(BaseAccountHandler):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_filename(plan_name):
|
def get_filename(name):
|
||||||
filename = os.path.join(
|
filename = os.path.join(
|
||||||
PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.xlsx'
|
PATH, f'{name}-{local_now_filename()}-{time.time()}.xlsx'
|
||||||
)
|
)
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
@ -118,32 +120,41 @@ class AssetAccountHandler(BaseAccountHandler):
|
||||||
cls.handler_secret(data, section)
|
cls.handler_secret(data, section)
|
||||||
data_map.update(cls.add_rows(data, header_fields, sheet_name))
|
data_map.update(cls.add_rows(data, header_fields, sheet_name))
|
||||||
number_of_backup_accounts = _('Number of backup accounts')
|
number_of_backup_accounts = _('Number of backup accounts')
|
||||||
print('\n\033[33m- {}: {}\033[0m'.format(number_of_backup_accounts, accounts.count()))
|
print('\033[33m- {}: {}\033[0m'.format(number_of_backup_accounts, accounts.count()))
|
||||||
return data_map
|
return data_map
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupHandler:
|
class AccountBackupHandler:
|
||||||
def __init__(self, execution):
|
def __init__(self, manager, execution):
|
||||||
|
self.manager = manager
|
||||||
self.execution = execution
|
self.execution = execution
|
||||||
self.plan_name = self.execution.plan.name
|
self.name = self.execution.snapshot.get('name', '-')
|
||||||
self.is_frozen = False # 任务状态冻结标志
|
|
||||||
|
def get_accounts(self):
|
||||||
|
# TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作
|
||||||
|
types = self.execution.snapshot.get('types', [])
|
||||||
|
self.manager.summary['total_types'] = len(types)
|
||||||
|
qs = Account.objects.filter(
|
||||||
|
asset__platform__type__in=types
|
||||||
|
).annotate(type=F('asset__platform__type'))
|
||||||
|
return qs
|
||||||
|
|
||||||
def create_excel(self, section='complete'):
|
def create_excel(self, section='complete'):
|
||||||
hint = _('Generating asset or application related backup information files')
|
hint = _('Generating asset related backup information files')
|
||||||
print(
|
print(
|
||||||
'\n'
|
|
||||||
f'\033[32m>>> {hint}\033[0m'
|
f'\033[32m>>> {hint}\033[0m'
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
# Print task start date
|
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
files = []
|
files = []
|
||||||
accounts = self.execution.backup_accounts
|
accounts = self.get_accounts()
|
||||||
|
self.manager.summary['total_accounts'] = accounts.count()
|
||||||
data_map = AssetAccountHandler.create_data_map(accounts, section)
|
data_map = AssetAccountHandler.create_data_map(accounts, section)
|
||||||
if not data_map:
|
if not data_map:
|
||||||
return files
|
return files
|
||||||
|
|
||||||
filename = AssetAccountHandler.get_filename(self.plan_name)
|
filename = AssetAccountHandler.get_filename(self.name)
|
||||||
|
|
||||||
wb = Workbook(filename)
|
wb = Workbook(filename)
|
||||||
for sheet, data in data_map.items():
|
for sheet, data in data_map.items():
|
||||||
|
@ -164,19 +175,18 @@ class AccountBackupHandler:
|
||||||
return
|
return
|
||||||
recipients = User.objects.filter(id__in=list(recipients))
|
recipients = User.objects.filter(id__in=list(recipients))
|
||||||
print(
|
print(
|
||||||
'\n'
|
|
||||||
f'\033[32m>>> {_("Start sending backup emails")}\033[0m'
|
f'\033[32m>>> {_("Start sending backup emails")}\033[0m'
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
plan_name = self.plan_name
|
name = self.name
|
||||||
for user in recipients:
|
for user in recipients:
|
||||||
if not user.secret_key:
|
if not user.secret_key:
|
||||||
attachment_list = []
|
attachment_list = []
|
||||||
else:
|
else:
|
||||||
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
attachment = os.path.join(PATH, f'{name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
encrypt_and_compress_zip_file(attachment, user.secret_key, files)
|
encrypt_and_compress_zip_file(attachment, user.secret_key, files)
|
||||||
attachment_list = [attachment, ]
|
attachment_list = [attachment]
|
||||||
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
|
AccountBackupExecutionTaskMsg(name, user).publish(attachment_list)
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
os.remove(file)
|
os.remove(file)
|
||||||
|
@ -186,49 +196,37 @@ class AccountBackupHandler:
|
||||||
return
|
return
|
||||||
recipients = ReplayStorage.objects.filter(id__in=list(recipients))
|
recipients = ReplayStorage.objects.filter(id__in=list(recipients))
|
||||||
print(
|
print(
|
||||||
'\n'
|
|
||||||
'\033[32m>>> 📃 ---> sftp \033[0m'
|
'\033[32m>>> 📃 ---> sftp \033[0m'
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
plan_name = self.plan_name
|
name = self.name
|
||||||
encrypt_file = _('Encrypting files using encryption password')
|
encrypt_file = _('Encrypting files using encryption password')
|
||||||
for rec in recipients:
|
for rec in recipients:
|
||||||
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
attachment = os.path.join(PATH, f'{name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
if password:
|
if password:
|
||||||
print(f'\033[32m>>> {encrypt_file}\033[0m')
|
print(f'\033[32m>>> {encrypt_file}\033[0m')
|
||||||
encrypt_and_compress_zip_file(attachment, password, files)
|
encrypt_and_compress_zip_file(attachment, password, files)
|
||||||
else:
|
else:
|
||||||
zip_files(attachment, files)
|
zip_files(attachment, files)
|
||||||
attachment_list = attachment
|
attachment_list = attachment
|
||||||
AccountBackupByObjStorageExecutionTaskMsg(plan_name, rec).publish(attachment_list)
|
AccountBackupByObjStorageExecutionTaskMsg(name, rec).publish(attachment_list)
|
||||||
file_sent_to = _('The backup file will be sent to')
|
file_sent_to = _('The backup file will be sent to')
|
||||||
print('{}: {}({})'.format(file_sent_to, rec.name, rec.id))
|
print('{}: {}({})'.format(file_sent_to, rec.name, rec.id))
|
||||||
for file in files:
|
for file in files:
|
||||||
os.remove(file)
|
os.remove(file)
|
||||||
|
|
||||||
def step_perform_task_update(self, is_success, reason):
|
|
||||||
self.execution.reason = reason[:1024]
|
|
||||||
self.execution.is_success = is_success
|
|
||||||
self.execution.save()
|
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
is_success = False
|
|
||||||
error = '-'
|
|
||||||
try:
|
try:
|
||||||
backup_type = self.execution.snapshot.get('backup_type', AccountBackupType.email.value)
|
backup_type = self.execution.snapshot.get('backup_type', AccountBackupType.email)
|
||||||
if backup_type == AccountBackupType.email.value:
|
if backup_type == AccountBackupType.email:
|
||||||
self.backup_by_email()
|
self.backup_by_email()
|
||||||
elif backup_type == AccountBackupType.object_storage.value:
|
elif backup_type == AccountBackupType.object_storage:
|
||||||
self.backup_by_obj_storage()
|
self.backup_by_obj_storage()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.is_frozen = True
|
|
||||||
print(e)
|
|
||||||
error = str(e)
|
error = str(e)
|
||||||
else:
|
print(f'\033[31m>>> {error}\033[0m')
|
||||||
is_success = True
|
self.execution.status = Status.error
|
||||||
finally:
|
self.execution.summary['error'] = error
|
||||||
reason = error
|
|
||||||
self.step_perform_task_update(is_success, reason)
|
|
||||||
|
|
||||||
def backup_by_obj_storage(self):
|
def backup_by_obj_storage(self):
|
||||||
object_id = self.execution.snapshot.get('id')
|
object_id = self.execution.snapshot.get('id')
|
||||||
|
@ -265,7 +263,7 @@ class AccountBackupHandler:
|
||||||
f'\033[31m>>> {warn_text}\033[0m'
|
f'\033[31m>>> {warn_text}\033[0m'
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
raise RecipientsNotFound('Not Found Recipients')
|
return
|
||||||
if recipients_part_one and recipients_part_two:
|
if recipients_part_one and recipients_part_two:
|
||||||
print(f'\033[32m>>> {split_help_text}\033[0m')
|
print(f'\033[32m>>> {split_help_text}\033[0m')
|
||||||
files = self.create_excel(section='front')
|
files = self.create_excel(section='front')
|
||||||
|
@ -279,16 +277,5 @@ class AccountBackupHandler:
|
||||||
self.send_backup_mail(files, recipients)
|
self.send_backup_mail(files, recipients)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
plan_start = _('Plan start')
|
print('{}: {}'.format(_('Plan start'), local_now_display()))
|
||||||
time_cost = _('Duration')
|
|
||||||
error = _('An exception occurred during task execution')
|
|
||||||
print('{}: {}'.format(plan_start, local_now_display()))
|
|
||||||
time_start = time.time()
|
|
||||||
try:
|
|
||||||
self._run()
|
self._run()
|
||||||
except Exception as e:
|
|
||||||
print(error)
|
|
||||||
print(e)
|
|
||||||
finally:
|
|
||||||
timedelta = round((time.time() - time_start), 2)
|
|
||||||
print('{}: {}s'.format(time_cost, timedelta))
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import time
|
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from assets.automations.base.manager import BaseManager
|
from assets.automations.base.manager import BaseManager
|
||||||
from common.db.utils import safe_db_connection
|
|
||||||
from common.utils.timezone import local_now_display
|
from common.utils.timezone import local_now_display
|
||||||
from .handlers import AccountBackupHandler
|
from .handlers import AccountBackupHandler
|
||||||
|
|
||||||
|
@ -14,23 +12,19 @@ class AccountBackupManager(BaseManager):
|
||||||
def do_run(self):
|
def do_run(self):
|
||||||
execution = self.execution
|
execution = self.execution
|
||||||
account_backup_execution_being_executed = _('The account backup plan is being executed')
|
account_backup_execution_being_executed = _('The account backup plan is being executed')
|
||||||
print(f'\n\033[33m# {account_backup_execution_being_executed}\033[0m')
|
print(f'\033[33m# {account_backup_execution_being_executed}\033[0m')
|
||||||
handler = AccountBackupHandler(execution)
|
handler = AccountBackupHandler(self, execution)
|
||||||
handler.run()
|
handler.run()
|
||||||
|
|
||||||
def send_report_if_need(self):
|
def send_report_if_need(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def update_execution(self):
|
|
||||||
timedelta = int(time.time() - self.time_start)
|
|
||||||
self.execution.timedelta = timedelta
|
|
||||||
|
|
||||||
with safe_db_connection():
|
|
||||||
self.execution.save(update_fields=['timedelta', ])
|
|
||||||
|
|
||||||
def print_summary(self):
|
def print_summary(self):
|
||||||
print('\n\n' + '-' * 80)
|
print('\n\n' + '-' * 80)
|
||||||
plan_execution_end = _('Plan execution end')
|
plan_execution_end = _('Plan execution end')
|
||||||
print('{} {}\n'.format(plan_execution_end, local_now_display()))
|
print('{} {}\n'.format(plan_execution_end, local_now_display()))
|
||||||
time_cost = _('Duration')
|
time_cost = _('Duration')
|
||||||
print('{}: {}s'.format(time_cost, self.duration))
|
print('{}: {}s'.format(time_cost, self.duration))
|
||||||
|
|
||||||
|
def get_report_template(self):
|
||||||
|
return "accounts/backup_account_report.html"
|
||||||
|
|
|
@ -18,7 +18,7 @@ class ExecutionManager:
|
||||||
AutomationTypes.gather_accounts: GatherAccountsManager,
|
AutomationTypes.gather_accounts: GatherAccountsManager,
|
||||||
AutomationTypes.verify_gateway_account: VerifyGatewayAccountManager,
|
AutomationTypes.verify_gateway_account: VerifyGatewayAccountManager,
|
||||||
AutomationTypes.check_account: CheckAccountManager,
|
AutomationTypes.check_account: CheckAccountManager,
|
||||||
'backup_account': AccountBackupManager,
|
AutomationTypes.backup_account: AccountBackupManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, execution):
|
def __init__(self, execution):
|
||||||
|
|
|
@ -79,35 +79,3 @@ class BackupAccountAutomation(AccountBaseAutomation):
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.type = AutomationTypes.backup_account
|
self.type = AutomationTypes.backup_account
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
# class AccountBackupExecution(AutomationExecution):
|
|
||||||
# plan = models.ForeignKey(
|
|
||||||
# 'AccountBackupAutomation', related_name='execution', on_delete=models.CASCADE,
|
|
||||||
# verbose_name=_('Account backup plan')
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
# class Meta:
|
|
||||||
# verbose_name = _('Account backup execution')
|
|
||||||
#
|
|
||||||
# @property
|
|
||||||
# def types(self):
|
|
||||||
# types = self.snapshot.get('types')
|
|
||||||
# return types
|
|
||||||
#
|
|
||||||
# @lazyproperty
|
|
||||||
# def backup_accounts(self):
|
|
||||||
# from accounts.models import Account
|
|
||||||
# # TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作
|
|
||||||
# qs = Account.objects.filter(
|
|
||||||
# asset__platform__type__in=self.types
|
|
||||||
# ).annotate(type=F('asset__platform__type'))
|
|
||||||
# return qs
|
|
||||||
#
|
|
||||||
# @property
|
|
||||||
# def manager_type(self):
|
|
||||||
# return 'backup_account'
|
|
||||||
#
|
|
||||||
# def start(self):
|
|
||||||
# from accounts.automations.endpoint import ExecutionManager
|
|
||||||
# manager = ExecutionManager(execution=self)
|
|
||||||
# return manager.run()
|
|
||||||
|
|
|
@ -40,6 +40,9 @@ class AutomationExecution(AssetAutomationExecution):
|
||||||
|
|
||||||
('view_pushaccountexecution', _('Can view push account execution')),
|
('view_pushaccountexecution', _('Can view push account execution')),
|
||||||
('add_pushaccountexecution', _('Can add push account execution')),
|
('add_pushaccountexecution', _('Can add push account execution')),
|
||||||
|
|
||||||
|
('view_backupaccountexecution', _('Can view backup account execution')),
|
||||||
|
('add_backupaccountexecution', _('Can add backup account execution')),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
|
from accounts.const import AutomationTypes
|
||||||
from accounts.models import BackupAccountAutomation
|
from accounts.models import BackupAccountAutomation
|
||||||
from common.const.choices import Trigger
|
from common.serializers.fields import EncryptedField
|
||||||
from common.serializers.fields import LabeledChoiceField, EncryptedField
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from .base import BaseAutomationSerializer
|
from .base import BaseAutomationSerializer
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
__all__ = ['BackupAccountSerializer', 'BackupAccountExecutionSerializer']
|
__all__ = ['BackupAccountSerializer']
|
||||||
|
|
||||||
|
|
||||||
class BackupAccountSerializer(BaseAutomationSerializer):
|
class BackupAccountSerializer(BaseAutomationSerializer):
|
||||||
|
@ -41,14 +40,6 @@ class BackupAccountSerializer(BaseAutomationSerializer):
|
||||||
'types': {'label': _('Asset type')}
|
'types': {'label': _('Asset type')}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
class BackupAccountExecutionSerializer(serializers.ModelSerializer):
|
def model_type(self):
|
||||||
trigger = LabeledChoiceField(choices=Trigger.choices, label=_("Trigger mode"), read_only=True)
|
return AutomationTypes.backup_account
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = BackupAccountAutomation
|
|
||||||
read_only_fields = [
|
|
||||||
'id', 'date_start', 'timedelta', 'snapshot',
|
|
||||||
'trigger', 'reason', 'is_success', 'org_id'
|
|
||||||
]
|
|
||||||
fields = read_only_fields + ['plan']
|
|
||||||
|
|
|
@ -26,15 +26,14 @@ class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSe
|
||||||
class Meta:
|
class Meta:
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'date_created', 'date_updated', 'created_by',
|
'date_created', 'date_updated', 'created_by',
|
||||||
'periodic_display', 'executed_amount'
|
'periodic_display', 'executed_amount', 'type'
|
||||||
]
|
]
|
||||||
fields = read_only_fields + [
|
fields = read_only_fields + [
|
||||||
'id', 'name', 'is_periodic', 'interval', 'crontab', 'comment',
|
'id', 'name', 'is_periodic', 'interval', 'crontab', 'comment',
|
||||||
'type', 'accounts', 'nodes', 'assets', 'is_active',
|
'accounts', 'nodes', 'assets', 'is_active',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {'required': True},
|
'name': {'required': True},
|
||||||
'type': {'read_only': True},
|
|
||||||
'executed_amount': {'label': _('Executions')},
|
'executed_amount': {'label': _('Executions')},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +57,7 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AutomationExecution
|
model = AutomationExecution
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'trigger', 'date_start', 'date_finished', 'snapshot', 'status'
|
'trigger', 'date_start', 'date_finished', 'snapshot', 'status', 'duration'
|
||||||
]
|
]
|
||||||
fields = ['id', 'automation', 'type'] + read_only_fields
|
fields = ['id', 'automation', 'type'] + read_only_fields
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class='summary'>
|
||||||
|
<p>{% trans 'The following is a summary of account backup tasks, please review and handle them' %}</p>
|
||||||
|
<table>
|
||||||
|
<caption></caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan='2'>任务汇总:</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Task name' %}:</td>
|
||||||
|
<td>{{ execution.automation.name }} </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Date start' %}:</td>
|
||||||
|
<td>{{ execution.date_start | date:"Y/m/d H:i:s" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Date end' %}:</td>
|
||||||
|
<td>{{ execution.date_finished | date:"Y/m/d H:i:s" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Time using' %}:</td>
|
||||||
|
<td>{{ execution.duration }}s</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Account count' %}:</td>
|
||||||
|
<td>{{ summary.total_accounts }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Type count' %}:</td>
|
||||||
|
<td>{{ summary.total_types }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
max-width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #f2f2f2;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr :first-child {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result tr :first-child {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
|
@ -15,7 +15,7 @@ router.register(r'gathered-accounts', api.GatheredAccountViewSet, 'gathered-acco
|
||||||
router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
|
router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
|
||||||
router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template')
|
router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template')
|
||||||
router.register(r'account-template-secrets', api.AccountTemplateSecretsViewSet, 'account-template-secret')
|
router.register(r'account-template-secrets', api.AccountTemplateSecretsViewSet, 'account-template-secret')
|
||||||
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
|
router.register(r'account-backup-plans', api.BackupAccountViewSet, 'account-backup')
|
||||||
router.register(r'account-backup-plan-executions', api.BackupAccountExecutionViewSet, 'account-backup-execution')
|
router.register(r'account-backup-plan-executions', api.BackupAccountExecutionViewSet, 'account-backup-execution')
|
||||||
router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automation')
|
router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automation')
|
||||||
router.register(r'change-secret-executions', api.ChangSecretExecutionViewSet, 'change-secret-execution')
|
router.register(r'change-secret-executions', api.ChangSecretExecutionViewSet, 'change-secret-execution')
|
||||||
|
|
|
@ -98,7 +98,7 @@ class BaseManager:
|
||||||
self.summary = defaultdict(int)
|
self.summary = defaultdict(int)
|
||||||
self.result = defaultdict(list)
|
self.result = defaultdict(list)
|
||||||
self.duration = 0
|
self.duration = 0
|
||||||
self.status = 'success'
|
self.status = Status.success
|
||||||
|
|
||||||
def get_assets_group_by_platform(self):
|
def get_assets_group_by_platform(self):
|
||||||
return self.execution.all_assets_group_by_platform()
|
return self.execution.all_assets_group_by_platform()
|
||||||
|
|
|
@ -42,17 +42,17 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AutomationExecution
|
model = AutomationExecution
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'trigger', 'date_start', 'date_finished', 'snapshot', 'status'
|
'trigger', 'date_start', 'date_finished', 'snapshot', 'status', 'duration'
|
||||||
]
|
]
|
||||||
fields = ['id', 'automation'] + read_only_fields
|
fields = ['id', 'automation'] + read_only_fields
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_status(obj):
|
def get_status(obj):
|
||||||
if obj.status == 'success':
|
from common.const import Status
|
||||||
return _("Success")
|
status = Status._member_map_.get(obj.status)
|
||||||
elif obj.status == 'pending':
|
if status is None:
|
||||||
return _("Pending")
|
|
||||||
return obj.status
|
return obj.status
|
||||||
|
return status.label
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_snapshot(obj):
|
def get_snapshot(obj):
|
||||||
|
|
Loading…
Reference in New Issue