Unverified Commit fa715085 authored by BaiJiangJie's avatar BaiJiangJie Committed by GitHub

Merge pull request #3243 from jumpserver/gather_asset_user

[Feature] 收集资产用户
parents 737f0bc5 6fc666e6
......@@ -6,3 +6,4 @@ from .node import *
from .domain import *
from .cmd_filter import *
from .asset_user import *
from .gathered_user import *
......@@ -19,7 +19,7 @@ from rest_framework import generics
from rest_framework.response import Response
from orgs.mixins.api import OrgBulkModelViewSet
from common.mixins import IDInCacheFilterMixin
from common.mixins import CommonApiMixin
from common.utils import get_logger
from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset
......
......@@ -5,9 +5,7 @@ import random
from rest_framework import generics
from rest_framework.response import Response
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from django.db.models import Q
from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
......@@ -16,7 +14,7 @@ from ..models import Asset, AdminUser, Node
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectivity_manual
from ..utils import LabelFilter
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
logger = get_logger(__file__)
......@@ -27,7 +25,7 @@ __all__ = [
]
class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
class AssetViewSet(OrgBulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
......@@ -37,7 +35,8 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsOrgAdminOrAppUser,)
success_message = _("%(hostname)s was %(action)s successfully")
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend]
custom_filter_fields = ['admin_user_id']
def set_assets_node(self, assets):
if not isinstance(assets, list):
......@@ -54,30 +53,6 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
assets = serializer.save()
self.set_assets_node(assets)
def filter_node(self, queryset):
node_id = self.request.query_params.get("node_id")
if not node_id:
return queryset
node = get_object_or_404(Node, id=node_id)
show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true')
# 当前节点是顶层节点, 并且仅显示直接资产
if node.is_org_root() and show_current_asset:
queryset = queryset.filter(
Q(nodes=node_id) | Q(nodes__isnull=True)
).distinct()
# 当前节点是顶层节点,显示所有资产
elif node.is_org_root() and not show_current_asset:
return queryset
# 当前节点不是鼎城节点,只显示直接资产
elif not node.is_org_root() and show_current_asset:
queryset = queryset.filter(nodes=node)
else:
children = node.get_all_children(with_self=True)
queryset = queryset.filter(nodes__in=children).distinct()
return queryset
def filter_admin_user_id(self, queryset):
admin_user_id = self.request.query_params.get('admin_user_id')
if not admin_user_id:
......@@ -88,7 +63,6 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.filter_node(queryset)
queryset = self.filter_admin_user_id(queryset)
return queryset
......
......@@ -10,7 +10,7 @@ from django.http import Http404
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
from common.utils import get_object_or_none, get_logger
from common.mixins import IDInCacheFilterMixin
from common.mixins import CommonApiMixin
from ..backends import AssetUserManager
from ..models import Asset, Node, SystemUser, AdminUser
from .. import serializers
......@@ -52,7 +52,7 @@ class AssetUserSearchBackend(filters.BaseFilterBackend):
return _queryset
class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
serializer_class = serializers.AssetUserSerializer
permission_classes = [IsOrgAdminOrAppUser]
http_method_names = ['get', 'post']
......
# -*- coding: utf-8 -*-
#
from orgs.mixins.api import OrgModelViewSet
from assets.models import GatheredUser
from common.permissions import IsOrgAdmin
from ..serializers import GatheredUserSerializer
from ..filters import AssetRelatedByNodeFilterBackend
__all__ = ['GatheredUserViewSet']
class GatheredUserViewSet(OrgModelViewSet):
queryset = GatheredUser.objects.all()
serializer_class = GatheredUserSerializer
permission_classes = [IsOrgAdmin]
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filter_fields = ['asset', 'username', 'present']
search_fields = ['username', 'asset__ip', 'asset__hostname']
# -*- coding: utf-8 -*-
#
import coreapi
from rest_framework import filters
from django.db.models import Q
from common.utils import dict_get_any, is_uuid, get_object_or_none
from .models import Node, Label
class AssetByNodeFilterBackend(filters.BaseFilterBackend):
fields = ['node', 'all']
def get_schema_fields(self, view):
return [
coreapi.Field(
name=field, location='query', required=False,
type='string', example='', description='', schema=None,
)
for field in self.fields
]
@staticmethod
def is_query_all(request):
query_all_arg = request.query_params.get('all')
show_current_asset_arg = request.query_params.get('show_current_asset')
query_all = query_all_arg == '1'
if show_current_asset_arg is not None:
query_all = show_current_asset_arg != '1'
return query_all
@staticmethod
def get_query_node(request):
node_id = dict_get_any(request.query_params, ['node', 'node_id'])
if not node_id:
return None, False
if is_uuid(node_id):
node = get_object_or_none(Node, id=node_id)
else:
node = get_object_or_none(Node, key=node_id)
return node, True
@staticmethod
def perform_query(pattern, queryset):
return queryset.filter(nodes__key__regex=pattern)
def filter_queryset(self, request, queryset, view):
node, has_query_arg = self.get_query_node(request)
if not has_query_arg:
return queryset
if node is None:
return queryset.none()
query_all = self.is_query_all(request)
if query_all:
pattern = node.get_all_children_pattern(with_self=True)
else:
pattern = node.get_children_key_pattern(with_self=True)
return self.perform_query(pattern, queryset)
class LabelFilterBackend(filters.BaseFilterBackend):
sep = '#'
query_arg = 'label'
def get_schema_fields(self, view):
example = self.sep.join(['os', 'linux'])
return [
coreapi.Field(
name=self.query_arg, location='query', required=False,
type='string', example=example, description=''
)
]
def get_query_labels(self, request):
labels_query = request.query_params.getlist(self.query_arg)
if not labels_query:
return None
q = None
for kv in labels_query:
if self.sep not in kv:
continue
key, value = kv.strip().split(self.sep)[:2]
if not all([key, value]):
continue
if q:
q |= Q(name=key, value=value)
else:
q = Q(name=key, value=value)
if not q:
return []
labels = Label.objects.filter(q, is_active=True)\
.values_list('id', flat=True)
return labels
def filter_queryset(self, request, queryset, view):
labels = self.get_query_labels(request)
if labels is None:
return queryset
if len(labels) == 0:
return queryset.none()
for label in labels:
queryset = queryset.filter(labels=label)
return queryset
class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend):
@staticmethod
def perform_query(pattern, queryset):
return queryset.filter(asset__nodes__key__regex=pattern).distinct()
# Generated by Django 2.1.7 on 2019-09-17 12:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0038_auto_20190911_1634'),
]
operations = [
migrations.AddField(
model_name='authbook',
name='is_active',
field=models.BooleanField(default=True, verbose_name='Is active'),
),
]
# Generated by Django 2.1.7 on 2019-09-17 12:56
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0039_authbook_is_active'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='authbook',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='gateway',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
]
# Generated by Django 2.1.7 on 2019-09-18 04:10
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0040_auto_20190917_2056'),
]
operations = [
migrations.CreateModel(
name='GatheredUser',
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('username', models.CharField(blank=True, db_index=True, max_length=32, verbose_name='Username')),
('present', models.BooleanField(default=True, verbose_name='Present')),
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
],
options={'ordering': ['asset'], 'verbose_name': 'GatherUser'},
),
]
......@@ -9,3 +9,4 @@ from .cmd_filter import *
from .authbook import *
from .utils import *
from .authbook import *
from .gathered_user import *
......@@ -13,7 +13,7 @@ __all__ = ['AuthBook']
class AuthBookQuerySet(models.QuerySet):
def latest_version(self):
return self.filter(is_latest=True)
return self.filter(is_latest=True).filter(is_active=True)
class AuthBookManager(OrgManager):
......@@ -24,6 +24,7 @@ class AuthBook(AssetUser):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
version = models.IntegerField(default=1, verbose_name=_('Version'))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
backend = "db"
......@@ -34,25 +35,25 @@ class AuthBook(AssetUser):
class Meta:
verbose_name = _('AuthBook')
def _set_latest(self):
self._remove_pre_obj_latest()
def set_to_latest(self):
self.remove_pre_latest()
self.is_latest = True
self.save()
def _get_pre_obj(self):
def get_pre_latest(self):
pre_obj = self.__class__.objects.filter(
username=self.username, asset=self.asset
).latest_version().first()
return pre_obj
def _remove_pre_obj_latest(self):
pre_obj = self._get_pre_obj()
def remove_pre_latest(self):
pre_obj = self.get_pre_latest()
if pre_obj:
pre_obj.is_latest = False
pre_obj.save()
def _set_version(self):
pre_obj = self._get_pre_obj()
def set_version(self):
pre_obj = self.get_pre_latest()
if pre_obj:
self.version = pre_obj.version + 1
else:
......@@ -60,8 +61,8 @@ class AuthBook(AssetUser):
self.save()
def set_version_and_latest(self):
self._set_version()
self._set_latest()
self.set_version()
self.set_to_latest()
def get_related_assets(self):
return [self.asset]
......
......@@ -26,7 +26,7 @@ logger = get_logger(__file__)
class AssetUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
......
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
__all__ = ['GatheredUser']
class GatheredUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset"))
username = models.CharField(max_length=32, blank=True, db_index=True,
verbose_name=_('Username'))
present = models.BooleanField(default=True, verbose_name=_("Present"))
date_created = models.DateTimeField(auto_now_add=True,
verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True,
verbose_name=_("Date updated"))
@property
def hostname(self):
return self.asset.hostname
@property
def ip(self):
return self.asset.ip
class Meta:
verbose_name = _('GatherUser')
ordering = ['asset']
def __str__(self):
return '{}: {}'.format(self.asset.hostname, self.username)
......@@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.core.cache import cache
from common.utils import get_logger
from common.utils import get_logger, timeit
from orgs.mixins.models import OrgModelMixin, OrgManager
from orgs.utils import set_current_org, get_current_org, tmp_to_org
from orgs.models import Organization
......@@ -116,16 +116,24 @@ class FamilyMixin:
def all_children(self):
return self.get_all_children(with_self=False)
def get_children(self, with_self=False):
def get_children_key_pattern(self, with_self=False):
pattern = r'^{0}:[0-9]+$'.format(self.key)
if with_self:
pattern += r'|^{0}$'.format(self.key)
return pattern
def get_children(self, with_self=False):
pattern = self.get_children_key_pattern(with_self=with_self)
return Node.objects.filter(key__regex=pattern)
def get_all_children(self, with_self=False):
def get_all_children_pattern(self, with_self=False):
pattern = r'^{0}:'.format(self.key)
if with_self:
pattern += r'|^{0}$'.format(self.key)
return pattern
def get_all_children(self, with_self=False):
pattern = self.get_all_children_pattern(with_self=with_self)
children = Node.objects.filter(key__regex=pattern)
return children
......@@ -290,14 +298,16 @@ class NodeAssetsMixin:
return self.get_all_assets().valid()
@classmethod
def get_nodes_all_assets(cls, nodes_keys):
def get_nodes_all_assets(cls, nodes_keys, extra_assets_ids=None):
from .asset import Asset
nodes_keys = cls.clean_children_keys(nodes_keys)
pattern = set()
assets_ids = set()
for key in nodes_keys:
pattern.add(r'^{0}$|^{0}:'.format(key))
pattern = '|'.join(list(pattern))
return Asset.objects.filter(nodes__key__regex=pattern)
node_assets_ids = cls.tree().all_assets(key)
assets_ids.update(set(node_assets_ids))
if extra_assets_ids:
assets_ids.update(set(extra_assets_ids))
return Asset.objects.filter(id__in=assets_ids)
class SomeNodesMixin:
......
......@@ -9,3 +9,4 @@ from .node import *
from .domain import *
from .cmd_filter import *
from .asset_user import *
from .gathered_user import *
......@@ -53,6 +53,7 @@ class AssetUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
if not validated_data.get("name") and validated_data.get("username"):
validated_data["name"] = validated_data["username"]
instance = AssetUserManager.create(**validated_data)
instance.set_version_and_latest()
return instance
......
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
from ..models import GatheredUser
class GatheredUserSerializer(OrgResourceModelSerializerMixin):
class Meta:
model = GatheredUser
fields = [
'id', 'asset', 'hostname', 'ip', 'username',
'present', 'date_created', 'date_updated'
]
read_only_fields = fields
labels = {
'hostname': _("Hostname"),
'ip': "IP"
}
......@@ -7,13 +7,14 @@ from django.db.models.signals import (
from django.db.models.aggregates import Count
from django.dispatch import receiver
from common.utils import get_logger
from common.utils import get_logger, timeit
from common.decorator import on_transaction_commit
from .models import Asset, SystemUser, Node, AuthBook
from .models import Asset, SystemUser, Node
from .tasks import (
update_assets_hardware_info_util,
test_asset_connectivity_util,
push_system_user_to_assets
push_system_user_to_assets,
add_nodes_assets_to_system_users
)
......@@ -99,7 +100,7 @@ def on_system_user_nodes_change(sender, instance=None, action=None, model=None,
"""
if action != "post_add":
return
logger.info("System user `{}` nodes update signal recv".format(instance))
logger.info("System user nodes update signal recv: {}".format(instance))
queryset = model.objects.filter(pk__in=pk_set)
if model == Node:
......@@ -108,9 +109,7 @@ def on_system_user_nodes_change(sender, instance=None, action=None, model=None,
else:
nodes_keys = [instance.key]
system_users = queryset
assets = Node.get_nodes_all_assets(nodes_keys).values_list('id', flat=True)
for system_user in system_users:
system_user.assets.add(*tuple(assets))
add_nodes_assets_to_system_users.delay(nodes_keys, system_users)
@receiver(m2m_changed, sender=Asset.nodes.through)
......@@ -190,10 +189,3 @@ def on_asset_nodes_remove(sender, instance=None, action='', model=None,
def on_node_update_or_created(sender, **kwargs):
# 刷新节点
Node.refresh_nodes()
@receiver(post_save, sender=AuthBook)
def on_auth_book_created(sender, instance=None, created=False, **kwargs):
if created:
logger.debug('Receive create auth book object signal.')
instance.set_version_and_latest()
# ~*~ coding: utf-8 ~*~
import json
import re
import os
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _
from django.core.cache import cache
from common.utils import (
capacity_convert, sum_capacity, encrypt_password, get_logger
)
from ops.celery.decorator import (
register_as_period_task, after_app_shutdown_clean_periodic
)
from .models import SystemUser, AdminUser
from .models.utils import Connectivity
from . import const
FORKS = 10
TIMEOUT = 60
logger = get_logger(__file__)
CACHE_MAX_TIME = 60*60*2
disk_pattern = re.compile(r'^hd|sd|xvd|vd|nv')
PERIOD_TASK = os.environ.get("PERIOD_TASK", "on")
def check_asset_can_run_ansible(asset):
if not asset.is_active:
msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg)
return False
if not asset.is_support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg)
return False
return True
def clean_hosts(assets):
clean_assets = []
for asset in assets:
if not check_asset_can_run_ansible(asset):
continue
clean_assets.append(asset)
if not clean_assets:
logger.info(_("No assets matched, stop task"))
return clean_assets
def clean_hosts_by_protocol(system_user, assets):
hosts = [
asset for asset in assets
if asset.has_protocol(system_user.protocol)
]
if not hosts:
msg = _("No assets matched related system user protocol, stop task")
logger.info(msg)
return hosts
@shared_task(queue="ansible")
def set_assets_hardware_info(assets, result, **kwargs):
"""
Using ops task run result, to update asset info
@shared_task must be exit, because we using it as a task callback, is must
be a celery task also
:param assets:
:param result:
:param kwargs: {task_name: ""}
:return:
"""
result_raw = result[0]
assets_updated = []
success_result = result_raw.get('ok', {})
for asset in assets:
hostname = asset.hostname
info = success_result.get(hostname, {})
info = info.get('setup', {}).get('ansible_facts', {})
if not info:
logger.error(_("Get asset info failed: {}").format(hostname))
continue
___vendor = info.get('ansible_system_vendor', 'Unknown')
___model = info.get('ansible_product_name', 'Unknown')
___sn = info.get('ansible_product_serial', 'Unknown')
for ___cpu_model in info.get('ansible_processor', []):
if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"):
break
else:
___cpu_model = 'Unknown'
___cpu_model = ___cpu_model[:48]
___cpu_count = info.get('ansible_processor_count', 0)
___cpu_cores = info.get('ansible_processor_cores', None) or \
len(info.get('ansible_processor', []))
___cpu_vcpus = info.get('ansible_processor_vcpus', 0)
___memory = '%s %s' % capacity_convert(
'{} MB'.format(info.get('ansible_memtotal_mb'))
)
disk_info = {}
for dev, dev_info in info.get('ansible_devices', {}).items():
if disk_pattern.match(dev) and dev_info['removable'] == '0':
disk_info[dev] = dev_info['size']
___disk_total = '%.1f %s' % sum_capacity(disk_info.values())
___disk_info = json.dumps(disk_info)
# ___platform = info.get('ansible_system', 'Unknown')
___os = info.get('ansible_distribution', 'Unknown')
___os_version = info.get('ansible_distribution_version', 'Unknown')
___os_arch = info.get('ansible_architecture', 'Unknown')
___hostname_raw = info.get('ansible_hostname', 'Unknown')
for k, v in locals().items():
if k.startswith('___'):
setattr(asset, k.strip('_'), v)
asset.save()
assets_updated.append(asset)
return assets_updated
@shared_task
def update_assets_hardware_info_util(assets, task_name=None):
"""
Using ansible api to update asset hardware info
:param assets: asset seq
:param task_name: task_name running
:return: result summary ['contacted': {}, 'dark': {}]
"""
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Update some assets hardware info")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hosts = clean_hosts(assets)
if not hosts:
return {}
created_by = str(assets[0].org_id)
task, created = update_or_create_ansible_task(
task_name, hosts=hosts, tasks=tasks, created_by=created_by,
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
)
result = task.run()
set_assets_hardware_info(assets, result)
return result
@shared_task(queue="ansible")
def update_asset_hardware_info_manual(asset):
task_name = _("Update asset hardware info: {}").format(asset.hostname)
update_assets_hardware_info_util(
[asset], task_name=task_name
)
@shared_task(queue="ansible")
def update_assets_hardware_info_period():
"""
Update asset hardware period task
:return:
"""
if PERIOD_TASK != "on":
logger.debug("Period task disabled, update assets hardware info pass")
return
## ADMIN USER CONNECTIVE ##
@shared_task(queue="ansible")
def test_asset_connectivity_util(assets, task_name=None):
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Test assets connectivity")
hosts = clean_hosts(assets)
if not hosts:
return {}
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.TEST_ADMIN_USER_CONN_TASKS
},
'windows': {
'hosts': [],
'tasks': const.TEST_WINDOWS_ADMIN_USER_CONN_TASKS
}
}
for host in hosts:
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(host)
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
)
created_by = assets[0].org_id
for k, value in hosts_category.items():
if not value['hosts']:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
created_by=created_by,
)
raw, summary = task.run()
success = summary.get('success', False)
contacted = summary.get('contacted', {})
dark = summary.get('dark', {})
results_summary['success'] &= success
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
for asset in assets:
if asset.hostname in results_summary.get('dark', {}).keys():
asset.connectivity = Connectivity.unreachable()
elif asset.hostname in results_summary.get('contacted', {}).keys():
asset.connectivity = Connectivity.reachable()
else:
asset.connectivity = Connectivity.unknown()
return results_summary
@shared_task(queue="ansible")
def test_asset_connectivity_manual(asset):
task_name = _("Test assets connectivity: {}").format(asset)
summary = test_asset_connectivity_util([asset], task_name=task_name)
if summary.get('dark'):
return False, summary['dark']
else:
return True, ""
@shared_task(queue="ansible")
def test_admin_user_connectivity_util(admin_user, task_name):
"""
Test asset admin user can connect or not. Using ansible api do that
:param admin_user:
:param task_name:
:return:
"""
assets = admin_user.get_related_assets()
hosts = clean_hosts(assets)
if not hosts:
return {}
summary = test_asset_connectivity_util(hosts, task_name)
return summary
@shared_task(queue="ansible")
@register_as_period_task(interval=3600)
def test_admin_user_connectivity_period():
"""
A period task that update the ansible task period
"""
if PERIOD_TASK != "on":
logger.debug('Period task off, skip')
return
key = '_JMS_TEST_ADMIN_USER_CONNECTIVITY_PERIOD'
prev_execute_time = cache.get(key)
if prev_execute_time:
logger.debug("Test admin user connectivity, less than 40 minutes, skip")
return
cache.set(key, 1, 60*40)
admin_users = AdminUser.objects.all()
for admin_user in admin_users:
task_name = _("Test admin user connectivity period: {}").format(admin_user.name)
test_admin_user_connectivity_util(admin_user, task_name)
cache.set(key, 1, 60*40)
@shared_task(queue="ansible")
def test_admin_user_connectivity_manual(admin_user):
task_name = _("Test admin user connectivity: {}").format(admin_user.name)
test_admin_user_connectivity_util(admin_user, task_name)
return True
## System user connective ##
@shared_task(queue="ansible")
def test_system_user_connectivity_util(system_user, assets, task_name):
"""
Test system cant connect his assets or not.
:param system_user:
:param assets:
:param task_name:
:return:
"""
from ops.utils import update_or_create_ansible_task
hosts = clean_hosts(assets)
if not hosts:
return {}
hosts = clean_hosts_by_protocol(system_user, hosts)
if not hosts:
return {}
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.TEST_SYSTEM_USER_CONN_TASKS
},
'windows': {
'hosts': [],
'tasks': const.TEST_WINDOWS_SYSTEM_USER_CONN_TASKS
}
}
for host in hosts:
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(host)
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
)
for k, value in hosts_category.items():
if not value['hosts']:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
pattern='all', options=const.TASK_OPTIONS,
run_as=system_user.username, created_by=system_user.org_id,
)
raw, summary = task.run()
success = summary.get('success', False)
contacted = summary.get('contacted', {})
dark = summary.get('dark', {})
results_summary['success'] &= success
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
system_user.set_connectivity(results_summary)
return results_summary
@shared_task(queue="ansible")
def test_system_user_connectivity_manual(system_user):
task_name = _("Test system user connectivity: {}").format(system_user)
assets = system_user.get_all_assets()
return test_system_user_connectivity_util(system_user, assets, task_name)
@shared_task(queue="ansible")
def test_system_user_connectivity_a_asset(system_user, asset):
task_name = _("Test system user connectivity: {} => {}").format(
system_user, asset
)
return test_system_user_connectivity_util(system_user, [asset], task_name)
@shared_task(queue="ansible")
def test_system_user_connectivity_period():
if PERIOD_TASK != "on":
logger.debug("Period task disabled, test system user connectivity pass")
return
system_users = SystemUser.objects.all()
for system_user in system_users:
task_name = _("Test system user connectivity period: {}").format(system_user)
assets = system_user.get_all_assets()
test_system_user_connectivity_util(system_user, assets, task_name)
#### Push system user tasks ####
def get_push_linux_system_user_tasks(system_user):
tasks = [
{
'name': 'Add user {}'.format(system_user.username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present'.format(
system_user.username, system_user.shell,
),
}
},
{
'name': 'Add group {}'.format(system_user.username),
'action': {
'module': 'group',
'args': 'name={} state=present'.format(
system_user.username,
),
}
},
{
'name': 'Check home dir exists',
'action': {
'module': 'stat',
'args': 'path=/home/{}'.format(system_user.username)
},
'register': 'home_existed'
},
{
'name': "Set home dir permission",
'action': {
'module': 'file',
'args': "path=/home/{0} owner={0} group={0} mode=700".format(system_user.username)
},
'when': 'home_existed.stat.exists == true'
}
]
if system_user.password:
tasks.append({
'name': 'Set {} password'.format(system_user.username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present password={}'.format(
system_user.username, system_user.shell,
encrypt_password(system_user.password, salt="K3mIlKK"),
),
}
})
if system_user.public_key:
tasks.append({
'name': 'Set {} authorized key'.format(system_user.username),
'action': {
'module': 'authorized_key',
'args': "user={} state=present key='{}'".format(
system_user.username, system_user.public_key
)
}
})
if system_user.sudo:
sudo = system_user.sudo.replace('\r\n', '\n').replace('\r', '\n')
sudo_list = sudo.split('\n')
sudo_tmp = []
for s in sudo_list:
sudo_tmp.append(s.strip(','))
sudo = ','.join(sudo_tmp)
tasks.append({
'name': 'Set {} sudo setting'.format(system_user.username),
'action': {
'module': 'lineinfile',
'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf %s'".format(
system_user.username, sudo,
)
}
})
return tasks
def get_push_windows_system_user_tasks(system_user):
tasks = []
if system_user.password:
tasks.append({
'name': 'Add user {}'.format(system_user.username),
'action': {
'module': 'win_user',
'args': 'fullname={} '
'name={} '
'password={} '
'state=present '
'update_password=always '
'password_expired=no '
'password_never_expires=yes '
'groups="Users,Remote Desktop Users" '
'groups_action=add '
''.format(system_user.name,
system_user.username,
system_user.password),
}
})
return tasks
def get_push_system_user_tasks(host, system_user):
if host.is_unixlike():
tasks = get_push_linux_system_user_tasks(system_user)
elif host.is_windows():
tasks = get_push_windows_system_user_tasks(system_user)
else:
msg = _(
"The asset {} system platform {} does not "
"support run Ansible tasks".format(host.hostname, host.platform)
)
logger.info(msg)
tasks = []
return tasks
@shared_task(queue="ansible")
def push_system_user_util(system_user, assets, task_name):
from ops.utils import update_or_create_ansible_task
if not system_user.is_need_push():
msg = _("Push system user task skip, auto push not enable or "
"protocol is not ssh or rdp: {}").format(system_user.name)
logger.info(msg)
return {}
# Set root as system user is dangerous
if system_user.username.lower() in ["root", "administrator"]:
msg = _("For security, do not push user {}".format(system_user.username))
logger.info(msg)
return {}
hosts = clean_hosts(assets)
if not hosts:
return {}
hosts = clean_hosts_by_protocol(system_user, hosts)
if not hosts:
return {}
for host in hosts:
system_user.load_specific_asset_auth(host)
tasks = get_push_system_user_tasks(host, system_user)
if not tasks:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=[host], tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True,
created_by=system_user.org_id,
)
task.run()
@shared_task(queue="ansible")
def push_system_user_to_assets_manual(system_user):
assets = system_user.get_all_assets()
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name=task_name)
@shared_task(queue="ansible")
def push_system_user_a_asset_manual(system_user, asset):
task_name = _("Push system users to asset: {} => {}").format(
system_user.name, asset
)
return push_system_user_util(system_user, [asset], task_name=task_name)
@shared_task(queue="ansible")
def push_system_user_to_assets(system_user, assets):
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name)
@shared_task
@after_app_shutdown_clean_periodic
def test_system_user_connectability_period():
pass
@shared_task
@after_app_shutdown_clean_periodic
def test_admin_user_connectability_period():
pass
#### Test Asset user connectivity task ####
def get_test_asset_user_connectivity_tasks(asset):
if asset.is_unixlike():
tasks = const.TEST_ASSET_USER_CONN_TASKS
elif asset.is_windows():
tasks = const.TEST_WINDOWS_ASSET_USER_CONN_TASKS
else:
msg = _(
"The asset {} system platform {} does not "
"support run Ansible tasks".format(asset.hostname, asset.platform)
)
logger.info(msg)
tasks = []
return tasks
@shared_task(queue="ansible")
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
"""
:param asset_user: <AuthBook>对象
:param task_name:
:param run_as_admin:
:return:
"""
from ops.utils import update_or_create_ansible_task
if not check_asset_can_run_ansible(asset_user.asset):
return
tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
if not tasks:
logger.debug("No tasks ")
return
args = (task_name,)
kwargs = {
'hosts': [asset_user.asset], 'tasks': tasks,
'pattern': 'all', 'options': const.TASK_OPTIONS,
'created_by': asset_user.org_id,
}
if run_as_admin:
kwargs["run_as_admin"] = True
else:
kwargs["run_as"] = asset_user.username
task, created = update_or_create_ansible_task(*args, **kwargs)
raw, summary = task.run()
asset_user.set_connectivity(summary)
@shared_task(queue="ansible")
def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
"""
:param asset_users: <AuthBook>对象
"""
for asset_user in asset_users:
task_name = _("Test asset user connectivity: {}").format(asset_user)
test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=run_as_admin)
# @shared_task
# @register_as_period_task(interval=3600)
# @after_app_ready_start
# @after_app_shutdown_clean_periodic
# def push_system_user_period():
# for system_user in SystemUser.objects.all():
# push_system_user_related_nodes(system_user)
# -*- coding: utf-8 -*-
#
from .utils import *
from .common import *
from .admin_user_connectivity import *
from .asset_connectivity import *
from .asset_user_connectivity import *
from .gather_asset_users import *
from .gather_asset_hardware_info import *
from .push_system_user import *
from .system_user_connectivity import *
# ~*~ coding: utf-8 ~*~
from celery import shared_task
from django.utils.translation import ugettext as _
from django.core.cache import cache
from common.utils import get_logger
from ops.celery.decorator import register_as_period_task
from ..models import AdminUser
from .utils import clean_hosts
from .asset_connectivity import test_asset_connectivity_util
from . import const
logger = get_logger(__file__)
__all__ = [
'test_admin_user_connectivity_util', 'test_admin_user_connectivity_manual',
'test_admin_user_connectivity_period'
]
@shared_task(queue="ansible")
def test_admin_user_connectivity_util(admin_user, task_name):
"""
Test asset admin user can connect or not. Using ansible api do that
:param admin_user:
:param task_name:
:return:
"""
assets = admin_user.get_related_assets()
hosts = clean_hosts(assets)
if not hosts:
return {}
summary = test_asset_connectivity_util(hosts, task_name)
return summary
@shared_task(queue="ansible")
@register_as_period_task(interval=3600)
def test_admin_user_connectivity_period():
"""
A period task that update the ansible task period
"""
if const.PERIOD_TASK_ENABLED:
logger.debug('Period task off, skip')
return
key = '_JMS_TEST_ADMIN_USER_CONNECTIVITY_PERIOD'
prev_execute_time = cache.get(key)
if prev_execute_time:
logger.debug("Test admin user connectivity, less than 40 minutes, skip")
return
cache.set(key, 1, 60*40)
admin_users = AdminUser.objects.all()
for admin_user in admin_users:
task_name = _("Test admin user connectivity period: {}").format(admin_user.name)
test_admin_user_connectivity_util(admin_user, task_name)
cache.set(key, 1, 60*40)
@shared_task(queue="ansible")
def test_admin_user_connectivity_manual(admin_user):
task_name = _("Test admin user connectivity: {}").format(admin_user.name)
test_admin_user_connectivity_util(admin_user, task_name)
return True
# ~*~ coding: utf-8 ~*~
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import get_logger
from ..models.utils import Connectivity
from . import const
from .utils import clean_hosts
logger = get_logger(__file__)
__all__ = ['test_asset_connectivity_util', 'test_asset_connectivity_manual']
@shared_task(queue="ansible")
def test_asset_connectivity_util(assets, task_name=None):
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Test assets connectivity")
hosts = clean_hosts(assets)
if not hosts:
return {}
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.TEST_ADMIN_USER_CONN_TASKS
},
'windows': {
'hosts': [],
'tasks': const.TEST_WINDOWS_ADMIN_USER_CONN_TASKS
}
}
for host in hosts:
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(host)
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
)
created_by = assets[0].org_id
for k, value in hosts_category.items():
if not value['hosts']:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
created_by=created_by,
)
raw, summary = task.run()
success = summary.get('success', False)
contacted = summary.get('contacted', {})
dark = summary.get('dark', {})
results_summary['success'] &= success
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
for asset in assets:
if asset.hostname in results_summary.get('dark', {}).keys():
asset.connectivity = Connectivity.unreachable()
elif asset.hostname in results_summary.get('contacted', {}).keys():
asset.connectivity = Connectivity.reachable()
else:
asset.connectivity = Connectivity.unknown()
return results_summary
@shared_task(queue="ansible")
def test_asset_connectivity_manual(asset):
task_name = _("Test assets connectivity: {}").format(asset)
summary = test_asset_connectivity_util([asset], task_name=task_name)
if summary.get('dark'):
return False, summary['dark']
else:
return True, ""
# ~*~ coding: utf-8 ~*~
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import get_logger
from . import const
from .utils import check_asset_can_run_ansible
logger = get_logger(__file__)
__all__ = [
'test_asset_user_connectivity_util', 'test_asset_users_connectivity_manual',
'get_test_asset_user_connectivity_tasks',
]
def get_test_asset_user_connectivity_tasks(asset):
if asset.is_unixlike():
tasks = const.TEST_ASSET_USER_CONN_TASKS
elif asset.is_windows():
tasks = const.TEST_WINDOWS_ASSET_USER_CONN_TASKS
else:
msg = _(
"The asset {} system platform {} does not "
"support run Ansible tasks".format(asset.hostname, asset.platform)
)
logger.info(msg)
tasks = []
return tasks
@shared_task(queue="ansible")
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
"""
:param asset_user: <AuthBook>对象
:param task_name:
:param run_as_admin:
:return:
"""
from ops.utils import update_or_create_ansible_task
if not check_asset_can_run_ansible(asset_user.asset):
return
tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
if not tasks:
logger.debug("No tasks ")
return
args = (task_name,)
kwargs = {
'hosts': [asset_user.asset], 'tasks': tasks,
'pattern': 'all', 'options': const.TASK_OPTIONS,
'created_by': asset_user.org_id,
}
if run_as_admin:
kwargs["run_as_admin"] = True
else:
kwargs["run_as"] = asset_user.username
task, created = update_or_create_ansible_task(*args, **kwargs)
raw, summary = task.run()
asset_user.set_connectivity(summary)
@shared_task(queue="ansible")
def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
"""
:param asset_users: <AuthBook>对象
"""
for asset_user in asset_users:
task_name = _("Test asset user connectivity: {}").format(asset_user)
test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=run_as_admin)
# -*- coding: utf-8 -*-
#
from celery import shared_task
__all__ = ['add_nodes_assets_to_system_users']
@shared_task
def add_nodes_assets_to_system_users(nodes_keys, system_users):
from ..models import Node
assets = Node.get_nodes_all_assets(nodes_keys).values_list('id', flat=True)
for system_user in system_users:
system_user.assets.add(*tuple(assets))
# -*- coding: utf-8 -*-
#
import os
from django.utils.translation import ugettext_lazy as _
PERIOD_TASK_ENABLED = os.environ.get("PERIOD_TASK", "on") == 'on'
UPDATE_ASSETS_HARDWARE_TASKS = [
{
'name': "setup",
......@@ -79,3 +83,22 @@ CONNECTIVITY_CHOICES = (
(CONN_UNKNOWN, _("Unknown")),
)
GATHER_ASSET_USERS_TASKS = [
{
"name": "gather host users",
"action": {
"module": 'getent',
"args": "database=passwd"
},
},
]
GATHER_ASSET_USERS_TASKS_WINDOWS = [
{
"name": "gather windows host users",
"action": {
"module": 'win_shell',
"args": "net user"
}
}
]
# -*- coding: utf-8 -*-
#
import json
import re
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import (
capacity_convert, sum_capacity, get_logger
)
from . import const
from .utils import clean_hosts
logger = get_logger(__file__)
disk_pattern = re.compile(r'^hd|sd|xvd|vd|nv')
__all__ = [
'update_assets_hardware_info_util', 'update_asset_hardware_info_manual',
'update_assets_hardware_info_period',
]
def set_assets_hardware_info(assets, result, **kwargs):
"""
Using ops task run result, to update asset info
@shared_task must be exit, because we using it as a task callback, is must
be a celery task also
:param assets:
:param result:
:param kwargs: {task_name: ""}
:return:
"""
result_raw = result[0]
assets_updated = []
success_result = result_raw.get('ok', {})
for asset in assets:
hostname = asset.hostname
info = success_result.get(hostname, {})
info = info.get('setup', {}).get('ansible_facts', {})
if not info:
logger.error(_("Get asset info failed: {}").format(hostname))
continue
___vendor = info.get('ansible_system_vendor', 'Unknown')
___model = info.get('ansible_product_name', 'Unknown')
___sn = info.get('ansible_product_serial', 'Unknown')
for ___cpu_model in info.get('ansible_processor', []):
if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"):
break
else:
___cpu_model = 'Unknown'
___cpu_model = ___cpu_model[:48]
___cpu_count = info.get('ansible_processor_count', 0)
___cpu_cores = info.get('ansible_processor_cores', None) or \
len(info.get('ansible_processor', []))
___cpu_vcpus = info.get('ansible_processor_vcpus', 0)
___memory = '%s %s' % capacity_convert(
'{} MB'.format(info.get('ansible_memtotal_mb'))
)
disk_info = {}
for dev, dev_info in info.get('ansible_devices', {}).items():
if disk_pattern.match(dev) and dev_info['removable'] == '0':
disk_info[dev] = dev_info['size']
___disk_total = '%.1f %s' % sum_capacity(disk_info.values())
___disk_info = json.dumps(disk_info)
# ___platform = info.get('ansible_system', 'Unknown')
___os = info.get('ansible_distribution', 'Unknown')
___os_version = info.get('ansible_distribution_version', 'Unknown')
___os_arch = info.get('ansible_architecture', 'Unknown')
___hostname_raw = info.get('ansible_hostname', 'Unknown')
for k, v in locals().items():
if k.startswith('___'):
setattr(asset, k.strip('_'), v)
asset.save()
assets_updated.append(asset)
return assets_updated
@shared_task
def update_assets_hardware_info_util(assets, task_name=None):
"""
Using ansible api to update asset hardware info
:param assets: asset seq
:param task_name: task_name running
:return: result summary ['contacted': {}, 'dark': {}]
"""
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Update some assets hardware info")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hosts = clean_hosts(assets)
if not hosts:
return {}
created_by = str(assets[0].org_id)
task, created = update_or_create_ansible_task(
task_name, hosts=hosts, tasks=tasks, created_by=created_by,
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
)
result = task.run()
set_assets_hardware_info(assets, result)
return True
@shared_task(queue="ansible")
def update_asset_hardware_info_manual(asset):
task_name = _("Update asset hardware info: {}").format(asset.hostname)
update_assets_hardware_info_util(
[asset], task_name=task_name
)
@shared_task(queue="ansible")
def update_assets_hardware_info_period():
"""
Update asset hardware period task
:return:
"""
if not const.PERIOD_TASK_ENABLED:
logger.debug("Period task disabled, update assets hardware info pass")
return
# ~*~ coding: utf-8 ~*~
import re
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _
from orgs.utils import tmp_to_org
from common.utils import get_logger
from ..models import GatheredUser, Node
from .utils import clean_hosts
from . import const
__all__ = ['gather_asset_users', 'gather_nodes_asset_users']
logger = get_logger(__name__)
space = re.compile('\s+')
ignore_login_shell = re.compile(r'nologin$|sync$|shutdown$|halt$')
def parse_linux_result_to_users(result):
task_result = {}
for task_name, raw in result.items():
res = raw.get('ansible_facts', {}).get('getent_passwd')
if res:
task_result = res
break
if not task_result or not isinstance(task_result, dict):
return []
users = []
for username, attr in task_result.items():
if ignore_login_shell.search(attr[-1]):
continue
users.append(username)
return users
def parse_windows_result_to_users(result):
task_result = []
for task_name, raw in result.items():
res = raw.get('stdout_lines', {})
if res:
task_result = res
break
if not task_result:
return []
users = []
for i in range(4):
task_result.pop(0)
for i in range(2):
task_result.pop()
for line in task_result:
user = space.split(line)
if user[0]:
users.append(user[0])
return users
def add_asset_users(assets, results):
assets_map = {a.hostname: a for a in assets}
parser_map = {
'linux': parse_linux_result_to_users,
'windows': parse_windows_result_to_users
}
assets_users_map = {}
for platform, platform_results in results.items():
for hostname, res in platform_results.items():
parse = parser_map.get(platform)
users = parse(res)
logger.debug('Gathered host users: {} {}'.format(hostname, users))
asset = assets_map.get(hostname)
if not asset:
continue
assets_users_map[asset] = users
for asset, users in assets_users_map.items():
with tmp_to_org(asset.org_id):
GatheredUser.objects.filter(asset=asset, present=True)\
.update(present=False)
for username in users:
defaults = {'asset': asset, 'username': username, 'present': True}
GatheredUser.objects.update_or_create(
defaults=defaults, asset=asset, username=username,
)
@shared_task(queue="ansible")
def gather_asset_users(assets, task_name=None):
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Gather assets users")
assets = clean_hosts(assets)
if not assets:
return
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.GATHER_ASSET_USERS_TASKS
},
'windows': {
'hosts': [],
'tasks': const.GATHER_ASSET_USERS_TASKS_WINDOWS
}
}
for asset in assets:
hosts_list = hosts_category['windows']['hosts'] if asset.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(asset)
results = {'linux': defaultdict(dict), 'windows': defaultdict(dict)}
for k, value in hosts_category.items():
if not value['hosts']:
continue
_task_name = '{}: {}'.format(task_name, k)
task, created = update_or_create_ansible_task(
task_name=_task_name, hosts=value['hosts'], tasks=value['tasks'],
pattern='all', options=const.TASK_OPTIONS,
run_as_admin=True, created_by=value['hosts'][0].org_id,
)
raw, summary = task.run()
results[k].update(raw['ok'])
add_asset_users(assets, results)
@shared_task(queue="ansible")
def gather_nodes_asset_users(nodes_key):
assets = Node.get_nodes_all_assets(nodes_key)
assets_groups_by_100 = [assets[i:i+100] for i in range(0, len(assets), 100)]
for _assets in assets_groups_by_100:
gather_asset_users(_assets)
# ~*~ coding: utf-8 ~*~
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import encrypt_password, get_logger
from . import const
from .utils import clean_hosts_by_protocol, clean_hosts
logger = get_logger(__file__)
__all__ = [
'push_system_user_util', 'push_system_user_to_assets',
'push_system_user_to_assets_manual', 'push_system_user_a_asset_manual',
]
def get_push_linux_system_user_tasks(system_user):
tasks = [
{
'name': 'Add user {}'.format(system_user.username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present'.format(
system_user.username, system_user.shell,
),
}
},
{
'name': 'Add group {}'.format(system_user.username),
'action': {
'module': 'group',
'args': 'name={} state=present'.format(
system_user.username,
),
}
},
{
'name': 'Check home dir exists',
'action': {
'module': 'stat',
'args': 'path=/home/{}'.format(system_user.username)
},
'register': 'home_existed'
},
{
'name': "Set home dir permission",
'action': {
'module': 'file',
'args': "path=/home/{0} owner={0} group={0} mode=700".format(system_user.username)
},
'when': 'home_existed.stat.exists == true'
}
]
if system_user.password:
tasks.append({
'name': 'Set {} password'.format(system_user.username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present password={}'.format(
system_user.username, system_user.shell,
encrypt_password(system_user.password, salt="K3mIlKK"),
),
}
})
if system_user.public_key:
tasks.append({
'name': 'Set {} authorized key'.format(system_user.username),
'action': {
'module': 'authorized_key',
'args': "user={} state=present key='{}'".format(
system_user.username, system_user.public_key
)
}
})
if system_user.sudo:
sudo = system_user.sudo.replace('\r\n', '\n').replace('\r', '\n')
sudo_list = sudo.split('\n')
sudo_tmp = []
for s in sudo_list:
sudo_tmp.append(s.strip(','))
sudo = ','.join(sudo_tmp)
tasks.append({
'name': 'Set {} sudo setting'.format(system_user.username),
'action': {
'module': 'lineinfile',
'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf %s'".format(
system_user.username, sudo,
)
}
})
return tasks
def get_push_windows_system_user_tasks(system_user):
tasks = []
if not system_user.password:
return tasks
tasks.append({
'name': 'Add user {}'.format(system_user.username),
'action': {
'module': 'win_user',
'args': 'fullname={} '
'name={} '
'password={} '
'state=present '
'update_password=always '
'password_expired=no '
'password_never_expires=yes '
'groups="Users,Remote Desktop Users" '
'groups_action=add '
''.format(system_user.name,
system_user.username,
system_user.password),
}
})
return tasks
def get_push_system_user_tasks(host, system_user):
if host.is_unixlike():
tasks = get_push_linux_system_user_tasks(system_user)
elif host.is_windows():
tasks = get_push_windows_system_user_tasks(system_user)
else:
msg = _(
"The asset {} system platform {} does not "
"support run Ansible tasks".format(host.hostname, host.platform)
)
logger.info(msg)
tasks = []
return tasks
@shared_task(queue="ansible")
def push_system_user_util(system_user, assets, task_name):
from ops.utils import update_or_create_ansible_task
if not system_user.is_need_push():
msg = _("Push system user task skip, auto push not enable or "
"protocol is not ssh or rdp: {}").format(system_user.name)
logger.info(msg)
return {}
# Set root as system user is dangerous
if system_user.username.lower() in ["root", "administrator"]:
msg = _("For security, do not push user {}".format(system_user.username))
logger.info(msg)
return {}
hosts = clean_hosts(assets)
if not hosts:
return {}
hosts = clean_hosts_by_protocol(system_user, hosts)
if not hosts:
return {}
for host in hosts:
system_user.load_specific_asset_auth(host)
tasks = get_push_system_user_tasks(host, system_user)
if not tasks:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=[host], tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True,
created_by=system_user.org_id,
)
task.run()
@shared_task(queue="ansible")
def push_system_user_to_assets_manual(system_user):
assets = system_user.get_all_assets()
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name=task_name)
@shared_task(queue="ansible")
def push_system_user_a_asset_manual(system_user, asset):
task_name = _("Push system users to asset: {} => {}").format(
system_user.name, asset
)
return push_system_user_util(system_user, [asset], task_name=task_name)
@shared_task(queue="ansible")
def push_system_user_to_assets(system_user, assets):
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name)
# @shared_task
# @register_as_period_task(interval=3600)
# @after_app_ready_start
# @after_app_shutdown_clean_periodic
# def push_system_user_period():
# for system_user in SystemUser.objects.all():
# push_system_user_related_nodes(system_user)
\ No newline at end of file
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import get_logger
from ..models import SystemUser
from . import const
from .utils import clean_hosts, clean_hosts_by_protocol
logger = get_logger(__name__)
__all__ = [
'test_system_user_connectivity_util', 'test_system_user_connectivity_manual',
'test_system_user_connectivity_period', 'test_system_user_connectivity_a_asset',
]
@shared_task(queue="ansible")
def test_system_user_connectivity_util(system_user, assets, task_name):
"""
Test system cant connect his assets or not.
:param system_user:
:param assets:
:param task_name:
:return:
"""
from ops.utils import update_or_create_ansible_task
hosts = clean_hosts(assets)
if not hosts:
return {}
hosts = clean_hosts_by_protocol(system_user, hosts)
if not hosts:
return {}
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.TEST_SYSTEM_USER_CONN_TASKS
},
'windows': {
'hosts': [],
'tasks': const.TEST_WINDOWS_SYSTEM_USER_CONN_TASKS
}
}
for host in hosts:
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(host)
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
)
for k, value in hosts_category.items():
if not value['hosts']:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
pattern='all', options=const.TASK_OPTIONS,
run_as=system_user.username, created_by=system_user.org_id,
)
raw, summary = task.run()
success = summary.get('success', False)
contacted = summary.get('contacted', {})
dark = summary.get('dark', {})
results_summary['success'] &= success
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
system_user.set_connectivity(results_summary)
return results_summary
@shared_task(queue="ansible")
def test_system_user_connectivity_manual(system_user):
task_name = _("Test system user connectivity: {}").format(system_user)
assets = system_user.get_all_assets()
return test_system_user_connectivity_util(system_user, assets, task_name)
@shared_task(queue="ansible")
def test_system_user_connectivity_a_asset(system_user, asset):
task_name = _("Test system user connectivity: {} => {}").format(
system_user, asset
)
return test_system_user_connectivity_util(system_user, [asset], task_name)
@shared_task(queue="ansible")
def test_system_user_connectivity_period():
if not const.PERIOD_TASK_ENABLED:
logger.debug("Period task disabled, test system user connectivity pass")
return
system_users = SystemUser.objects.all()
for system_user in system_users:
task_name = _("Test system user connectivity period: {}").format(system_user)
assets = system_user.get_all_assets()
test_system_user_connectivity_util(system_user, assets, task_name)
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from common.utils import get_logger
logger = get_logger(__file__)
__all__ = [
'check_asset_can_run_ansible', 'clean_hosts', 'clean_hosts_by_protocol'
]
def check_asset_can_run_ansible(asset):
if not asset.is_active:
msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg)
return False
if not asset.is_support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg)
return False
return True
def clean_hosts(assets):
clean_assets = []
for asset in assets:
if not check_asset_can_run_ansible(asset):
continue
clean_assets.append(asset)
if not clean_assets:
logger.info(_("No assets matched, stop task"))
return clean_assets
def clean_hosts_by_protocol(system_user, assets):
hosts = [
asset for asset in assets
if asset.has_protocol(system_user.protocol)
]
if not hosts:
msg = _("No assets matched related system user protocol, stop task")
logger.info(msg)
return hosts
......@@ -85,7 +85,7 @@
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels">
{% for label in labels %}
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
<li><a style="font-weight: bolder">{{ label.name }}#{{ label.value }}</a></li>
{% endfor %}
</ul>
</div>
......@@ -171,9 +171,13 @@ function initTable() {
],
ajax_url: '{% url "api-assets:asset-list" %}',
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "id"}, {data: "hostname"}, {data: "ip"},
{data: "cpu_cores", orderable: false},
{data: "connectivity", orderable: false}, {data: "id", orderable: false }
{
data: "connectivity",
orderable: false,
width: '60px'
}, {data: "id", orderable: false}
],
op_html: $('#actions').html()
};
......@@ -271,7 +275,7 @@ $(document).ready(function(){
setAssetModalOptions(modalOption);
})
.on('click', '.labels li', function () {
var val = $(this).text();
var val = 'label:' + $(this).text();
$("#asset_list_table_filter input").val(val);
asset_table.search(val).draw();
})
......
......@@ -24,7 +24,7 @@
var treeUrl = "{% url 'api-perms:my-nodes-children-as-tree' %}?&cache_policy=1";
var assetTableUrl = "{% url 'api-perms:my-assets' %}?cache_policy=1";
var selectUrl = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1&all=1';
var systemUsersUrl = "{% url 'api-perms:my-asset-system-users' asset_id=DEFAULT_PK %}";
var systemUsersUrl = "{% url 'api-perms:my-asset-system-users' asset_id=DEFAULT_PK %}?cache_policy=1";
var showAssetHref = false; // Need input default true
var actions = {
targets: 4, createdCell: function (td, cellData) {
......
......@@ -21,6 +21,7 @@ router.register(r'gateways', api.GatewayViewSet, 'gateway')
router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter')
router.register(r'asset-users', api.AssetUserViewSet, 'asset-user')
router.register(r'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
......
......@@ -24,37 +24,6 @@ def get_system_user_by_id(id):
return system_user
class LabelFilterMixin:
def get_filter_labels_ids(self):
query_params = self.request.query_params
query_keys = query_params.keys()
all_label_keys = Label.objects.values_list('name', flat=True)
valid_keys = set(all_label_keys) & set(query_keys)
if not valid_keys:
return []
labels_query = [
{"name": key, "value": query_params[key]}
for key in valid_keys
]
args = [Q(**kwargs) for kwargs in labels_query]
args = reduce(lambda x, y: x | y, args)
labels_id = Label.objects.filter(args).values_list('id', flat=True)
return labels_id
class LabelFilter(LabelFilterMixin):
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
labels_ids = self.get_filter_labels_ids()
if not labels_ids:
return queryset
for labels_id in labels_ids:
queryset = queryset.filter(labels=labels_id)
return queryset
class TreeService(Tree):
tag_sep = ' / '
cache_key = '_NODE_FULL_TREE'
......
# -*- coding: utf-8 -*-
#
import coreapi
from rest_framework import filters
from rest_framework.fields import DateTimeField
from rest_framework.serializers import ValidationError
from django.core.cache import cache
import logging
__all__ = ["DatetimeRangeFilter"]
from . import const
__all__ = ["DatetimeRangeFilter", "IDSpmFilter", "CustomFilter"]
class DatetimeRangeFilter(filters.BaseFilterBackend):
......@@ -40,3 +44,52 @@ class DatetimeRangeFilter(filters.BaseFilterBackend):
if kwargs:
queryset = queryset.filter(**kwargs)
return queryset
class IDSpmFilter(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
name='spm', location='query', required=False,
type='string', example='',
description='Pre post objects id get spm id, then using filter'
)
]
def filter_queryset(self, request, queryset, view):
spm = request.query_params.get('spm')
if not spm:
return queryset
cache_key = const.KEY_CACHE_RESOURCES_ID.format(spm)
resources_id = cache.get(cache_key)
if not resources_id or not isinstance(resources_id, list):
queryset = queryset.none()
return queryset
queryset = queryset.filter(id__in=resources_id)
return queryset
class CustomFilter(filters.BaseFilterBackend):
def get_schema_fields(self, view):
fields = []
defaults = dict(
location='query', required=False,
type='string', example='',
description=''
)
if not hasattr(view, 'custom_filter_fields'):
return []
for field in view.custom_filter_fields:
if isinstance(field, str):
defaults['name'] = field
elif isinstance(field, dict):
defaults.update(field)
else:
continue
fields.append(coreapi.Field(**defaults))
return fields
def filter_queryset(self, request, queryset, view):
return queryset
# -*- coding: utf-8 -*-
#
from django.http import JsonResponse
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from rest_framework.settings import api_settings
from ..const import KEY_CACHE_RESOURCES_ID
from ..filters import IDSpmFilter, CustomFilter
__all__ = [
"JSONResponseMixin", "IDInCacheFilterMixin", "IDExportFilterMixin",
"IDInFilterMixin", "ApiMessageMixin"
"JSONResponseMixin", "CommonApiMixin",
"IDSpmFilterMixin", "CommonApiMixin",
]
......@@ -20,69 +18,31 @@ class JSONResponseMixin(object):
return JsonResponse(context)
class IDInFilterMixin(object):
def filter_queryset(self, queryset):
queryset = super(IDInFilterMixin, self).filter_queryset(queryset)
id_list = self.request.query_params.get('id__in')
if id_list:
import json
try:
ids = json.loads(id_list)
except Exception as e:
return queryset
if isinstance(ids, list):
queryset = queryset.filter(id__in=ids)
return queryset
class IDSpmFilterMixin:
def get_filter_backends(self):
backends = super().get_filter_backends()
backends.append(IDSpmFilter)
return backends
class IDInCacheFilterMixin(object):
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
spm = self.request.query_params.get('spm')
if not spm:
return queryset
cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
resources_id = cache.get(cache_key)
if not resources_id or not isinstance(resources_id, list):
queryset = queryset.none()
return queryset
queryset = queryset.filter(id__in=resources_id)
return queryset
class ExtraFilterFieldsMixin:
default_added_filters = [CustomFilter, IDSpmFilter]
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
extra_filter_fields = []
extra_filter_backends = []
def get_filter_backends(self):
if self.filter_backends != self.__class__.filter_backends:
return self.filter_backends
return list(self.filter_backends) + \
self.default_added_filters + \
list(self.extra_filter_backends)
class IDExportFilterMixin(object):
def filter_queryset(self, queryset):
# 下载导入模版
if self.request.query_params.get('template') == 'import':
return []
else:
return super(IDExportFilterMixin, self).filter_queryset(queryset)
class ApiMessageMixin:
success_message = _("%(name)s was %(action)s successfully")
_action_map = {"create": _("create"), "update": _("update")}
for backend in self.get_filter_backends():
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def get_success_message(self, cleaned_data):
if not isinstance(cleaned_data, dict):
return ''
data = {k: v for k, v in cleaned_data.items()}
action = getattr(self, "action", "create")
data["action"] = self._action_map.get(action)
try:
message = self.success_message % data
except:
message = ''
return message
def dispatch(self, request, *args, **kwargs):
resp = super().dispatch(request, *args, **kwargs)
if request.method.lower() in ("get", "delete", "patch"):
return resp
if resp.status_code >= 400:
return resp
message = self.get_success_message(resp.data)
if message:
messages.success(request, message)
return resp
class CommonApiMixin(ExtraFilterFieldsMixin):
pass
......@@ -8,7 +8,6 @@ import datetime
import uuid
from functools import wraps
import time
import copy
import ipaddress
......@@ -199,3 +198,31 @@ def timeit(func):
logger.debug(msg)
return result
return wrapper
def group_obj_by_count(objs, count=50):
objs_grouped = [
objs[i:i + count] for i in range(0, len(objs), count)
]
return objs_grouped
def dict_get_any(d, keys):
for key in keys:
value = d.get(key)
if value:
return value
return None
class lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, cls):
if instance is None:
return self
else:
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value
\ No newline at end of file
# -*- coding: utf-8 -*-
#
from django.http import HttpResponse
from django.conf import settings
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import csrf_exempt
from proxy.views import proxy_view
flower_url = settings.FLOWER_URL
@csrf_exempt
def celery_flower_view(request, path):
if not request.user.is_superuser:
return HttpResponse("Forbidden")
remote_url = 'http://{}/{}'.format(flower_url, path)
try:
response = proxy_view(request, remote_url)
except Exception as e:
msg = _("<h1>Flow service unavailable, check it</h1>") + \
'<br><br> <div>{}</div>'.format(e)
response = HttpResponse(msg)
return response
......@@ -382,6 +382,8 @@ defaults = {
'SYSLOG_ADDR': '', # '192.168.0.1:514'
'SYSLOG_FACILITY': 'user',
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
'WINDOWS_SSH_DEFAULT_SHELL': 'cmd',
'FLOWER_URL': "127.0.0.1:5555"
}
......
......@@ -622,3 +622,5 @@ ASSETS_PERM_CACHE_TIME = CONFIG.ASSETS_PERM_CACHE_TIME
BACKEND_ASSET_USER_AUTH_VAULT = False
PERM_SINGLE_ASSET_TO_UNGROUP_NODE = CONFIG.PERM_SINGLE_ASSET_TO_UNGROUP_NODE
WINDOWS_SSH_DEFAULT_SHELL = CONFIG.WINDOWS_SSH_DEFAULT_SHELL
FLOWER_URL = CONFIG.FLOWER_URL
......@@ -33,6 +33,21 @@ class CustomSwaggerAutoSchema(SwaggerAutoSchema):
operation.summary = operation.operation_id
return operation
def get_filter_parameters(self):
if not self.should_filter():
return []
fields = []
if hasattr(self.view, 'get_filter_backends'):
backends = self.view.get_filter_backends()
elif hasattr(self.view, 'filter_backends'):
backends = self.view.filter_backends
else:
backends = []
for filter_backend in backends:
fields += self.probe_inspectors(self.filter_inspectors, 'get_filter_parameters', filter_backend()) or []
return fields
def get_swagger_view(version='v1'):
from .urls import api_v1, api_v2
......
......@@ -7,7 +7,9 @@ from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns
from django.views.i18n import JavaScriptCatalog
from .views import IndexView, LunaView, I18NView, HealthCheckView, redirect_format_api
# from .views import IndexView, LunaView, I18NView, HealthCheckView, redirect_format_api
from . import views
from .celery_flower import celery_flower_view
from .swagger import get_swagger_view
api_v1 = [
......@@ -40,6 +42,7 @@ app_view_patterns = [
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
path('auth/', include('authentication.urls.view_urls'), name='auth'),
path('applications/', include('applications.urls.views_urls', namespace='applications')),
re_path(r'flower/(?P<path>.*)', celery_flower_view, name='flower-view'),
]
......@@ -57,13 +60,13 @@ js_i18n_patterns = i18n_patterns(
urlpatterns = [
path('', IndexView.as_view(), name='index'),
path('', views.IndexView.as_view(), name='index'),
path('api/v1/', include(api_v1)),
path('api/v2/', include(api_v2)),
re_path('api/(?P<app>\w+)/(?P<version>v\d)/.*', redirect_format_api),
path('api/health/', HealthCheckView.as_view(), name="health"),
path('luna/', LunaView.as_view(), name='luna-view'),
path('i18n/<str:lang>/', I18NView.as_view(), name='i18n-switch'),
re_path('api/(?P<app>\w+)/(?P<version>v\d)/.*', views.redirect_format_api),
path('api/health/', views.HealthCheckView.as_view(), name="health"),
path('luna/', views.LunaView.as_view(), name='luna-view'),
path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'),
path('settings/', include('settings.urls.view_urls', namespace='settings')),
# External apps url
......
......@@ -224,3 +224,6 @@ class HealthCheckView(APIView):
def get(self, request):
return JsonResponse({"status": 1, "time": int(time.time())})
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-12 18:01+0800\n"
"POT-Creation-Date: 2019-09-19 19:50+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
......@@ -71,14 +71,14 @@ msgstr "目标地址"
msgid "Operating parameter"
msgstr "运行参数"
#: applications/forms/remote_app.py:104 applications/models/remote_app.py:23
#: applications/forms/remote_app.py:100 applications/models/remote_app.py:23
#: applications/templates/applications/remote_app_detail.html:57
#: applications/templates/applications/remote_app_list.html:22
#: applications/templates/applications/user_remote_app_list.html:18
#: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:295 assets/models/authbook.py:24
#: assets/serializers/admin_user.py:32 assets/serializers/asset_user.py:81
#: assets/serializers/system_user.py:31
#: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32
#: assets/serializers/asset_user.py:82 assets/serializers/system_user.py:31
#: assets/templates/assets/admin_user_list.html:46
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:26
......@@ -95,41 +95,19 @@ msgstr "运行参数"
#: terminal/templates/terminal/command_list.html:66
#: terminal/templates/terminal/session_list.html:28
#: terminal/templates/terminal/session_list.html:72
#: xpack/plugins/change_auth_plan/forms.py:121
#: xpack/plugins/change_auth_plan/models.py:413
#: xpack/plugins/change_auth_plan/forms.py:64
#: xpack/plugins/change_auth_plan/models.py:412
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14
#: xpack/plugins/cloud/models.py:310
#: xpack/plugins/cloud/models.py:307
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63
#: xpack/plugins/orgs/templates/orgs/org_list.html:16
#: xpack/plugins/orgs/templates/orgs/org_list.html:17
#: xpack/plugins/vault/forms.py:13 xpack/plugins/vault/forms.py:15
msgid "Asset"
msgstr "资产"
#: applications/forms/remote_app.py:107 applications/models/remote_app.py:27
#: applications/templates/applications/remote_app_detail.html:61
#: applications/templates/applications/remote_app_list.html:23
#: applications/templates/applications/user_remote_app_list.html:19
#: assets/models/user.py:168 assets/templates/assets/user_asset_list.html:52
#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:52
#: audits/templates/audits/ftp_log_list.html:75
#: perms/forms/asset_permission.py:85 perms/models/asset_permission.py:80
#: perms/templates/perms/asset_permission_detail.html:140
#: perms/templates/perms/asset_permission_list.html:54
#: perms/templates/perms/asset_permission_list.html:75
#: perms/templates/perms/asset_permission_list.html:127 templates/_nav.html:25
#: terminal/backends/command/models.py:14 terminal/models.py:156
#: terminal/templates/terminal/command_list.html:31
#: terminal/templates/terminal/command_list.html:67
#: terminal/templates/terminal/session_list.html:29
#: terminal/templates/terminal/session_list.html:73
#: users/templates/users/_granted_assets.html:27
#: xpack/plugins/orgs/templates/orgs/org_list.html:19
msgid "System user"
msgstr "系统用户"
#: applications/models/remote_app.py:21
#: applications/templates/applications/remote_app_detail.html:53
#: applications/templates/applications/remote_app_list.html:20
......@@ -166,15 +144,15 @@ msgstr "系统用户"
#: settings/templates/settings/terminal_setting.html:105 terminal/models.py:22
#: terminal/models.py:258 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
#: users/models/user.py:330 users/templates/users/_select_user_modal.html:13
#: users/models/user.py:373 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_detail.html:63
#: users/templates/users/user_group_detail.html:55
#: users/templates/users/user_group_list.html:35
#: users/templates/users/user_list.html:35
#: users/templates/users/user_profile.html:51
#: users/templates/users/user_pubkey_update.html:57
#: xpack/plugins/change_auth_plan/forms.py:104
#: xpack/plugins/change_auth_plan/models.py:61
#: xpack/plugins/change_auth_plan/forms.py:47
#: xpack/plugins/change_auth_plan/models.py:63
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:144
......@@ -182,29 +160,31 @@ msgstr "系统用户"
#: xpack/plugins/cloud/templates/cloud/account_list.html:12
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:56
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12
#: xpack/plugins/gathered_user/models.py:28
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:16
#: xpack/plugins/orgs/templates/orgs/org_detail.html:52
#: xpack/plugins/orgs/templates/orgs/org_list.html:12
msgid "Name"
msgstr "名称"
#: applications/models/remote_app.py:32
#: applications/templates/applications/remote_app_detail.html:65
#: applications/models/remote_app.py:28
#: applications/templates/applications/remote_app_detail.html:61
#: applications/templates/applications/remote_app_list.html:21
#: applications/templates/applications/user_remote_app_list.html:17
msgid "App type"
msgstr "应用类型"
#: applications/models/remote_app.py:36
#: applications/templates/applications/remote_app_detail.html:69
#: applications/models/remote_app.py:32
#: applications/templates/applications/remote_app_detail.html:65
msgid "App path"
msgstr "应用路径"
#: applications/models/remote_app.py:40
#: applications/models/remote_app.py:36
msgid "Parameters"
msgstr "参数"
#: applications/models/remote_app.py:43
#: applications/templates/applications/remote_app_detail.html:77
#: applications/models/remote_app.py:39
#: applications/templates/applications/remote_app_detail.html:73
#: assets/models/asset.py:174 assets/models/base.py:36
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
#: assets/models/cmd_filter.py:58 assets/models/group.py:21
......@@ -213,31 +193,32 @@ msgstr "参数"
#: assets/templates/assets/cmd_filter_detail.html:77
#: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:100
#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:14
#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15
#: perms/models/base.py:54
#: perms/templates/perms/asset_permission_detail.html:98
#: perms/templates/perms/remote_app_permission_detail.html:90
#: users/models/user.py:371 users/serializers/v1.py:119
#: users/models/user.py:414 users/serializers/v1.py:141
#: users/templates/users/user_detail.html:111
#: xpack/plugins/change_auth_plan/models.py:106
#: xpack/plugins/change_auth_plan/models.py:108
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
#: xpack/plugins/cloud/models.py:80 xpack/plugins/cloud/models.py:179
#: xpack/plugins/gathered_user/models.py:46
msgid "Created by"
msgstr "创建者"
# msgid "Created by"
# msgstr "创建者"
#: applications/models/remote_app.py:46
#: applications/templates/applications/remote_app_detail.html:73
#: applications/models/remote_app.py:42
#: applications/templates/applications/remote_app_detail.html:69
#: assets/models/asset.py:175 assets/models/base.py:34
#: assets/models/cluster.py:26 assets/models/domain.py:23
#: assets/models/group.py:22 assets/models/label.py:25
#: assets/templates/assets/admin_user_detail.html:64
#: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/cmd_filter_detail.html:69
#: assets/templates/assets/domain_detail.html:68
#: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64
#: orgs/models.py:15 perms/models/base.py:55
#: orgs/models.py:16 perms/models/base.py:55
#: perms/templates/perms/asset_permission_detail.html:94
#: perms/templates/perms/remote_app_permission_detail.html:86
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
......@@ -252,10 +233,10 @@ msgstr "创建日期"
# msgid "Date created"
# msgstr "创建日期"
#: applications/models/remote_app.py:49
#: applications/templates/applications/remote_app_detail.html:81
#: applications/templates/applications/remote_app_list.html:24
#: applications/templates/applications/user_remote_app_list.html:20
#: applications/models/remote_app.py:45
#: applications/templates/applications/remote_app_detail.html:77
#: applications/templates/applications/remote_app_list.html:23
#: applications/templates/applications/user_remote_app_list.html:19
#: assets/models/asset.py:176 assets/models/base.py:33
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
#: assets/models/cmd_filter.py:55 assets/models/domain.py:21
......@@ -271,16 +252,16 @@ msgstr "创建日期"
#: assets/templates/assets/domain_list.html:28
#: assets/templates/assets/system_user_detail.html:104
#: assets/templates/assets/system_user_list.html:59 ops/models/adhoc.py:43
#: orgs/models.py:16 perms/models/base.py:56
#: orgs/models.py:17 perms/models/base.py:56
#: perms/templates/perms/asset_permission_detail.html:102
#: perms/templates/perms/remote_app_permission_detail.html:94
#: settings/models.py:34 terminal/models.py:32
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
#: users/models/user.py:363 users/templates/users/user_detail.html:129
#: users/models/user.py:406 users/templates/users/user_detail.html:129
#: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:37
#: users/templates/users/user_profile.html:138
#: xpack/plugins/change_auth_plan/models.py:102
#: xpack/plugins/change_auth_plan/models.py:104
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
#: xpack/plugins/cloud/models.py:77 xpack/plugins/cloud/models.py:173
......@@ -288,23 +269,24 @@ msgstr "创建日期"
#: xpack/plugins/cloud/templates/cloud/account_list.html:15
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:105
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18
#: xpack/plugins/gathered_user/models.py:42
#: xpack/plugins/orgs/templates/orgs/org_detail.html:64
#: xpack/plugins/orgs/templates/orgs/org_list.html:22
#: xpack/plugins/orgs/templates/orgs/org_list.html:23
msgid "Comment"
msgstr "备注"
#: applications/models/remote_app.py:53 perms/forms/remote_app_permission.py:37
#: applications/models/remote_app.py:49 perms/forms/remote_app_permission.py:37
#: perms/models/remote_app_permission.py:15
#: perms/templates/perms/remote_app_permission_create_update.html:48
#: perms/templates/perms/remote_app_permission_detail.html:27
#: perms/templates/perms/remote_app_permission_list.html:17
#: perms/templates/perms/remote_app_permission_remote_app.html:26
#: perms/templates/perms/remote_app_permission_user.html:26
#: templates/_nav.html:36 templates/_nav.html:48 templates/_nav_user.html:16
#: templates/_nav.html:60 templates/_nav.html:76 templates/_nav_user.html:16
msgid "RemoteApp"
msgstr "远程应用"
#: applications/templates/applications/remote_app_create_update.html:56
#: applications/templates/applications/remote_app_create_update.html:55
#: assets/templates/assets/_system_user.html:75
#: assets/templates/assets/admin_user_create_update.html:45
#: assets/templates/assets/asset_bulk_update.html:23
......@@ -315,7 +297,7 @@ msgstr "远程应用"
#: assets/templates/assets/gateway_create_update.html:58
#: assets/templates/assets/label_create_update.html:18
#: perms/templates/perms/asset_permission_create_update.html:83
#: perms/templates/perms/remote_app_permission_create_update.html:83
#: perms/templates/perms/remote_app_permission_create_update.html:84
#: settings/templates/settings/basic_setting.html:64
#: settings/templates/settings/command_storage_create.html:79
#: settings/templates/settings/email_content_setting.html:54
......@@ -336,12 +318,13 @@ msgstr "远程应用"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:71
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:53
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:44
#: xpack/plugins/interface/templates/interface/interface.html:72
#: xpack/plugins/vault/templates/vault/vault_create.html:45
msgid "Reset"
msgstr "重置"
#: applications/templates/applications/remote_app_create_update.html:58
#: applications/templates/applications/remote_app_create_update.html:57
#: assets/templates/assets/_system_user.html:76
#: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24
......@@ -354,7 +337,7 @@ msgstr "重置"
#: assets/templates/assets/label_create_update.html:19
#: audits/templates/audits/login_log_list.html:95
#: perms/templates/perms/asset_permission_create_update.html:84
#: perms/templates/perms/remote_app_permission_create_update.html:84
#: perms/templates/perms/remote_app_permission_create_update.html:85
#: settings/templates/settings/basic_setting.html:65
#: settings/templates/settings/command_storage_create.html:80
#: settings/templates/settings/email_content_setting.html:55
......@@ -401,12 +384,12 @@ msgstr "提交"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:20
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:17
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:106
#: xpack/plugins/change_auth_plan/views.py:88
#: xpack/plugins/change_auth_plan/views.py:91
msgid "Detail"
msgstr "详情"
#: applications/templates/applications/remote_app_detail.html:21
#: applications/templates/applications/remote_app_list.html:56
#: applications/templates/applications/remote_app_list.html:54
#: assets/templates/assets/_asset_user_list.html:69
#: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:26
......@@ -447,13 +430,14 @@ msgstr "详情"
#: xpack/plugins/cloud/templates/cloud/account_list.html:40
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:29
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:57
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:51
#: xpack/plugins/orgs/templates/orgs/org_detail.html:25
#: xpack/plugins/orgs/templates/orgs/org_list.html:88
#: xpack/plugins/orgs/templates/orgs/org_list.html:93
msgid "Update"
msgstr "更新"
#: applications/templates/applications/remote_app_detail.html:25
#: applications/templates/applications/remote_app_list.html:57
#: applications/templates/applications/remote_app_list.html:55
#: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:112
#: assets/templates/assets/asset_detail.html:31
......@@ -488,8 +472,9 @@ msgstr "更新"
#: xpack/plugins/cloud/templates/cloud/account_list.html:42
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:33
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:58
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:52
#: xpack/plugins/orgs/templates/orgs/org_detail.html:29
#: xpack/plugins/orgs/templates/orgs/org_list.html:90
#: xpack/plugins/orgs/templates/orgs/org_list.html:95
msgid "Delete"
msgstr "删除"
......@@ -511,8 +496,8 @@ msgstr "下载应用加载器"
msgid "Create RemoteApp"
msgstr "创建远程应用"
#: applications/templates/applications/remote_app_list.html:25
#: applications/templates/applications/user_remote_app_list.html:21
#: applications/templates/applications/remote_app_list.html:24
#: applications/templates/applications/user_remote_app_list.html:20
#: assets/models/cmd_filter.py:54
#: assets/templates/assets/_asset_user_list.html:20
#: assets/templates/assets/admin_user_list.html:51
......@@ -546,11 +531,12 @@ msgstr "创建远程应用"
#: xpack/plugins/cloud/templates/cloud/account_list.html:16
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:72
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:19
#: xpack/plugins/orgs/templates/orgs/org_list.html:23
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:20
#: xpack/plugins/orgs/templates/orgs/org_list.html:24
msgid "Action"
msgstr "动作"
#: applications/templates/applications/user_remote_app_list.html:57
#: applications/templates/applications/user_remote_app_list.html:52
#: assets/templates/assets/user_asset_list.html:32
#: perms/models/asset_permission.py:30
msgid "Connect"
......@@ -558,7 +544,7 @@ msgstr "连接"
#: applications/views/remote_app.py:31 applications/views/remote_app.py:47
#: applications/views/remote_app.py:70 applications/views/remote_app.py:89
#: templates/_nav.html:33
#: templates/_nav.html:57
msgid "Applications"
msgstr "应用管理"
......@@ -578,11 +564,6 @@ msgstr "远程应用详情"
msgid "My RemoteApp"
msgstr "我的远程应用"
#: assets/api/asset.py:40
#, python-format
msgid "%(hostname)s was %(action)s successfully"
msgstr "%(hostname)s %(action)s成功"
#: assets/api/node.py:58
msgid "You can't update the root node name"
msgstr "不能修改根节点名称"
......@@ -595,20 +576,6 @@ msgstr "更新节点资产硬件信息: {}"
msgid "Test if the assets under the node are connectable: {}"
msgstr "测试节点下资产是否可连接: {}"
#: assets/const.py:77 assets/models/utils.py:43
msgid "Unreachable"
msgstr "不可达"
#: assets/const.py:78 assets/models/utils.py:44
#: assets/templates/assets/asset_list.html:99
msgid "Reachable"
msgstr "可连接"
#: assets/const.py:79 assets/models/utils.py:45 authentication/utils.py:13
#: xpack/plugins/license/models.py:78
msgid "Unknown"
msgstr "未知"
#: assets/forms/asset.py:24 assets/models/asset.py:140
#: assets/models/domain.py:50
#: assets/templates/assets/domain_gateway_list.html:69
......@@ -621,16 +588,18 @@ msgstr "端口"
#: assets/templates/assets/asset_detail.html:198
#: assets/templates/assets/system_user_assets.html:83
#: perms/models/asset_permission.py:79
#: xpack/plugins/change_auth_plan/models.py:72
#: xpack/plugins/change_auth_plan/models.py:74
#: xpack/plugins/gathered_user/models.py:31
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17
msgid "Nodes"
msgstr "节点"
#: assets/forms/asset.py:58 assets/forms/asset.py:104
#: assets/models/asset.py:149 assets/models/cluster.py:19
#: assets/models/user.py:68 assets/templates/assets/asset_detail.html:76
#: templates/_nav.html:24 xpack/plugins/cloud/models.py:161
#: templates/_nav.html:44 xpack/plugins/cloud/models.py:161
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:68
#: xpack/plugins/orgs/templates/orgs/org_list.html:18
#: xpack/plugins/orgs/templates/orgs/org_list.html:19
msgid "Admin user"
msgstr "管理用户"
......@@ -638,7 +607,7 @@ msgstr "管理用户"
#: assets/templates/assets/asset_create.html:48
#: assets/templates/assets/asset_create.html:50
#: assets/templates/assets/asset_list.html:85
#: xpack/plugins/orgs/templates/orgs/org_list.html:20
#: xpack/plugins/orgs/templates/orgs/org_list.html:21
msgid "Label"
msgstr "标签"
......@@ -646,18 +615,18 @@ msgstr "标签"
#: assets/models/asset.py:144 assets/models/domain.py:26
#: assets/models/domain.py:52 assets/templates/assets/asset_detail.html:80
#: assets/templates/assets/user_asset_list.html:53
#: xpack/plugins/orgs/templates/orgs/org_list.html:17
#: xpack/plugins/orgs/templates/orgs/org_list.html:18
msgid "Domain"
msgstr "网域"
#: assets/forms/asset.py:68 assets/forms/asset.py:101 assets/forms/asset.py:114
#: assets/forms/asset.py:149 assets/models/node.py:393
#: assets/forms/asset.py:149 assets/models/node.py:402
#: assets/templates/assets/asset_create.html:42
#: perms/forms/asset_permission.py:82 perms/forms/asset_permission.py:89
#: perms/templates/perms/asset_permission_list.html:53
#: perms/templates/perms/asset_permission_list.html:74
#: perms/templates/perms/asset_permission_list.html:124
#: xpack/plugins/change_auth_plan/forms.py:122
#: xpack/plugins/change_auth_plan/forms.py:65
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15
#: xpack/plugins/cloud/models.py:157
......@@ -686,8 +655,8 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
#: assets/forms/asset.py:129 assets/forms/asset.py:133
#: assets/forms/domain.py:17 assets/forms/label.py:15
#: perms/templates/perms/asset_permission_asset.html:78
#: xpack/plugins/change_auth_plan/forms.py:112
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:84
#: xpack/plugins/change_auth_plan/forms.py:55
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:74
msgid "Select assets"
msgstr "选择资产"
......@@ -704,7 +673,7 @@ msgid "SSH gateway support proxy SSH,RDP,VNC"
msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: assets/forms/domain.py:74 assets/forms/user.py:75 assets/forms/user.py:95
#: assets/models/base.py:29
#: assets/models/base.py:29 assets/models/gathered_user.py:16
#: assets/templates/assets/_asset_user_auth_update_modal.html:15
#: assets/templates/assets/_asset_user_auth_view_modal.html:21
#: assets/templates/assets/_asset_user_list.html:16
......@@ -720,17 +689,18 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: perms/templates/perms/asset_permission_user.html:55
#: perms/templates/perms/remote_app_permission_user.html:54
#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:14
#: users/models/user.py:328 users/templates/users/_select_user_modal.html:14
#: users/models/user.py:371 users/templates/users/_select_user_modal.html:14
#: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:36
#: users/templates/users/user_profile.html:47
#: xpack/plugins/change_auth_plan/forms.py:106
#: xpack/plugins/change_auth_plan/models.py:63
#: xpack/plugins/change_auth_plan/models.py:409
#: xpack/plugins/change_auth_plan/forms.py:49
#: xpack/plugins/change_auth_plan/models.py:65
#: xpack/plugins/change_auth_plan/models.py:408
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:13
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:74
msgid "Username"
msgstr "用户名"
......@@ -739,7 +709,7 @@ msgid "Password or private key passphrase"
msgstr "密码或密钥密码"
#: assets/forms/user.py:26 assets/models/base.py:30
#: assets/serializers/asset_user.py:62
#: assets/serializers/asset_user.py:63
#: assets/templates/assets/_asset_user_auth_update_modal.html:21
#: assets/templates/assets/_asset_user_auth_view_modal.html:27
#: authentication/forms.py:15
......@@ -752,14 +722,14 @@ msgstr "密码或密钥密码"
#: users/templates/users/user_profile_update.html:41
#: users/templates/users/user_pubkey_update.html:41
#: users/templates/users/user_update.html:20
#: xpack/plugins/change_auth_plan/models.py:93
#: xpack/plugins/change_auth_plan/models.py:264
#: xpack/plugins/change_auth_plan/models.py:95
#: xpack/plugins/change_auth_plan/models.py:263
msgid "Password"
msgstr "密码"
#: assets/forms/user.py:29 assets/serializers/asset_user.py:70
#: assets/forms/user.py:29 assets/serializers/asset_user.py:71
#: assets/templates/assets/_asset_user_auth_update_modal.html:27
#: users/models/user.py:357
#: users/models/user.py:400
msgid "Private key"
msgstr "ssh私钥"
......@@ -810,11 +780,13 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
#: audits/templates/audits/login_log_list.html:60
#: perms/templates/perms/asset_permission_asset.html:58 settings/forms.py:140
#: users/templates/users/_granted_assets.html:26
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:51
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:54
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:73
msgid "IP"
msgstr "IP"
#: assets/models/asset.py:136 assets/serializers/asset_user.py:27
#: assets/serializers/gathered_user.py:19
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/_asset_user_auth_update_modal.html:9
#: assets/templates/assets/_asset_user_auth_view_modal.html:15
......@@ -825,7 +797,8 @@ msgstr "IP"
#: perms/templates/perms/asset_permission_asset.html:57
#: perms/templates/perms/asset_permission_list.html:73 settings/forms.py:139
#: users/templates/users/_granted_assets.html:25
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:50
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:53
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:72
msgid "Hostname"
msgstr "主机名"
......@@ -841,7 +814,7 @@ msgstr "协议"
#: assets/models/asset.py:142 assets/serializers/asset.py:63
#: assets/templates/assets/asset_create.html:24
#: assets/templates/assets/user_asset_list.html:50
#: perms/serializers/user_permission.py:38
#: perms/serializers/user_permission.py:48
msgid "Protocols"
msgstr "协议组"
......@@ -850,9 +823,9 @@ msgstr "协议组"
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:146 assets/models/cmd_filter.py:21
#: assets/models/domain.py:54 assets/models/label.py:22
#: assets/templates/assets/asset_detail.html:112
#: assets/models/asset.py:146 assets/models/authbook.py:27
#: assets/models/cmd_filter.py:21 assets/models/domain.py:54
#: assets/models/label.py:22 assets/templates/assets/asset_detail.html:112
msgid "Is active"
msgstr "激活"
......@@ -922,7 +895,7 @@ msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:173 assets/templates/assets/asset_create.html:46
#: assets/templates/assets/asset_detail.html:224 templates/_nav.html:26
#: assets/templates/assets/asset_detail.html:224 templates/_nav.html:46
msgid "Labels"
msgstr "标签管理"
......@@ -938,22 +911,24 @@ msgstr "最新版本"
msgid "Version"
msgstr "版本"
#: assets/models/authbook.py:35
#: assets/models/authbook.py:36
msgid "AuthBook"
msgstr ""
#: assets/models/base.py:31 xpack/plugins/change_auth_plan/models.py:97
#: xpack/plugins/change_auth_plan/models.py:271
#: assets/models/base.py:31 xpack/plugins/change_auth_plan/models.py:99
#: xpack/plugins/change_auth_plan/models.py:270
msgid "SSH private key"
msgstr "ssh密钥"
#: assets/models/base.py:32 xpack/plugins/change_auth_plan/models.py:100
#: xpack/plugins/change_auth_plan/models.py:267
#: assets/models/base.py:32 xpack/plugins/change_auth_plan/models.py:102
#: xpack/plugins/change_auth_plan/models.py:266
msgid "SSH public key"
msgstr "ssh公钥"
#: assets/models/base.py:35 assets/templates/assets/cmd_filter_detail.html:73
#: assets/models/base.py:35 assets/models/gathered_user.py:21
#: assets/templates/assets/cmd_filter_detail.html:73
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:76
msgid "Date updated"
msgstr "更新日期"
......@@ -965,7 +940,7 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:349
#: assets/models/cluster.py:22 users/models/user.py:392
#: users/templates/users/user_detail.html:76
msgid "Phone"
msgstr "手机"
......@@ -991,7 +966,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:469
#: users/models/user.py:512
msgid "System"
msgstr "系统"
......@@ -1081,6 +1056,15 @@ msgstr "命令过滤规则"
msgid "Gateway"
msgstr "网关"
#: assets/models/gathered_user.py:17
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:75
msgid "Present"
msgstr "存在"
#: assets/models/gathered_user.py:32
msgid "GatherUser"
msgstr "收集用户"
#: assets/models/group.py:30
msgid "Asset group"
msgstr "资产组"
......@@ -1110,16 +1094,16 @@ msgstr "默认资产组"
#: terminal/templates/terminal/command_list.html:65
#: terminal/templates/terminal/session_list.html:27
#: terminal/templates/terminal/session_list.html:71 users/forms.py:316
#: users/models/user.py:127 users/models/user.py:457
#: users/serializers/v1.py:108 users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:36 users/views/user.py:243
#: users/models/user.py:127 users/models/user.py:143 users/models/user.py:500
#: users/serializers/v1.py:130 users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:36 users/views/user.py:250
#: xpack/plugins/orgs/forms.py:26
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113
#: xpack/plugins/orgs/templates/orgs/org_list.html:14
#: xpack/plugins/orgs/templates/orgs/org_list.html:15
msgid "User"
msgstr "用户"
#: assets/models/label.py:19 assets/models/node.py:384
#: assets/models/label.py:19 assets/models/node.py:393
#: assets/templates/assets/label_list.html:15 settings/models.py:30
msgid "Value"
msgstr "值"
......@@ -1128,19 +1112,19 @@ msgstr "值"
msgid "Category"
msgstr "分类"
#: assets/models/node.py:222
#: assets/models/node.py:230
msgid "New node"
msgstr "新节点"
#: assets/models/node.py:308 perms/api/mixin.py:146
#: assets/models/node.py:317
msgid "ungrouped"
msgstr "未分组"
#: assets/models/node.py:310 perms/api/mixin.py:151
#: assets/models/node.py:319
msgid "empty"
msgstr "空"
#: assets/models/node.py:383
#: assets/models/node.py:392
msgid "Key"
msgstr "键"
......@@ -1171,7 +1155,7 @@ msgstr "手动登录"
#: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:73
#: assets/views/system_user.py:29 assets/views/system_user.py:46
#: assets/views/system_user.py:63 assets/views/system_user.py:79
#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:68
#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:70
msgid "Assets"
msgstr "资产管理"
......@@ -1194,11 +1178,45 @@ msgstr "Shell"
msgid "Login mode"
msgstr "登录模式"
#: assets/models/user.py:168 assets/templates/assets/user_asset_list.html:52
#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:52
#: audits/templates/audits/ftp_log_list.html:75
#: perms/forms/asset_permission.py:85 perms/forms/remote_app_permission.py:40
#: perms/models/asset_permission.py:80 perms/models/remote_app_permission.py:16
#: perms/templates/perms/asset_permission_detail.html:140
#: perms/templates/perms/asset_permission_list.html:54
#: perms/templates/perms/asset_permission_list.html:75
#: perms/templates/perms/asset_permission_list.html:127
#: perms/templates/perms/remote_app_permission_detail.html:131
#: templates/_nav.html:45 terminal/backends/command/models.py:14
#: terminal/models.py:156 terminal/templates/terminal/command_list.html:31
#: terminal/templates/terminal/command_list.html:67
#: terminal/templates/terminal/session_list.html:29
#: terminal/templates/terminal/session_list.html:73
#: users/templates/users/_granted_assets.html:27
#: xpack/plugins/orgs/templates/orgs/org_list.html:20
msgid "System user"
msgstr "系统用户"
#: assets/models/utils.py:35
#, python-format
msgid "%(value)s is not an even number"
msgstr "%(value)s is not an even number"
#: assets/models/utils.py:43 assets/tasks/const.py:81
msgid "Unreachable"
msgstr "不可达"
#: assets/models/utils.py:44 assets/tasks/const.py:82
#: assets/templates/assets/asset_list.html:99
msgid "Reachable"
msgstr "可连接"
#: assets/models/utils.py:45 assets/tasks/const.py:83
#: authentication/utils.py:13 xpack/plugins/license/models.py:78
msgid "Unknown"
msgstr "未知"
#: assets/serializers/asset.py:21
msgid "Protocol format should {}/{}"
msgstr "协议格式 {}/{}"
......@@ -1216,7 +1234,7 @@ msgstr "连接"
msgid "Hardware info"
msgstr "硬件信息"
#: assets/serializers/asset.py:91 orgs/mixins/serializers.py:26
#: assets/serializers/asset.py:91 orgs/mixins/serializers.py:27
msgid "Org name"
msgstr "组织名称"
......@@ -1224,8 +1242,8 @@ msgstr "组织名称"
msgid "Backend"
msgstr "后端"
#: assets/serializers/asset_user.py:66 users/forms.py:263
#: users/models/user.py:360 users/templates/users/first_login.html:42
#: assets/serializers/asset_user.py:67 users/forms.py:263
#: users/models/user.py:403 users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:49
#: users/templates/users/user_profile.html:69
#: users/templates/users/user_profile_update.html:46
......@@ -1262,88 +1280,93 @@ msgstr "自动登录模式,必须填写用户名"
msgid "Password or private key required"
msgstr "密码或密钥密码需要一个"
#: assets/tasks.py:33
msgid "Asset has been disabled, skipped: {}"
msgstr "资产或许不支持ansible, 跳过: {}"
#: assets/tasks/admin_user_connectivity.py:56
msgid "Test admin user connectivity period: {}"
msgstr "定期测试管理账号可连接性: {}"
#: assets/tasks.py:37
msgid "Asset may not be support ansible, skipped: {}"
msgstr "资产或许不支持ansible, 跳过: {}"
#: assets/tasks/admin_user_connectivity.py:63
msgid "Test admin user connectivity: {}"
msgstr "测试管理行号可连接性: {}"
#: assets/tasks.py:50
msgid "No assets matched, stop task"
msgstr "没有匹配到资产,结束任务"
#: assets/tasks/asset_connectivity.py:21
msgid "Test assets connectivity"
msgstr "测试资产可连接性"
#: assets/tasks.py:60
msgid "No assets matched related system user protocol, stop task"
msgstr "没有匹配到与系统用户协议相关的资产,结束任务"
#: assets/tasks/asset_connectivity.py:75
msgid "Test assets connectivity: {}"
msgstr "测试资产可连接性: {}"
#: assets/tasks/asset_user_connectivity.py:27
#: assets/tasks/push_system_user.py:130
#: xpack/plugins/change_auth_plan/models.py:521
msgid "The asset {} system platform {} does not support run Ansible tasks"
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
#: assets/tasks.py:86
#: assets/tasks/asset_user_connectivity.py:74
msgid "Test asset user connectivity: {}"
msgstr "测试资产用户可连接性: {}"
#: assets/tasks/gather_asset_hardware_info.py:44
msgid "Get asset info failed: {}"
msgstr "获取资产信息失败:{}"
#: assets/tasks.py:136
#: assets/tasks/gather_asset_hardware_info.py:94
msgid "Update some assets hardware info"
msgstr "更新资产硬件信息"
#: assets/tasks.py:153
#: assets/tasks/gather_asset_hardware_info.py:111
msgid "Update asset hardware info: {}"
msgstr "更新资产硬件信息: {}"
#: assets/tasks.py:178
msgid "Test assets connectivity"
msgstr "测试资产可连接性"
#: assets/tasks/gather_asset_users.py:96
msgid "Gather assets users"
msgstr "收集资产上的用户"
#: assets/tasks.py:232
msgid "Test assets connectivity: {}"
msgstr "测试资产可连接性: {}"
#: assets/tasks/push_system_user.py:142
msgid ""
"Push system user task skip, auto push not enable or protocol is not ssh or "
"rdp: {}"
msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh或rdp: {}"
#: assets/tasks.py:274
msgid "Test admin user connectivity period: {}"
msgstr "定期测试管理账号可连接性: {}"
#: assets/tasks/push_system_user.py:149
msgid "For security, do not push user {}"
msgstr "为了安全,禁止推送用户 {}"
#: assets/tasks.py:281
msgid "Test admin user connectivity: {}"
msgstr "测试管理行号可连接性: {}"
#: assets/tasks/push_system_user.py:177 assets/tasks/push_system_user.py:191
msgid "Push system users to assets: {}"
msgstr "推送系统用户到入资产: {}"
#: assets/tasks/push_system_user.py:183
msgid "Push system users to asset: {} => {}"
msgstr "推送系统用户到入资产: {} => {}"
#: assets/tasks.py:349
#: assets/tasks/system_user_connectivity.py:79
msgid "Test system user connectivity: {}"
msgstr "测试系统用户可连接性: {}"
#: assets/tasks.py:356
#: assets/tasks/system_user_connectivity.py:86
msgid "Test system user connectivity: {} => {}"
msgstr "测试系统用户可连接性: {} => {}"
#: assets/tasks.py:369
#: assets/tasks/system_user_connectivity.py:99
msgid "Test system user connectivity period: {}"
msgstr "定期测试系统用户可连接性: {}"
#: assets/tasks.py:487 assets/tasks.py:573
#: xpack/plugins/change_auth_plan/models.py:522
msgid "The asset {} system platform {} does not support run Ansible tasks"
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
#: assets/tasks.py:499
msgid ""
"Push system user task skip, auto push not enable or protocol is not ssh or "
"rdp: {}"
msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh或rdp: {}"
#: assets/tasks.py:506
msgid "For security, do not push user {}"
msgstr "为了安全,禁止推送用户 {}"
#: assets/tasks/utils.py:16
msgid "Asset has been disabled, skipped: {}"
msgstr "资产或许不支持ansible, 跳过: {}"
#: assets/tasks.py:534 assets/tasks.py:548
msgid "Push system users to assets: {}"
msgstr "推送系统用户到入资产: {}"
#: assets/tasks/utils.py:20
msgid "Asset may not be support ansible, skipped: {}"
msgstr "资产或许不支持ansible, 跳过: {}"
#: assets/tasks.py:540
msgid "Push system users to asset: {} => {}"
msgstr "推送系统用户到入资产: {} => {}"
#: assets/tasks/utils.py:33
msgid "No assets matched, stop task"
msgstr "没有匹配到资产,结束任务"
#: assets/tasks.py:620
msgid "Test asset user connectivity: {}"
msgstr "测试资产用户可连接性: {}"
#: assets/tasks/utils.py:43
msgid "No assets matched related system user protocol, stop task"
msgstr "没有匹配到与系统用户协议相关的资产,结束任务"
#: assets/templates/assets/_admin_user_import_modal.html:4
msgid "Import admin user"
......@@ -1385,14 +1408,14 @@ msgid "Import assets"
msgstr "导入资产"
#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:38
#: templates/_nav.html:22 xpack/plugins/change_auth_plan/views.py:115
#: templates/_nav.html:42 xpack/plugins/change_auth_plan/views.py:118
msgid "Asset list"
msgstr "资产列表"
#: assets/templates/assets/_asset_list_modal.html:33
#: assets/templates/assets/_node_tree.html:40
#: ops/templates/ops/command_execution_create.html:49
#: users/templates/users/_granted_assets.html:7
#: users/templates/users/_granted_assets.html:83
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66
msgid "Loading"
msgstr "加载中"
......@@ -1406,14 +1429,14 @@ msgid "Update asset user auth"
msgstr "更新资产用户认证信息"
#: assets/templates/assets/_asset_user_auth_update_modal.html:23
#: xpack/plugins/change_auth_plan/forms.py:108
#: xpack/plugins/change_auth_plan/forms.py:51
msgid "Please input password"
msgstr "请输入密码"
#: assets/templates/assets/_asset_user_auth_update_modal.html:68
#: assets/templates/assets/asset_detail.html:306
#: users/templates/users/user_detail.html:311
#: users/templates/users/user_detail.html:338
#: users/templates/users/user_detail.html:313
#: users/templates/users/user_detail.html:340
#: xpack/plugins/interface/views.py:35
msgid "Update successfully!"
msgstr "更新成功"
......@@ -1519,6 +1542,7 @@ msgstr "重命名成功"
#: perms/templates/perms/remote_app_permission_create_update.html:39
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:43
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:27
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:27
msgid "Basic"
msgstr "基本"
......@@ -1537,10 +1561,11 @@ msgstr "自动生成密钥"
#: assets/templates/assets/asset_create.html:74
#: assets/templates/assets/gateway_create_update.html:53
#: perms/templates/perms/asset_permission_create_update.html:53
#: perms/templates/perms/remote_app_permission_create_update.html:52
#: perms/templates/perms/remote_app_permission_create_update.html:53
#: terminal/templates/terminal/terminal_update.html:40
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:67
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:48
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:39
msgid "Other"
msgstr "其它"
......@@ -1590,14 +1615,15 @@ msgstr "替换资产的管理员"
#: assets/templates/assets/admin_user_detail.html:91
#: perms/templates/perms/asset_permission_asset.html:103
#: xpack/plugins/change_auth_plan/forms.py:116
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:109
#: xpack/plugins/change_auth_plan/forms.py:59
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:99
#: xpack/plugins/gathered_user/forms.py:36
msgid "Select nodes"
msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:204
#: assets/templates/assets/asset_list.html:418
#: assets/templates/assets/asset_list.html:422
#: assets/templates/assets/cmd_filter_detail.html:106
#: assets/templates/assets/system_user_assets.html:97
#: assets/templates/assets/system_user_detail.html:182
......@@ -1605,17 +1631,18 @@ msgstr "选择节点"
#: authentication/templates/authentication/_mfa_confirm_modal.html:20
#: settings/templates/settings/terminal_setting.html:168
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108
#: users/templates/users/user_detail.html:392
#: users/templates/users/user_detail.html:418
#: users/templates/users/user_detail.html:441
#: users/templates/users/user_detail.html:486
#: users/templates/users/user_detail.html:394
#: users/templates/users/user_detail.html:420
#: users/templates/users/user_detail.html:443
#: users/templates/users/user_detail.html:488
#: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:120
#: users/templates/users/user_list.html:256
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:54
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:45
#: xpack/plugins/interface/templates/interface/interface.html:103
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:34
msgid "Confirm"
msgstr "确认"
......@@ -1638,6 +1665,7 @@ msgstr "Jumpserver 使用该用户来 `推送系统用户`、`获取资产硬件
#: audits/templates/audits/login_log_list.html:91
#: users/templates/users/user_group_list.html:10
#: users/templates/users/user_list.html:10
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:59
#: xpack/plugins/vault/templates/vault/vault.html:55
msgid "Export"
msgstr "导出"
......@@ -1660,8 +1688,8 @@ msgstr "创建管理用户"
#: assets/templates/assets/admin_user_list.html:162
#: assets/templates/assets/admin_user_list.html:193
#: assets/templates/assets/asset_list.html:299
#: assets/templates/assets/asset_list.html:336
#: assets/templates/assets/asset_list.html:303
#: assets/templates/assets/asset_list.html:340
#: assets/templates/assets/system_user_list.html:192
#: assets/templates/assets/system_user_list.html:223
#: users/templates/users/user_group_list.html:164
......@@ -1721,7 +1749,7 @@ msgstr "创建日期"
#: perms/models/base.py:51
#: perms/templates/perms/asset_permission_create_update.html:55
#: perms/templates/perms/asset_permission_detail.html:120
#: perms/templates/perms/remote_app_permission_create_update.html:54
#: perms/templates/perms/remote_app_permission_create_update.html:55
#: perms/templates/perms/remote_app_permission_detail.html:112
#: terminal/templates/terminal/terminal_list.html:34
#: users/templates/users/_select_user_modal.html:18
......@@ -1779,69 +1807,69 @@ msgstr "禁用所选"
msgid "Active selected"
msgstr "激活所选"
#: assets/templates/assets/asset_list.html:189
#: assets/templates/assets/asset_list.html:193
msgid "Add assets to node"
msgstr "添加资产到节点"
#: assets/templates/assets/asset_list.html:190
#: assets/templates/assets/asset_list.html:194
msgid "Move assets to node"
msgstr "移动资产到节点"
#: assets/templates/assets/asset_list.html:192
#: assets/templates/assets/asset_list.html:196
msgid "Refresh node hardware info"
msgstr "更新节点资产硬件信息"
#: assets/templates/assets/asset_list.html:193
#: assets/templates/assets/asset_list.html:197
msgid "Test node connective"
msgstr "测试节点资产可连接性"
#: assets/templates/assets/asset_list.html:195
#: assets/templates/assets/asset_list.html:199
msgid "Display only current node assets"
msgstr "仅显示当前节点资产"
#: assets/templates/assets/asset_list.html:196
#: assets/templates/assets/asset_list.html:200
msgid "Displays all child node assets"
msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:412
#: assets/templates/assets/asset_list.html:416
#: assets/templates/assets/system_user_list.html:133
#: users/templates/users/user_detail.html:386
#: users/templates/users/user_detail.html:412
#: users/templates/users/user_detail.html:480
#: users/templates/users/user_detail.html:388
#: users/templates/users/user_detail.html:414
#: users/templates/users/user_detail.html:482
#: users/templates/users/user_group_list.html:114
#: users/templates/users/user_list.html:250
#: xpack/plugins/interface/templates/interface/interface.html:97
msgid "Are you sure?"
msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:413
#: assets/templates/assets/asset_list.html:417
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:416
#: assets/templates/assets/asset_list.html:420
#: assets/templates/assets/system_user_list.html:137
#: settings/templates/settings/terminal_setting.html:166
#: users/templates/users/user_detail.html:390
#: users/templates/users/user_detail.html:416
#: users/templates/users/user_detail.html:484
#: users/templates/users/user_detail.html:392
#: users/templates/users/user_detail.html:418
#: users/templates/users/user_detail.html:486
#: users/templates/users/user_group_create_update.html:31
#: users/templates/users/user_group_list.html:118
#: users/templates/users/user_list.html:254
#: xpack/plugins/interface/templates/interface/interface.html:101
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:32
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
msgid "Cancel"
msgstr "取消"
#: assets/templates/assets/asset_list.html:429
#: assets/templates/assets/asset_list.html:433
msgid "Asset Deleted."
msgstr "已被删除"
#: assets/templates/assets/asset_list.html:430
#: assets/templates/assets/asset_list.html:434
#: assets/templates/assets/asset_list.html:438
msgid "Asset Delete"
msgstr "删除"
#: assets/templates/assets/asset_list.html:433
#: assets/templates/assets/asset_list.html:437
msgid "Asset Deleting failed."
msgstr "删除失败"
......@@ -2082,7 +2110,7 @@ msgstr "创建命令过滤器规则"
msgid "Update command filter rule"
msgstr "更新命令过滤器规则"
#: assets/views/domain.py:31 templates/_nav.html:23
#: assets/views/domain.py:31 templates/_nav.html:43
msgid "Domain list"
msgstr "网域列表"
......@@ -2153,7 +2181,7 @@ msgstr "文件名"
#: audits/templates/audits/ftp_log_list.html:79
#: ops/templates/ops/command_execution_list.html:68
#: ops/templates/ops/task_list.html:31
#: users/templates/users/user_detail.html:462
#: users/templates/users/user_detail.html:464
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:14
#: xpack/plugins/cloud/api.py:61
msgid "Success"
......@@ -2191,8 +2219,8 @@ msgstr "启用"
msgid "-"
msgstr ""
#: audits/models.py:77 xpack/plugins/cloud/models.py:267
#: xpack/plugins/cloud/models.py:290
#: audits/models.py:77 xpack/plugins/cloud/models.py:264
#: xpack/plugins/cloud/models.py:287
msgid "Failed"
msgstr "失败"
......@@ -2214,21 +2242,21 @@ msgstr "Agent"
#: audits/models.py:85 audits/templates/audits/login_log_list.html:62
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: users/forms.py:175 users/models/user.py:352
#: users/forms.py:175 users/models/user.py:395
#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
#: audits/models.py:86 audits/templates/audits/login_log_list.html:63
#: xpack/plugins/change_auth_plan/models.py:417
#: xpack/plugins/change_auth_plan/models.py:416
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
#: xpack/plugins/cloud/models.py:281
#: xpack/plugins/cloud/models.py:278
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
msgid "Reason"
msgstr "原因"
#: audits/models.py:87 audits/templates/audits/login_log_list.html:64
#: xpack/plugins/cloud/models.py:278 xpack/plugins/cloud/models.py:313
#: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65
msgid "Status"
......@@ -2246,10 +2274,11 @@ msgstr "登录日期"
#: perms/templates/perms/asset_permission_detail.html:86
#: perms/templates/perms/remote_app_permission_detail.html:78
#: terminal/models.py:165 terminal/templates/terminal/session_list.html:34
#: xpack/plugins/change_auth_plan/models.py:250
#: xpack/plugins/change_auth_plan/models.py:420
#: xpack/plugins/change_auth_plan/models.py:249
#: xpack/plugins/change_auth_plan/models.py:419
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17
#: xpack/plugins/gathered_user/models.py:143
msgid "Date start"
msgstr "开始日期"
......@@ -2297,47 +2326,46 @@ msgstr "城市"
msgid "Date"
msgstr "日期"
#: audits/views.py:85 audits/views.py:129 audits/views.py:166
#: audits/views.py:211 audits/views.py:243 templates/_nav.html:87
#: templates/_nav_audits.html:22
#: audits/views.py:86 audits/views.py:130 audits/views.py:167
#: audits/views.py:212 audits/views.py:244 templates/_nav.html:129
msgid "Audits"
msgstr "日志审计"
#: audits/views.py:86 templates/_nav.html:91 templates/_nav_audits.html:26
#: audits/views.py:87 templates/_nav.html:133
msgid "FTP log"
msgstr "FTP日志"
#: audits/views.py:130 templates/_nav.html:92 templates/_nav_audits.html:27
#: audits/views.py:131 templates/_nav.html:134
msgid "Operate log"
msgstr "操作日志"
#: audits/views.py:167 templates/_nav.html:93 templates/_nav_audits.html:28
#: audits/views.py:168 templates/_nav.html:135
msgid "Password change log"
msgstr "改密日志"
#: audits/views.py:212 templates/_nav.html:90 templates/_nav_audits.html:25
#: audits/views.py:213 templates/_nav.html:132
msgid "Login log"
msgstr "登录日志"
#: audits/views.py:244
#: audits/views.py:245
msgid "Command execution log"
msgstr "命令执行"
#: authentication/api/auth.py:51 authentication/api/token.py:45
#: authentication/api/auth.py:61 authentication/api/token.py:45
#: authentication/templates/authentication/login.html:52
#: authentication/templates/authentication/new_login.html:77
msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试"
#: authentication/api/auth.py:76
#: authentication/api/auth.py:86
msgid "Please carry seed value and conduct MFA secondary certification"
msgstr "请携带seed值, 进行MFA二次认证"
#: authentication/api/auth.py:156
#: authentication/api/auth.py:176
msgid "Please verify the user name and password first"
msgstr "请先进行用户名和密码验证"
#: authentication/api/auth.py:161
#: authentication/api/auth.py:181
msgid "MFA certification failed"
msgstr "MFA认证失败"
......@@ -2463,18 +2491,19 @@ msgid "Secret"
msgstr "密文"
#: authentication/templates/authentication/_access_key_modal.html:48
#: users/templates/users/_granted_assets.html:75
msgid "Show"
msgstr "显示"
#: authentication/templates/authentication/_access_key_modal.html:66
#: users/models/user.py:287 users/templates/users/user_profile.html:94
#: users/models/user.py:330 users/templates/users/user_profile.html:94
#: users/templates/users/user_profile.html:163
#: users/templates/users/user_profile.html:166
msgid "Disable"
msgstr "禁用"
#: authentication/templates/authentication/_access_key_modal.html:67
#: users/models/user.py:288 users/templates/users/user_profile.html:92
#: users/models/user.py:331 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:170
msgid "Enable"
msgstr "启用"
......@@ -2611,8 +2640,8 @@ msgstr "欢迎回来,请输入用户名和密码登录"
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:174 users/views/user.py:386
#: users/views/user.py:411
#: authentication/views/login.py:174 users/views/user.py:393
#: users/views/user.py:418
msgid "MFA code invalid, or ntp sync server time"
msgstr "MFA验证码不正确,或者服务器端时间不对"
......@@ -2670,19 +2699,6 @@ msgstr ""
msgid "Encrypt field using Secret Key"
msgstr ""
#: common/mixins/api.py:63
#, python-format
msgid "%(name)s was %(action)s successfully"
msgstr "%(name)s %(action)s成功"
#: common/mixins/api.py:64
msgid "create"
msgstr "创建"
#: common/mixins/api.py:64
msgid "update"
msgstr "更新"
#: common/mixins/models.py:31
msgid "is discard"
msgstr ""
......@@ -2699,11 +2715,11 @@ msgstr "不能包含特殊字符"
msgid "This field must be unique."
msgstr "字段必须唯一"
#: jumpserver/views.py:188 templates/_nav.html:4 templates/_nav_audits.html:4
#: jumpserver/views.py:184 templates/_nav.html:7
msgid "Dashboard"
msgstr "仪表盘"
#: jumpserver/views.py:197
#: jumpserver/views.py:193
msgid ""
"<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
"configure nginx for url distribution,</div> </div>If you see this page, "
......@@ -2782,54 +2798,55 @@ msgstr "Become"
msgid "Create by"
msgstr "创建者"
#: ops/models/adhoc.py:224
#: ops/models/adhoc.py:226
msgid "{} Start task: {}"
msgstr "{} 任务开始: {}"
#: ops/models/adhoc.py:227
#: ops/models/adhoc.py:238
msgid "{} Task finish"
msgstr "{} 任务结束"
#: ops/models/adhoc.py:325
#: ops/models/adhoc.py:329
msgid "Start time"
msgstr "开始时间"
#: ops/models/adhoc.py:326
#: ops/models/adhoc.py:330
msgid "End time"
msgstr "完成时间"
#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_history.html:57
#: ops/models/adhoc.py:331 ops/templates/ops/adhoc_history.html:57
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:33
#: xpack/plugins/change_auth_plan/models.py:253
#: xpack/plugins/change_auth_plan/models.py:423
#: xpack/plugins/change_auth_plan/models.py:252
#: xpack/plugins/change_auth_plan/models.py:422
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16
#: xpack/plugins/gathered_user/models.py:146
msgid "Time"
msgstr "时间"
#: ops/models/adhoc.py:328 ops/templates/ops/adhoc_detail.html:106
#: ops/models/adhoc.py:332 ops/templates/ops/adhoc_detail.html:106
#: ops/templates/ops/adhoc_history.html:55
#: ops/templates/ops/adhoc_history_detail.html:69
#: ops/templates/ops/task_detail.html:84 ops/templates/ops/task_history.html:61
msgid "Is finished"
msgstr "是否完成"
#: ops/models/adhoc.py:329 ops/templates/ops/adhoc_history.html:56
#: ops/models/adhoc.py:333 ops/templates/ops/adhoc_history.html:56
#: ops/templates/ops/task_history.html:62
msgid "Is success"
msgstr "是否成功"
#: ops/models/adhoc.py:330
#: ops/models/adhoc.py:334
msgid "Adhoc raw result"
msgstr "结果"
#: ops/models/adhoc.py:331
#: ops/models/adhoc.py:335
msgid "Adhoc result summary"
msgstr "汇总"
#: ops/models/command.py:22
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:56
#: xpack/plugins/cloud/models.py:276
#: xpack/plugins/cloud/models.py:273
msgid "Result"
msgstr "结果"
......@@ -2864,6 +2881,7 @@ msgstr "运行用户"
#: ops/templates/ops/adhoc_detail.html:94 ops/templates/ops/task_list.html:28
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:18
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:19
msgid "Run times"
msgstr "执行次数"
......@@ -2950,39 +2968,39 @@ msgstr "成功资产"
msgid "Task log"
msgstr "任务列表"
#: ops/templates/ops/command_execution_create.html:71
#: ops/templates/ops/command_execution_create.html:90
#: terminal/templates/terminal/session_detail.html:91
#: terminal/templates/terminal/session_detail.html:100
msgid "Go"
msgstr ""
#: ops/templates/ops/command_execution_create.html:197
#: ops/templates/ops/command_execution_create.html:215
msgid "Selected assets"
msgstr "已选择资产"
#: ops/templates/ops/command_execution_create.html:200
#: ops/templates/ops/command_execution_create.html:218
msgid "In total"
msgstr "总共"
#: ops/templates/ops/command_execution_create.html:236
#: ops/templates/ops/command_execution_create.html:254
msgid ""
"Select the left asset, select the running system user, execute command in "
"batch"
msgstr "选择左侧资产, 选择运行的系统用户,批量执行命令"
#: ops/templates/ops/command_execution_create.html:257
#: ops/templates/ops/command_execution_create.html:275
msgid "Unselected assets"
msgstr "没有选中资产"
#: ops/templates/ops/command_execution_create.html:261
#: ops/templates/ops/command_execution_create.html:279
msgid "No input command"
msgstr "没有输入命令"
#: ops/templates/ops/command_execution_create.html:265
#: ops/templates/ops/command_execution_create.html:283
msgid "No system user was selected"
msgstr "没有选择系统用户"
#: ops/templates/ops/command_execution_create.html:310
#: ops/templates/ops/command_execution_create.html:328
msgid "Pending"
msgstr "等待"
......@@ -3031,6 +3049,7 @@ msgstr "版本"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:54
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:141
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:49
msgid "Run"
msgstr "执行"
......@@ -3044,11 +3063,12 @@ msgstr "更新任务内容: {}"
#: ops/views/adhoc.py:45 ops/views/adhoc.py:71 ops/views/adhoc.py:85
#: ops/views/adhoc.py:99 ops/views/adhoc.py:113 ops/views/adhoc.py:127
#: ops/views/adhoc.py:141 ops/views/command.py:47 ops/views/command.py:77
#: ops/views/adhoc.py:141 ops/views/command.py:48 ops/views/command.py:79
msgid "Ops"
msgstr "作业中心"
#: ops/views/adhoc.py:46 templates/_nav.html:81
#: ops/views/adhoc.py:46 templates/_nav.html:115
#: xpack/plugins/gathered_user/views.py:35
msgid "Task list"
msgstr "任务列表"
......@@ -3056,15 +3076,15 @@ msgstr "任务列表"
msgid "Task run history"
msgstr "执行历史"
#: ops/views/command.py:48
#: ops/views/command.py:49
msgid "Command execution list"
msgstr "命令执行列表"
#: ops/views/command.py:78 templates/_nav_user.html:26
#: ops/views/command.py:80 templates/_nav_user.html:26
msgid "Command execution"
msgstr "命令执行"
#: orgs/mixins/models.py:61 orgs/mixins/serializers.py:25 orgs/models.py:29
#: orgs/mixins/models.py:61 orgs/mixins/serializers.py:26 orgs/models.py:30
msgid "Organization"
msgstr "组织"
......@@ -3081,11 +3101,11 @@ msgstr "空"
#: perms/templates/perms/asset_permission_list.html:71
#: perms/templates/perms/asset_permission_list.html:118
#: perms/templates/perms/remote_app_permission_list.html:16
#: templates/_nav.html:14 users/forms.py:286 users/models/group.py:26
#: users/models/user.py:336 users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:217
#: templates/_nav.html:21 users/forms.py:286 users/models/group.py:26
#: users/models/user.py:379 users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:218
#: users/templates/users/user_list.html:38
#: xpack/plugins/orgs/templates/orgs/org_list.html:15
#: xpack/plugins/orgs/templates/orgs/org_list.html:16
msgid "User group"
msgstr "用户组"
......@@ -3095,7 +3115,7 @@ msgid ""
"downloading files"
msgstr "提示:RDP 协议不支持单独控制上传或下载文件"
#: perms/forms/asset_permission.py:102 perms/forms/remote_app_permission.py:47
#: perms/forms/asset_permission.py:102 perms/forms/remote_app_permission.py:50
msgid "User or group at least one required"
msgstr "用户和用户组至少选一个"
......@@ -3123,19 +3143,19 @@ msgstr "上传下载"
msgid "Actions"
msgstr "动作"
#: perms/models/asset_permission.py:85 templates/_nav.html:44
#: perms/models/asset_permission.py:85 templates/_nav.html:72
msgid "Asset permission"
msgstr "资产授权"
#: perms/models/base.py:53
#: perms/templates/perms/asset_permission_detail.html:90
#: perms/templates/perms/remote_app_permission_detail.html:82
#: users/models/user.py:368 users/templates/users/user_detail.html:107
#: users/models/user.py:411 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:120
msgid "Date expired"
msgstr "失效日期"
#: perms/models/remote_app_permission.py:19
#: perms/models/remote_app_permission.py:20
msgid "RemoteApp permission"
msgstr "远程应用授权"
......@@ -3165,13 +3185,14 @@ msgstr "添加资产"
#: perms/templates/perms/asset_permission_detail.html:157
#: perms/templates/perms/asset_permission_user.html:97
#: perms/templates/perms/asset_permission_user.html:125
#: perms/templates/perms/remote_app_permission_detail.html:148
#: perms/templates/perms/remote_app_permission_remote_app.html:96
#: perms/templates/perms/remote_app_permission_user.html:96
#: perms/templates/perms/remote_app_permission_user.html:124
#: settings/templates/settings/terminal_setting.html:98
#: settings/templates/settings/terminal_setting.html:120
#: users/templates/users/user_group_detail.html:95
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:90
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:80
#: xpack/plugins/orgs/templates/orgs/org_detail.html:93
#: xpack/plugins/orgs/templates/orgs/org_detail.html:130
msgid "Add"
......@@ -3182,13 +3203,13 @@ msgid "Add node to this permission"
msgstr "添加节点"
#: perms/templates/perms/asset_permission_asset.html:109
#: users/templates/users/user_detail.html:234
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:115
#: users/templates/users/user_detail.html:235
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:105
msgid "Join"
msgstr "加入"
#: perms/templates/perms/asset_permission_create_update.html:61
#: perms/templates/perms/remote_app_permission_create_update.html:60
#: perms/templates/perms/remote_app_permission_create_update.html:61
msgid "Validity period"
msgstr "有效期"
......@@ -3217,6 +3238,7 @@ msgid "System user count"
msgstr "系统用户数量"
#: perms/templates/perms/asset_permission_detail.html:148
#: perms/templates/perms/remote_app_permission_detail.html:139
msgid "Select system users"
msgstr "选择系统用户"
......@@ -3286,14 +3308,14 @@ msgstr "添加用户组"
#: perms/views/asset_permission.py:33 perms/views/asset_permission.py:65
#: perms/views/asset_permission.py:82 perms/views/asset_permission.py:99
#: perms/views/asset_permission.py:136 perms/views/asset_permission.py:171
#: perms/views/asset_permission.py:139 perms/views/asset_permission.py:170
#: perms/views/remote_app_permission.py:33
#: perms/views/remote_app_permission.py:49
#: perms/views/remote_app_permission.py:66
#: perms/views/remote_app_permission.py:81
#: perms/views/remote_app_permission.py:108
#: perms/views/remote_app_permission.py:145 templates/_nav.html:41
#: xpack/plugins/orgs/templates/orgs/org_list.html:21
#: perms/views/remote_app_permission.py:115
#: perms/views/remote_app_permission.py:148 templates/_nav.html:69
#: xpack/plugins/orgs/templates/orgs/org_list.html:22
msgid "Perms"
msgstr "权限管理"
......@@ -3313,11 +3335,11 @@ msgstr "更新资产授权"
msgid "Asset permission detail"
msgstr "资产授权详情"
#: perms/views/asset_permission.py:137
#: perms/views/asset_permission.py:140
msgid "Asset permission user list"
msgstr "资产授权用户列表"
#: perms/views/asset_permission.py:172
#: perms/views/asset_permission.py:171
msgid "Asset permission asset list"
msgstr "资产授权资产列表"
......@@ -3337,11 +3359,11 @@ msgstr "更新远程应用授权规则"
msgid "RemoteApp permission detail"
msgstr "远程应用授权详情"
#: perms/views/remote_app_permission.py:109
#: perms/views/remote_app_permission.py:116
msgid "RemoteApp permission user list"
msgstr "远程应用授权用户列表"
#: perms/views/remote_app_permission.py:146
#: perms/views/remote_app_permission.py:149
msgid "RemoteApp permission RemoteApp list"
msgstr "远程应用授权远程应用列表"
......@@ -3357,21 +3379,21 @@ msgstr "连接LDAP成功"
msgid "Match {} s users"
msgstr "匹配 {} 个用户"
#: settings/api.py:159
#: settings/api.py:161
msgid "succeed: {} failed: {} total: {}"
msgstr "成功:{} 失败:{} 总数:{}"
#: settings/api.py:181 settings/api.py:217
#: settings/api.py:183 settings/api.py:219
msgid ""
"Error: Account invalid (Please make sure the information such as Access key "
"or Secret key is correct)"
msgstr "错误:账户无效 (请确保 Access key 或 Secret key 等信息正确)"
#: settings/api.py:187 settings/api.py:223
#: settings/api.py:189 settings/api.py:225
msgid "Create succeed"
msgstr "创建成功"
#: settings/api.py:205 settings/api.py:243
#: settings/api.py:207 settings/api.py:245
#: settings/templates/settings/terminal_setting.html:154
msgid "Delete succeed"
msgstr "删除成功"
......@@ -3684,7 +3706,7 @@ msgid "Please submit the LDAP configuration before import"
msgstr "请先提交LDAP配置再进行导入"
#: settings/templates/settings/_ldap_list_users_modal.html:32
#: users/models/user.py:332 users/templates/users/user_detail.html:71
#: users/models/user.py:375 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59
msgid "Email"
msgstr "邮件"
......@@ -3830,7 +3852,7 @@ msgid "Endpoint suffix"
msgstr "端点后缀"
#: settings/templates/settings/replay_storage_create.html:136
#: xpack/plugins/cloud/models.py:307
#: xpack/plugins/cloud/models.py:304
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:109
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62
msgid "Region"
......@@ -3888,7 +3910,7 @@ msgstr "用户来源不是LDAP"
#: settings/views.py:19 settings/views.py:46 settings/views.py:73
#: settings/views.py:103 settings/views.py:131 settings/views.py:144
#: settings/views.py:158 settings/views.py:185 templates/_nav.html:122
#: settings/views.py:158 settings/views.py:185 templates/_nav.html:170
msgid "Settings"
msgstr "系统设置"
......@@ -3917,14 +3939,15 @@ msgstr "文档"
msgid "Commercial support"
msgstr "商业支持"
#: templates/_header_bar.html:70 templates/_nav_user.html:32 users/forms.py:154
#: templates/_header_bar.html:70 templates/_nav.html:30
#: templates/_nav_user.html:32 users/forms.py:154
#: users/templates/users/_user.html:43
#: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:40
#: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:61
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:224
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:231
msgid "Profile"
msgstr "个人信息"
......@@ -4029,72 +4052,74 @@ msgstr ""
"\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
" "
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:45
#: users/views/group.py:63 users/views/group.py:81 users/views/group.py:98
#: users/views/login.py:154 users/views/user.py:60 users/views/user.py:77
#: users/views/user.py:121 users/views/user.py:188 users/views/user.py:210
#: users/views/user.py:263 users/views/user.py:298
#: templates/_nav.html:17 users/views/group.py:28 users/views/group.py:45
#: users/views/group.py:63 users/views/group.py:82 users/views/group.py:99
#: users/views/login.py:154 users/views/user.py:61 users/views/user.py:78
#: users/views/user.py:122 users/views/user.py:189 users/views/user.py:217
#: users/views/user.py:270 users/views/user.py:305
msgid "Users"
msgstr "用户管理"
#: templates/_nav.html:13 users/views/user.py:61
#: templates/_nav.html:20 users/views/user.py:62
msgid "User list"
msgstr "用户列表"
#: templates/_nav.html:27
#: templates/_nav.html:47
msgid "Command filters"
msgstr "命令过滤"
#: templates/_nav.html:55 templates/_nav_audits.html:11
#: terminal/views/command.py:21 terminal/views/session.py:43
#: terminal/views/session.py:54 terminal/views/session.py:78
#: terminal/views/terminal.py:32 terminal/views/terminal.py:48
#: terminal/views/terminal.py:61
#: templates/_nav.html:88 terminal/views/command.py:21
#: terminal/views/session.py:43 terminal/views/session.py:54
#: terminal/views/session.py:78 terminal/views/terminal.py:32
#: terminal/views/terminal.py:48 terminal/views/terminal.py:61
msgid "Sessions"
msgstr "会话管理"
#: templates/_nav.html:58 templates/_nav_audits.html:14
#: templates/_nav.html:91
msgid "Session online"
msgstr "在线会话"
#: templates/_nav.html:59 templates/_nav_audits.html:15
#: terminal/views/session.py:55
#: templates/_nav.html:92 terminal/views/session.py:55
msgid "Session offline"
msgstr "历史会话"
#: templates/_nav.html:60 templates/_nav_audits.html:16
#: templates/_nav.html:93
msgid "Commands"
msgstr "命令记录"
#: templates/_nav.html:63 templates/_nav_user.html:37
#: templates/_nav.html:96 templates/_nav_user.html:37
msgid "Web terminal"
msgstr "Web终端"
#: templates/_nav.html:68 templates/_nav_user.html:42
#: templates/_nav.html:97 templates/_nav_user.html:42
msgid "File manager"
msgstr "文件管理"
#: templates/_nav.html:72
#: templates/_nav.html:101
msgid "Terminal"
msgstr "终端管理"
#: templates/_nav.html:78
#: templates/_nav.html:112
msgid "Job Center"
msgstr "作业中心"
#: templates/_nav.html:82 templates/_nav.html:94 templates/_nav_audits.html:29
#: templates/_nav.html:116 templates/_nav.html:136
msgid "Batch command"
msgstr "批量命令"
#: templates/_nav.html:100
#: templates/_nav.html:118
msgid "Task monitor"
msgstr "任务监控"
#: templates/_nav.html:146
msgid "XPack"
msgstr ""
#: templates/_nav.html:108 xpack/plugins/cloud/views.py:28
#: templates/_nav.html:154 xpack/plugins/cloud/views.py:28
msgid "Account list"
msgstr "账户列表"
#: templates/_nav.html:109
#: templates/_nav.html:155
msgid "Sync instance"
msgstr "同步实例"
......@@ -4476,11 +4501,11 @@ msgid ""
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
msgstr "你可以使用ssh客户端工具连接终端"
#: users/api/user.py:187
#: users/api/user.py:173
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
#: users/forms.py:33 users/models/user.py:340
#: users/forms.py:33 users/models/user.py:383
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:37
......@@ -4501,7 +4526,7 @@ msgstr ""
msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里"
#: users/forms.py:52 users/templates/users/user_detail.html:225
#: users/forms.py:52 users/templates/users/user_detail.html:226
msgid "Join user groups"
msgstr "添加到用户组"
......@@ -4509,11 +4534,11 @@ msgstr "添加到用户组"
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
#: users/forms.py:91 users/forms.py:252 users/serializers/v1.py:94
#: users/forms.py:91 users/forms.py:252 users/serializers/v1.py:116
msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法"
#: users/forms.py:104 users/views/login.py:114 users/views/user.py:280
#: users/forms.py:104 users/views/login.py:114 users/views/user.py:287
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
......@@ -4525,7 +4550,7 @@ msgstr "生成重置密码链接,通过邮件发送给用户"
msgid "Set password"
msgstr "设置密码"
#: users/forms.py:133 xpack/plugins/change_auth_plan/models.py:86
#: users/forms.py:133 xpack/plugins/change_auth_plan/models.py:88
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57
......@@ -4599,7 +4624,7 @@ msgstr "选择用户"
msgid "User auth from {}, go there change password"
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
#: users/models/user.py:126 users/models/user.py:465
#: users/models/user.py:126 users/models/user.py:508
msgid "Administrator"
msgstr "管理员"
......@@ -4607,72 +4632,85 @@ msgstr "管理员"
msgid "Application"
msgstr "应用程序"
#: users/models/user.py:129
#: users/models/user.py:129 xpack/plugins/orgs/forms.py:38
#: xpack/plugins/orgs/templates/orgs/org_list.html:14
msgid "Auditor"
msgstr "审计员"
#: users/models/user.py:289 users/templates/users/user_profile.html:90
#: users/models/user.py:139
msgid "Org admin"
msgstr "组织管理员"
#: users/models/user.py:141
msgid "Org auditor"
msgstr "组织审计员"
#: users/models/user.py:332 users/templates/users/user_profile.html:90
msgid "Force enable"
msgstr "强制启用"
#: users/models/user.py:343
#: users/models/user.py:386
msgid "Avatar"
msgstr "头像"
#: users/models/user.py:346 users/templates/users/user_detail.html:82
#: users/models/user.py:389 users/templates/users/user_detail.html:82
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:375 users/templates/users/user_detail.html:103
#: users/models/user.py:418 users/templates/users/user_detail.html:103
#: users/templates/users/user_list.html:39
#: users/templates/users/user_profile.html:102
msgid "Source"
msgstr "用户来源"
#: users/models/user.py:379
#: users/models/user.py:422
msgid "Date password last updated"
msgstr "最后更新密码日期"
#: users/models/user.py:468
#: users/models/user.py:511
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
#: users/serializers/v1.py:39
#: users/serializers/v1.py:45
msgid "Groups name"
msgstr "用户组名"
#: users/serializers/v1.py:40
#: users/serializers/v1.py:46
msgid "Source name"
msgstr "用户来源名"
#: users/serializers/v1.py:41
#: users/serializers/v1.py:47
msgid "Is first login"
msgstr "首次登录"
#: users/serializers/v1.py:42
#: users/serializers/v1.py:48
msgid "Role name"
msgstr "角色名"
#: users/serializers/v1.py:43
#: users/serializers/v1.py:49
msgid "Is valid"
msgstr "账户是否有效"
#: users/serializers/v1.py:44
#: users/serializers/v1.py:50
msgid "Is expired"
msgstr " 是否过期"
#: users/serializers/v1.py:45
#: users/serializers/v1.py:51
msgid "Avatar url"
msgstr "头像路径"
#: users/serializers/v1.py:54
#: users/serializers/v1.py:72
msgid "Role limit to {}"
msgstr "角色只能为 {}"
#: users/serializers/v1.py:66
#: users/serializers/v1.py:84
msgid "Password does not match security rules"
msgstr "密码不满足安全规则"
#: users/serializers/v1.py:147
msgid "Auditors cannot be join in the group"
msgstr ""
#: users/serializers_v2/user.py:36
msgid "name not unique"
msgstr "名称重复"
......@@ -4723,7 +4761,7 @@ msgid "Import users"
msgstr "导入用户"
#: users/templates/users/_user_update_modal.html:4
#: users/templates/users/user_update.html:4 users/views/user.py:122
#: users/templates/users/user_update.html:4 users/views/user.py:123
msgid "Update user"
msgstr "更新用户"
......@@ -4801,7 +4839,7 @@ msgid "Always young, always with tears in my eyes. Stay foolish Stay hungry"
msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry"
#: users/templates/users/reset_password.html:46
#: users/templates/users/user_detail.html:377 users/utils.py:84
#: users/templates/users/user_detail.html:379 users/utils.py:84
msgid "Reset password"
msgstr "重置密码"
......@@ -4866,12 +4904,12 @@ msgid "Very strong"
msgstr "很强"
#: users/templates/users/user_create.html:4
#: users/templates/users/user_list.html:28 users/views/user.py:78
#: users/templates/users/user_list.html:28 users/views/user.py:79
msgid "Create user"
msgstr "创建用户"
#: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:189
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:190
msgid "User detail"
msgstr "用户详情"
......@@ -4918,7 +4956,7 @@ msgid "Send reset ssh key mail"
msgstr "发送重置密钥邮件"
#: users/templates/users/user_detail.html:203
#: users/templates/users/user_detail.html:465
#: users/templates/users/user_detail.html:467
msgid "Unblock user"
msgstr "解除登录限制"
......@@ -4926,52 +4964,52 @@ msgstr "解除登录限制"
msgid "Unblock"
msgstr "解除"
#: users/templates/users/user_detail.html:320
#: users/templates/users/user_detail.html:322
msgid "Goto profile page enable MFA"
msgstr "请去个人信息页面启用自己的MFA"
#: users/templates/users/user_detail.html:376
#: users/templates/users/user_detail.html:378
msgid "An e-mail has been sent to the user`s mailbox."
msgstr "已发送邮件到用户邮箱"
#: users/templates/users/user_detail.html:387
#: users/templates/users/user_detail.html:389
msgid "This will reset the user password and send a reset mail"
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
#: users/templates/users/user_detail.html:402
#: users/templates/users/user_detail.html:404
msgid ""
"The reset-ssh-public-key E-mail has been sent successfully. Please inform "
"the user to update his new ssh public key."
msgstr "重设密钥邮件将会发送到用户邮箱"
#: users/templates/users/user_detail.html:403
#: users/templates/users/user_detail.html:405
msgid "Reset SSH public key"
msgstr "重置SSH密钥"
#: users/templates/users/user_detail.html:413
#: users/templates/users/user_detail.html:415
msgid "This will reset the user public key and send a reset mail"
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
#: users/templates/users/user_detail.html:431
#: users/templates/users/user_detail.html:433
msgid "Successfully updated the SSH public key."
msgstr "更新ssh密钥成功"
#: users/templates/users/user_detail.html:432
#: users/templates/users/user_detail.html:436
#: users/templates/users/user_detail.html:434
#: users/templates/users/user_detail.html:438
msgid "User SSH public key update"
msgstr "ssh密钥"
#: users/templates/users/user_detail.html:481
#: users/templates/users/user_detail.html:483
msgid "After unlocking the user, the user can log in normally."
msgstr "解除用户登录限制后,此用户即可正常登录"
#: users/templates/users/user_detail.html:495
#: users/templates/users/user_detail.html:497
msgid "Reset user MFA success"
msgstr "重置用户MFA成功"
#: users/templates/users/user_group_detail.html:22
#: users/templates/users/user_group_granted_asset.html:18
#: users/views/group.py:82
#: users/views/group.py:83
msgid "User group detail"
msgstr "用户组详情"
......@@ -5005,24 +5043,24 @@ msgstr "用户组删除失败"
msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!"
#: users/templates/users/user_list.html:267
#: users/templates/users/user_list.html:262
msgid "User Deleted."
msgstr "已被删除"
#: users/templates/users/user_list.html:268
#: users/templates/users/user_list.html:272
#: users/templates/users/user_list.html:263
#: users/templates/users/user_list.html:267
msgid "User Delete"
msgstr "删除"
#: users/templates/users/user_list.html:271
#: users/templates/users/user_list.html:266
msgid "User Deleting failed."
msgstr "用户删除失败"
#: users/templates/users/user_list.html:324
#: users/templates/users/user_list.html:327
msgid "User is expired"
msgstr "用户已失效"
#: users/templates/users/user_list.html:327
#: users/templates/users/user_list.html:330
msgid "User is inactive"
msgstr "用户已禁用"
......@@ -5327,7 +5365,7 @@ msgstr ""
msgid "User group list"
msgstr "用户组列表"
#: users/views/group.py:99
#: users/views/group.py:100
msgid "User group granted asset"
msgstr "用户组授权资产"
......@@ -5364,47 +5402,47 @@ msgstr "密码不一致"
msgid "First login"
msgstr "首次登录"
#: users/views/user.py:140
#: users/views/user.py:141
msgid "Bulk update user success"
msgstr "批量更新用户成功"
#: users/views/user.py:168
#: users/views/user.py:169
msgid "Bulk update user"
msgstr "批量更新用户"
#: users/views/user.py:211
#: users/views/user.py:218
msgid "User granted assets"
msgstr "用户授权资产"
#: users/views/user.py:244
#: users/views/user.py:251
msgid "Profile setting"
msgstr "个人信息设置"
#: users/views/user.py:264
#: users/views/user.py:271
msgid "Password update"
msgstr "密码更新"
#: users/views/user.py:299
#: users/views/user.py:306
msgid "Public key update"
msgstr "密钥更新"
#: users/views/user.py:341
#: users/views/user.py:348
msgid "Password invalid"
msgstr "用户名或密码无效"
#: users/views/user.py:441
#: users/views/user.py:448
msgid "MFA enable success"
msgstr "MFA 绑定成功"
#: users/views/user.py:442
#: users/views/user.py:449
msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:444
#: users/views/user.py:451
msgid "MFA disable success"
msgstr "MFA 解绑成功"
#: users/views/user.py:445
#: users/views/user.py:452
msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面"
......@@ -5412,21 +5450,7 @@ msgstr "MFA 解绑成功,返回登录页面"
msgid "Password length"
msgstr "密码长度"
#: xpack/plugins/change_auth_plan/forms.py:51
#: xpack/plugins/change_auth_plan/models.py:213
msgid "* For security, do not change {} user's password"
msgstr "* 为了安全,禁止更改 {} 用户的密码"
#: xpack/plugins/change_auth_plan/forms.py:61
msgid "* Please enter custom password"
msgstr "* 请输入自定义密码"
#: xpack/plugins/change_auth_plan/forms.py:70
#: xpack/plugins/cloud/serializers.py:73
msgid "* Please enter a valid crontab expression"
msgstr "* 请输入有效的 crontab 表达式"
#: xpack/plugins/change_auth_plan/forms.py:123
#: xpack/plugins/change_auth_plan/forms.py:66
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:60
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:81
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17
......@@ -5434,10 +5458,13 @@ msgstr "* 请输入有效的 crontab 表达式"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:41
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:72
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16
#: xpack/plugins/gathered_user/forms.py:13
#: xpack/plugins/gathered_user/forms.py:41
#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:32
msgid "Periodic perform"
msgstr "定时执行"
#: xpack/plugins/change_auth_plan/forms.py:127
#: xpack/plugins/change_auth_plan/forms.py:70
msgid ""
"Tips: The username of the user on the asset to be modified. if the user "
"exists, change the password; If the user does not exist, create the user."
......@@ -5445,11 +5472,13 @@ msgstr ""
"提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果"
"用户不存在,则创建用户。"
#: xpack/plugins/change_auth_plan/forms.py:131 xpack/plugins/cloud/forms.py:90
#: xpack/plugins/change_auth_plan/forms.py:74 xpack/plugins/cloud/forms.py:90
#: xpack/plugins/gathered_user/forms.py:44
msgid "Tips: (Units: hour)"
msgstr "提示:(单位: 时)"
#: xpack/plugins/change_auth_plan/forms.py:132 xpack/plugins/cloud/forms.py:91
#: xpack/plugins/change_auth_plan/forms.py:75 xpack/plugins/cloud/forms.py:91
#: xpack/plugins/gathered_user/forms.py:45
msgid ""
"eg: Every Sunday 03:05 run <5 3 * * 0> <br> Tips: Using 5 digits linux "
"crontab expressions <min hour day month week> (<a href='https://tool.lu/"
......@@ -5461,88 +5490,117 @@ msgstr ""
"具</a>) <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行"
#: xpack/plugins/change_auth_plan/meta.py:9
#: xpack/plugins/change_auth_plan/models.py:114
#: xpack/plugins/change_auth_plan/models.py:257
#: xpack/plugins/change_auth_plan/models.py:116
#: xpack/plugins/change_auth_plan/models.py:256
#: xpack/plugins/change_auth_plan/views.py:33
#: xpack/plugins/change_auth_plan/views.py:50
#: xpack/plugins/change_auth_plan/views.py:72
#: xpack/plugins/change_auth_plan/views.py:87
#: xpack/plugins/change_auth_plan/views.py:114
#: xpack/plugins/change_auth_plan/views.py:129
#: xpack/plugins/change_auth_plan/views.py:144
#: xpack/plugins/change_auth_plan/views.py:74
#: xpack/plugins/change_auth_plan/views.py:90
#: xpack/plugins/change_auth_plan/views.py:117
#: xpack/plugins/change_auth_plan/views.py:132
#: xpack/plugins/change_auth_plan/views.py:147
msgid "Change auth plan"
msgstr "改密计划"
#: xpack/plugins/change_auth_plan/models.py:55
#: xpack/plugins/change_auth_plan/models.py:57
msgid "Custom password"
msgstr "自定义密码"
#: xpack/plugins/change_auth_plan/models.py:56
#: xpack/plugins/change_auth_plan/models.py:58
msgid "All assets use the same random password"
msgstr "所有资产使用相同的随机密码"
#: xpack/plugins/change_auth_plan/models.py:57
#: xpack/plugins/change_auth_plan/models.py:59
msgid "All assets use different random password"
msgstr "所有资产使用不同的随机密码"
#: xpack/plugins/change_auth_plan/models.py:76
#: xpack/plugins/change_auth_plan/models.py:145
#: xpack/plugins/change_auth_plan/models.py:78
#: xpack/plugins/change_auth_plan/models.py:147
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100
#: xpack/plugins/cloud/models.py:165 xpack/plugins/cloud/models.py:219
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91
#: xpack/plugins/gathered_user/models.py:35
#: xpack/plugins/gathered_user/models.py:72
msgid "Cycle perform"
msgstr "周期执行"
#: xpack/plugins/change_auth_plan/models.py:81
#: xpack/plugins/change_auth_plan/models.py:143
#: xpack/plugins/change_auth_plan/models.py:83
#: xpack/plugins/change_auth_plan/models.py:145
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92
#: xpack/plugins/cloud/models.py:170 xpack/plugins/cloud/models.py:217
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83
#: xpack/plugins/gathered_user/models.py:40
#: xpack/plugins/gathered_user/models.py:70
msgid "Regularly perform"
msgstr "定期执行"
#: xpack/plugins/change_auth_plan/models.py:90
#: xpack/plugins/change_auth_plan/models.py:92
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74
msgid "Password rules"
msgstr "密码规则"
#: xpack/plugins/change_auth_plan/models.py:217
#: xpack/plugins/change_auth_plan/models.py:212
msgid "* For security, do not change {} user's password"
msgstr "* 为了安全,禁止更改 {} 用户的密码"
#: xpack/plugins/change_auth_plan/models.py:216
msgid "Assets is empty, please add the asset"
msgstr "资产为空,请添加资产"
#: xpack/plugins/change_auth_plan/models.py:261
#: xpack/plugins/change_auth_plan/models.py:260
msgid "Change auth plan snapshot"
msgstr "改密计划快照"
#: xpack/plugins/change_auth_plan/models.py:276
#: xpack/plugins/change_auth_plan/models.py:427
#: xpack/plugins/change_auth_plan/models.py:275
#: xpack/plugins/change_auth_plan/models.py:426
msgid "Change auth plan execution"
msgstr "改密计划执行"
#: xpack/plugins/change_auth_plan/models.py:436
#: xpack/plugins/change_auth_plan/models.py:435
msgid "Change auth plan execution subtask"
msgstr "改密计划执行子任务"
#: xpack/plugins/change_auth_plan/models.py:454
#: xpack/plugins/change_auth_plan/models.py:453
msgid "Authentication failed"
msgstr "认证失败"
#: xpack/plugins/change_auth_plan/models.py:456
#: xpack/plugins/change_auth_plan/models.py:455
msgid "Connection timeout"
msgstr "连接超时"
#: xpack/plugins/change_auth_plan/serializers.py:58
msgid "* For security, do not change {}'s password"
msgstr "* 为了安全,禁止更改 {} 的密码"
#: xpack/plugins/change_auth_plan/serializers.py:68
msgid "* Please enter custom password"
msgstr "* 请输入自定义密码"
#: xpack/plugins/change_auth_plan/serializers.py:78
msgid "* Please enter the correct password length"
msgstr "* 请输入正确的密码长度"
#: xpack/plugins/change_auth_plan/serializers.py:81
msgid "* Password length range 6-30 bits"
msgstr "* 密码长度范围 6-30 位"
#: xpack/plugins/change_auth_plan/serializers.py:97
#: xpack/plugins/cloud/serializers.py:73
msgid "* Please enter a valid crontab expression"
msgstr "* 请输入有效的 crontab 表达式"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:23
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:26
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:23
#: xpack/plugins/change_auth_plan/views.py:130
#: xpack/plugins/change_auth_plan/views.py:133
msgid "Plan execution list"
msgstr "执行列表"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:76
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:66
msgid "Add asset to this plan"
msgstr "添加资产"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:101
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:91
msgid "Add node to this plan"
msgstr "添加节点"
......@@ -5572,6 +5630,7 @@ msgstr "手动执行计划"
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:179
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:103
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:96
msgid "Execute failed"
msgstr "执行失败"
......@@ -5601,11 +5660,11 @@ msgstr "创建计划"
msgid "Plan list"
msgstr "计划列表"
#: xpack/plugins/change_auth_plan/views.py:73
#: xpack/plugins/change_auth_plan/views.py:75
msgid "Update plan"
msgstr "更新计划"
#: xpack/plugins/change_auth_plan/views.py:145
#: xpack/plugins/change_auth_plan/views.py:148
msgid "Plan execution task list"
msgstr "执行任务列表"
......@@ -5691,37 +5750,37 @@ msgstr "实例"
msgid "Date last sync"
msgstr "最后同步日期"
#: xpack/plugins/cloud/models.py:187 xpack/plugins/cloud/models.py:274
#: xpack/plugins/cloud/models.py:187 xpack/plugins/cloud/models.py:271
msgid "Sync instance task"
msgstr "同步实例任务"
#: xpack/plugins/cloud/models.py:268 xpack/plugins/cloud/models.py:291
#: xpack/plugins/cloud/models.py:265 xpack/plugins/cloud/models.py:288
msgid "Succeed"
msgstr "成功"
#: xpack/plugins/cloud/models.py:269
#: xpack/plugins/cloud/models.py:266
msgid "Partial succeed"
msgstr ""
#: xpack/plugins/cloud/models.py:284 xpack/plugins/cloud/models.py:316
#: xpack/plugins/cloud/models.py:281 xpack/plugins/cloud/models.py:313
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:71
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66
msgid "Date sync"
msgstr "同步日期"
#: xpack/plugins/cloud/models.py:292
#: xpack/plugins/cloud/models.py:289
msgid "Exist"
msgstr "存在"
#: xpack/plugins/cloud/models.py:297
#: xpack/plugins/cloud/models.py:294
msgid "Sync task"
msgstr "同步任务"
#: xpack/plugins/cloud/models.py:301
#: xpack/plugins/cloud/models.py:298
msgid "Sync instance task history"
msgstr "同步实例任务历史"
#: xpack/plugins/cloud/models.py:304
#: xpack/plugins/cloud/models.py:301
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:117
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:61
msgid "Instance"
......@@ -5841,6 +5900,53 @@ msgstr "创建同步实例任务"
msgid "Update sync Instance task"
msgstr "更新同步实例任务"
#: xpack/plugins/gathered_user/meta.py:11
#: xpack/plugins/gathered_user/views.py:21
#: xpack/plugins/gathered_user/views.py:34
msgid "Gathered user"
msgstr "收集用户"
#: xpack/plugins/gathered_user/models.py:33
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:18
msgid "Periodic"
msgstr "定时执行"
#: xpack/plugins/gathered_user/models.py:57
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:48
msgid "Gather user task"
msgstr "收集用户任务"
#: xpack/plugins/gathered_user/models.py:140
msgid "Task"
msgstr "任务"
#: xpack/plugins/gathered_user/models.py:152
msgid "gather user task execution"
msgstr "收集用户执行"
#: xpack/plugins/gathered_user/models.py:158
msgid "Assets is empty, please change nodes"
msgstr "资产为空,请更改节点"
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:7
#: xpack/plugins/gathered_user/views.py:50
msgid "Create task"
msgstr "创建任务"
#: xpack/plugins/gathered_user/views.py:22
msgid "Gathered user list"
msgstr "收集用户列表"
#: xpack/plugins/gathered_user/views.py:49
#: xpack/plugins/gathered_user/views.py:66 xpack/plugins/vault/meta.py:11
#: xpack/plugins/vault/views.py:23 xpack/plugins/vault/views.py:38
msgid "Vault"
msgstr "密码匣子"
#: xpack/plugins/gathered_user/views.py:67
msgid "Update task"
msgstr "更新任务"
#: xpack/plugins/interface/forms.py:17 xpack/plugins/interface/models.py:15
msgid "Title of login page"
msgstr "登录页面标题"
......@@ -6020,6 +6126,12 @@ msgstr "无效的许可证"
msgid "Admin"
msgstr "管理员"
#: xpack/plugins/orgs/forms.py:42
#, fuzzy
#| msgid "Select admins"
msgid "Select auditor"
msgstr "选择管理员"
#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26
#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:60
#: xpack/plugins/orgs/views.py:77
......@@ -6051,11 +6163,6 @@ msgstr "创建组织"
msgid "Update org"
msgstr "更新组织"
#: xpack/plugins/vault/meta.py:11 xpack/plugins/vault/views.py:23
#: xpack/plugins/vault/views.py:38
msgid "Vault"
msgstr "密码匣子"
#: xpack/plugins/vault/templates/vault/_xpack_import_modal.html:4
msgid "Import vault"
msgstr "导入密码"
......@@ -6068,14 +6175,20 @@ msgstr "密码匣子"
msgid "vault create"
msgstr "创建"
#~ msgid "* For security, do not change {}'s password"
#~ msgstr "* 为了安全,禁止更改 {} 的密码"
#~ msgid "Gather user plan"
#~ msgstr "收集用户计划"
#~ msgid "Task update"
#~ msgstr "更新"
#~ msgid "Task create"
#~ msgstr "创建"
#~ msgid "* Please enter the correct password length"
#~ msgstr "* 请输入正确的密码长度"
#~ msgid "%(hostname)s was %(action)s successfully"
#~ msgstr "%(hostname)s %(action)s成功"
#~ msgid "* Password length range 6-30 bits"
#~ msgstr "* 密码长度范围 6-30 位"
#~ msgid "%(name)s was %(action)s successfully"
#~ msgstr "%(name)s %(action)s成功"
#~ msgid "Loading..."
#~ msgstr "加载中..."
......
......@@ -6,7 +6,9 @@ import os
from django.conf import settings
from django.utils.timezone import get_current_timezone
from django.db.utils import ProgrammingError, OperationalError
from django_celery_beat.models import PeriodicTask, IntervalSchedule, CrontabSchedule
from django_celery_beat.models import (
PeriodicTask, IntervalSchedule, CrontabSchedule, PeriodicTasks
)
def create_or_update_celery_periodic_tasks(tasks):
......@@ -75,17 +77,20 @@ def create_or_update_celery_periodic_tasks(tasks):
task = PeriodicTask.objects.update_or_create(
defaults=defaults, name=name,
)
PeriodicTasks.update_changed()
return task
def disable_celery_periodic_task(task_name):
from django_celery_beat.models import PeriodicTask
PeriodicTask.objects.filter(name=task_name).update(enabled=False)
PeriodicTasks.update_changed()
def delete_celery_periodic_task(task_name):
from django_celery_beat.models import PeriodicTask
PeriodicTask.objects.filter(name=task_name).delete()
PeriodicTasks.update_changed()
def get_celery_task_log_path(task_id):
......
# -*- coding: utf-8 -*-
#
from django.conf import settings
from .ansible.inventory import BaseInventory
from common.utils import get_logger
......@@ -14,6 +15,7 @@ logger = get_logger(__file__)
class JMSBaseInventory(BaseInventory):
windows_ssh_default_ssh = settings.WINDOWS_SSH_DEFAULT_SHELL
def convert_to_ansible(self, asset, run_as_admin=False):
info = {
......@@ -33,7 +35,7 @@ class JMSBaseInventory(BaseInventory):
if asset.is_windows():
info["vars"].update({
"ansible_connection": "ssh",
"ansible_shell_type": "cmd",
"ansible_shell_type": self.windows_ssh_default_ssh,
})
for label in asset.labels.all():
info["vars"].update({
......@@ -49,7 +51,7 @@ class JMSBaseInventory(BaseInventory):
def make_proxy_command(asset):
gateway = asset.domain.random_gateway()
proxy_command_list = [
"ssh", "-p", str(gateway.port),
"ssh", "-o", "Port={}".format(gateway.port),
"-o", "StrictHostKeyChecking=no",
"{}@{}".format(gateway.username, gateway.ip),
"-W", "%h:%p", "-q",
......
# Generated by Django 2.1.7 on 2019-09-19 13:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ops', '0007_auto_20190724_2002'),
]
operations = [
migrations.AddField(
model_name='task',
name='date_updated',
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
),
migrations.AlterField(
model_name='task',
name='date_created',
field=models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Date created'),
),
migrations.AlterModelOptions(
name='task',
options={'get_latest_by': 'date_created',
'ordering': ('-date_updated',)},
),
]
......@@ -13,7 +13,7 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django_celery_beat.models import PeriodicTask
from common.utils import get_signer, get_logger
from common.utils import get_signer, get_logger, lazyproperty
from orgs.utils import set_to_root_org
from ..celery.utils import delete_celery_periodic_task, \
create_or_update_celery_periodic_tasks, \
......@@ -42,7 +42,8 @@ class Task(models.Model):
is_deleted = models.BooleanField(default=False)
comment = models.TextField(blank=True, verbose_name=_("Comment"))
created_by = models.CharField(max_length=128, blank=True, default='')
date_created = models.DateTimeField(auto_now_add=True, db_index=True)
date_created = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
__latest_adhoc = None
_ignore_auto_created_by = True
......@@ -51,16 +52,39 @@ class Task(models.Model):
return str(self.id).split('-')[-1]
@property
def latest_adhoc(self):
if not self.__latest_adhoc:
self.__latest_adhoc = self.get_latest_adhoc()
return self.__latest_adhoc
def versions(self):
return self.adhoc.all().count()
@property
def is_success(self):
if self.latest_history:
return self.latest_history.is_success
else:
return False
@property
def timedelta(self):
if self.latest_history:
return self.latest_history.timedelta
else:
return 0
@latest_adhoc.setter
def latest_adhoc(self, item):
self.__latest_adhoc = item
@property
def date_start(self):
if self.latest_history:
return self.latest_history.date_start
else:
return None
@property
def assets_amount(self):
return self.latest_adhoc.hosts.count()
@lazyproperty
def latest_adhoc(self):
return self.get_latest_adhoc()
@lazyproperty
def latest_history(self):
try:
return self.history.all().latest()
......@@ -139,6 +163,7 @@ class Task(models.Model):
class Meta:
db_table = 'ops_task'
unique_together = ('name', 'created_by')
ordering = ('-date_updated',)
get_latest_by = 'date_created'
......@@ -218,30 +243,35 @@ class AdHoc(models.Model):
hid = str(uuid.uuid4())
history = AdHocRunHistory(id=hid, adhoc=self, task=self.task)
time_start = time.time()
date_start = timezone.now()
is_success = False
try:
date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
history.date_start = timezone.now()
print(_("{} Start task: {}").format(date_start, self.task.name))
date_start_s = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(_("{} Start task: {}").format(date_start_s, self.task.name))
raw, summary = self._run_only()
date_end = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(_("{} Task finish").format(date_end))
history.is_finished = True
if summary.get('dark'):
history.is_success = False
else:
history.is_success = True
history.result = raw
history.summary = summary
is_success = summary.get('success', False)
return raw, summary
except Exception as e:
logger.error(e, exc_info=True)
return {}, {"dark": {"all": str(e)}, "contacted": []}
summary = {}
raw = {"dark": {"all": str(e)}, "contacted": []}
return raw, summary
finally:
history.date_finished = timezone.now()
history.timedelta = time.time() - time_start
history.save()
date_end = timezone.now()
date_end_s = date_end.strftime('%Y-%m-%d %H:%M:%S')
print(_("{} Task finish").format(date_end_s))
print('.\n\n.')
AdHocRunHistory.objects.filter(id=history.id).update(
date_start=date_start,
is_finished=True,
is_success=is_success,
date_finished=timezone.now(),
timedelta=time.time() - time_start
)
def _run_only(self):
Task.objects.filter(id=self.task.id).update(date_updated=timezone.now())
runner = AdHocRunner(self.inventory, options=self.options)
try:
result = runner.run(
......
......@@ -19,7 +19,12 @@ class CeleryTaskSerializer(serializers.Serializer):
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__'
fields = [
'id', 'name', 'interval', 'crontab', 'is_periodic',
'is_deleted', 'comment', 'created_by', 'date_created',
'versions', 'is_success', 'timedelta', 'assets_amount',
'date_updated', 'history_summary',
]
class AdHocSerializer(serializers.ModelSerializer):
......
......@@ -2,6 +2,7 @@
import os
import subprocess
import datetime
import time
from django.conf import settings
from celery import shared_task, subtask
......@@ -122,3 +123,22 @@ def hello123():
def hello_callback(result):
print(result)
print("Hello callback")
@shared_task
def add(a, b):
time.sleep(5)
return a + b
@shared_task
def add_m(x):
from celery import chain
a = range(x)
b = [a[i:i + 10] for i in range(0, len(a), 10)]
s = list()
s.append(add.s(b[0], b[1]))
for i in b[1:]:
s.append(add.s(i))
res = chain(*tuple(s))()
return res
{% extends '_base_list.html' %}
{% load i18n %}
{% load static %}
{% block content_left_head %}
{# <div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create task" %} </a></div>#}
{% endblock %}
{% block table_search %}
<form id="search_form" method="get" action="" class="pull-right form-inline">
<div class="input-group">
<input type="text" class="form-control input-sm" name="keyword" placeholder="{% trans 'Search' %}" value="{{ keyword }}">
</div>
<div class="input-group">
<div class="input-group-btn">
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
{% trans "Search" %}
</button>
</div>
</div>
</form>
{% endblock %}
{% block table_head %}
<th class="text-center"></th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Run times' %}</th>
<th class="text-center">{% trans 'Versions' %}</th>
<th class="text-center">{% trans 'Hosts' %}</th>
<th class="text-center">{% trans 'Success' %}</th>
<th class="text-center">{% trans 'Date' %}</th>
<th class="text-center">{% trans 'Time' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
{% endblock %}
{% block table_body %}
{% for object in task_list %}
<tr class="gradeX">
<td class="text-center"><input type="checkbox" class="cbx-term"> </td>
<td class="text-left"><a href="{% url 'ops:task-detail' pk=object.id %}">{{ object.name }}</a></td>
<td class="text-center">
<span class="text-danger">{{ object.history_summary.failed }}</span>/<span class="text-navy">{{ object.history_summary.success}}</span>/{{ object.history_summary.total}}
</td>
<td class="text-center">{{ object.adhoc.all | length}}</td>
<td class="text-center">{{ object.latest_adhoc.hosts | length}}</td>
<td class="text-center">
{% if object.latest_history %}
{% if object.latest_history.is_success %}
<i class="fa fa-check text-navy"></i>
{% else %}
<i class="fa fa-times text-danger"></i>
{% endif %}
{% endif %}
</td>
<td class="text-center">{{ object.latest_history.date_start }}</td>
<td class="text-center">
{% if object.latest_history %}
{{ object.latest_history.timedelta|floatformat }} s
{% endif %}
</td>
<td class="text-center">
<a data-uid="{{ object.id }}" class="btn btn-xs btn-primary btn-run">{% trans "Run" %}</a>
<a data-uid="{{ object.id }}" class="btn btn-xs btn-danger btn-del">{% trans "Delete" %}</a>
</td>
{% load i18n static %}
{% block table_search %}{% endblock %}
{% block table_container %}
<table class="table table-striped table-bordered table-hover " id="task_list_table">
<thead>
<tr>
<th class="text-center">
<input id="" type="checkbox" class="ipt_check_all">
</th>
<th class="text-left">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Run times' %}</th>
<th class="text-center">{% trans 'Versions' %}</th>
<th class="text-center">{% trans 'Hosts' %}</th>
<th class="text-center">{% trans 'Success' %}</th>
<th class="text-center">{% trans 'Date' %}</th>
<th class="text-center">{% trans 'Time' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
{% endfor %}
</thead>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function() {
$('table').DataTable({
"searching": false,
"paging": false,
"bInfo" : false,
"order": [],
"columnDefs": [
{ "targets": 0, "orderable": false },
{ "targets": 4, "orderable": false },
{ "targets": 5, "orderable": false },
{ "targets": 8, "orderable": false }
]
});
$('.select2').select2({
dropdownAutoWidth : true,
width: 'auto'
});
$('#date .input-daterange').datepicker({
format: "yyyy-mm-dd",
todayBtn: "linked",
keyboardNavigation: false,
forceParse: false,
calendarWeeks: true,
autoclose: true
});
$(document).ready(function () {
var options = {
ele: $('#task_list_table'),
buttons: [],
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var innerHtml = '<a href="{% url "ops:task-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'
innerHtml = innerHtml.replace('{{ DEFAULT_PK }}', rowData.id);
$(td).html(innerHtml);
}},
{targets: 2, createdCell: function (td, cellData) {
var innerHtml = '<span class="text-danger">failed</span>/<span class="text-navy">success</span>/total';
if (cellData) {
innerHtml = innerHtml.replace('failed', cellData.failed)
.replace('success', cellData.success)
.replace('total', cellData.total);
$(td).html(innerHtml);
} else {
$(td).html('')
}
}},
{targets: 5, createdCell: function (td, cellData) {
var successBtn = '<i class="fa fa-check text-navy"></i>';
var failedBtn = '<i class="fa fa-times text-danger"></i>';
if (cellData) {
$(td).html(successBtn)
} else {
$(td).html(failedBtn)
}
}},
{targets: 6, createdCell: function (td, cellData) {
$(td).html(toSafeLocalDateStr(cellData));
}},
{targets: 7, createdCell: function (td, cellData) {
var delta = readableSecond(cellData);
$(td).html(delta);
}},
{
targets: 8,
createdCell: function (td, cellData, rowData) {
var runBtn = '<a data-uid="ID" class="btn btn-xs btn-primary btn-run">{% trans "Run" %}</a> '.replace('ID', cellData);
var delBtn = '<a data-uid="ID" class="btn btn-xs btn-danger btn-del">{% trans "Delete" %}</a>'.replace('ID', cellData);
$(td).html(runBtn + delBtn)
}
}
],
ajax_url: '{% url "api-ops:task-list" %}',
columns: [
{data: "id"}, {data: "name", className: "text-left"}, {data: "history_summary", orderable: false},
{data: "versions", orderable: false}, {data: "assets_amount", orderable: false},
{data: "is_success", orderable: false}, {data: "date_updated"},
{data: "timedelta", orderable:false}, {data: "id", orderable: false},
],
order: [],
op_html: $('#actions').html()
};
jumpserver.initServerSideDataTable(options);
}).on('click', '.btn-del', function () {
var $this = $(this);
var name = $this.closest("tr").find(":nth-child(2)").children('a').html();
......@@ -122,7 +108,6 @@ $(document).ready(function() {
success: success,
success_message: "{% trans 'Task start: ' %}" + " " + name
});
})
</script>
{% endblock %}
......
......@@ -2,7 +2,7 @@
from django.utils.translation import ugettext as _
from django.conf import settings
from django.views.generic import ListView, DetailView
from django.views.generic import ListView, DetailView, TemplateView
from common.mixins import DatetimeSearchMixin
from common.permissions import PermissionsMixin, IsOrgAdmin
......@@ -17,7 +17,7 @@ __all__ = [
]
class TaskListView(PermissionsMixin, DatetimeSearchMixin, ListView):
class TaskListView(PermissionsMixin, TemplateView):
paginate_by = settings.DISPLAY_PER_PAGE
model = Task
ordering = ('-date_created',)
......@@ -26,27 +26,10 @@ class TaskListView(PermissionsMixin, DatetimeSearchMixin, ListView):
keyword = ''
permission_classes = [IsOrgAdmin]
def get_queryset(self):
queryset = super().get_queryset()
if current_org.is_real():
queryset = queryset.filter(created_by=current_org.id)
else:
queryset = queryset.filter(created_by='')
self.keyword = self.request.GET.get('keyword', '')
if self.keyword:
queryset = queryset.filter(
name__icontains=self.keyword,
)
return queryset
def get_context_data(self, **kwargs):
context = {
'app': _('Ops'),
'action': _('Task list'),
'date_from': self.date_from,
'date_to': self.date_to,
'keyword': self.keyword,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......
......@@ -3,7 +3,7 @@
from django.shortcuts import get_object_or_404
from rest_framework.viewsets import ModelViewSet
from rest_framework_bulk import BulkModelViewSet
from common.mixins import IDInCacheFilterMixin
from common.mixins import CommonApiMixin
from ..utils import set_to_root_org
from ..models import Organization
......@@ -20,20 +20,23 @@ class RootOrgViewMixin:
return super().dispatch(request, *args, **kwargs)
class OrgModelViewSet(IDInCacheFilterMixin, ModelViewSet):
def get_queryset(self):
return super().get_queryset().all()
class OrgBulkModelViewSet(IDInCacheFilterMixin, BulkModelViewSet):
class OrgQuerySetMixin:
def get_queryset(self):
queryset = super().get_queryset().all()
if hasattr(self, 'swagger_fake_view'):
return queryset[:1]
if hasattr(self, 'action') and self.action == 'list' and \
hasattr(self, 'serializer_class') and \
hasattr(self.serializer_class, 'setup_eager_loading'):
queryset = self.serializer_class.setup_eager_loading(queryset)
return queryset
class OrgModelViewSet(CommonApiMixin, OrgQuerySetMixin, ModelViewSet):
pass
class OrgBulkModelViewSet(CommonApiMixin, OrgQuerySetMixin, BulkModelViewSet):
def allow_bulk_destroy(self, qs, filtered):
if qs.count() <= filtered.count():
return False
......
......@@ -11,7 +11,8 @@ from ..utils import get_current_org_id_for_serializer
__all__ = [
"OrgResourceSerializerMixin", "BulkOrgResourceSerializerMixin",
"BulkOrgResourceModelSerializer", "OrgMembershipSerializerMixin"
"BulkOrgResourceModelSerializer", "OrgMembershipSerializerMixin",
"OrgResourceModelSerializerMixin",
]
......@@ -42,6 +43,10 @@ class OrgResourceSerializerMixin(serializers.Serializer):
return fields
class OrgResourceModelSerializerMixin(OrgResourceSerializerMixin, serializers.ModelSerializer):
pass
class BulkOrgResourceSerializerMixin(BulkSerializerMixin, OrgResourceSerializerMixin):
pass
......
......@@ -18,6 +18,8 @@ def get_org_from_request(request):
def set_current_org(org):
if isinstance(org, str):
org = Organization.get_instance(org)
setattr(thread_local, 'current_org_id', org.id)
......
......@@ -180,6 +180,7 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
users = serializer.validated_data.get('users')
if users:
perm.users.remove(*tuple(users))
perm.save()
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
......@@ -197,6 +198,7 @@ class AssetPermissionAddUserApi(RetrieveUpdateAPIView):
users = serializer.validated_data.get('users')
if users:
perm.users.add(*tuple(users))
perm.save()
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
......@@ -217,6 +219,7 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
assets = serializer.validated_data.get('assets')
if assets:
perm.assets.remove(*tuple(assets))
perm.save()
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
......@@ -234,6 +237,7 @@ class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
assets = serializer.validated_data.get('assets')
if assets:
perm.assets.add(*tuple(assets))
perm.save()
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
......
# -*- coding: utf-8 -*-
#
import uuid
from django.db.models import Q
from rest_framework.generics import get_object_or_404
from assets.utils import LabelFilterMixin
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.utils import get_logger
from orgs.utils import set_to_root_org
from ..hands import User, Asset, SystemUser
from .. import serializers
from ..hands import User, UserGroup
logger = get_logger(__name__)
__all__ = [
'UserPermissionMixin',
'UserPermissionMixin', 'UserGroupPermissionMixin',
]
......@@ -45,101 +41,12 @@ class UserPermissionMixin:
return super().get_permissions()
class GrantAssetsMixin(LabelFilterMixin):
serializer_class = serializers.AssetGrantedSerializer
def get_serializer_queryset(self, queryset):
assets_ids = []
system_users_ids = set()
for asset in queryset:
assets_ids.append(asset["id"])
system_users_ids.update(set(asset["system_users"]))
assets = Asset.objects.filter(id__in=assets_ids).only(
*self.serializer_class.Meta.only_fields
)
assets_map = {asset.id: asset for asset in assets}
system_users = SystemUser.objects.filter(id__in=system_users_ids).only(
*self.serializer_class.system_users_only_fields
)
system_users_map = {s.id: s for s in system_users}
data = []
for item in queryset:
i = item["id"]
asset = assets_map.get(i)
if not asset:
continue
_system_users = item["system_users"]
system_users_granted = []
for sid, action in _system_users.items():
system_user = system_users_map.get(sid)
if not system_user:
continue
if not asset.has_protocol(system_user.protocol):
continue
system_user.actions = action
system_users_granted.append(system_user)
asset.system_users_granted = system_users_granted
data.append(asset)
return data
def get_serializer(self, assets_items=None, many=True):
if assets_items is None:
assets_items = []
assets_items = self.get_serializer_queryset(assets_items)
return super().get_serializer(assets_items, many=many)
def filter_queryset_by_id(self, assets_items):
i = self.request.query_params.get("id")
if not i:
return assets_items
try:
pk = uuid.UUID(i)
except ValueError:
return assets_items
assets_map = {asset['id']: asset for asset in assets_items}
if pk in assets_map:
return [assets_map.get(pk)]
else:
return []
def search_queryset(self, assets_items):
search = self.request.query_params.get("search")
if not search:
return assets_items
assets_map = {asset['id']: asset for asset in assets_items}
assets_ids = set(assets_map.keys())
assets_ids_search = Asset.objects.filter(id__in=assets_ids).filter(
Q(hostname__icontains=search) | Q(ip__icontains=search)
).values_list('id', flat=True)
return [assets_map.get(asset_id) for asset_id in assets_ids_search]
def filter_queryset_by_label(self, assets_items):
labels_id = self.get_filter_labels_ids()
if not labels_id:
return assets_items
assets_map = {asset['id']: asset for asset in assets_items}
assets_matched = Asset.objects.filter(id__in=assets_map.keys())
for label_id in labels_id:
assets_matched = assets_matched.filter(labels=label_id)
assets_ids_matched = assets_matched.values_list('id', flat=True)
return [assets_map.get(asset_id) for asset_id in assets_ids_matched]
class UserGroupPermissionMixin:
obj = None
def sort_queryset(self, assets_items):
order_by = self.request.query_params.get('order', 'hostname')
def get_obj(self):
user_group_id = self.kwargs.get('pk', '')
user_group = get_object_or_404(UserGroup, id=user_group_id)
return user_group
if order_by not in ['hostname', '-hostname', 'ip', '-ip']:
order_by = 'hostname'
assets_map = {asset['id']: asset for asset in assets_items}
assets_ids_search = Asset.objects.filter(id__in=assets_map.keys())\
.order_by(order_by)\
.values_list('id', flat=True)
return [assets_map.get(asset_id) for asset_id in assets_ids_search]
def filter_queryset(self, assets_items):
assets_items = self.filter_queryset_by_id(assets_items)
assets_items = self.search_queryset(assets_items)
assets_items = self.filter_queryset_by_label(assets_items)
assets_items = self.sort_queryset(assets_items)
return assets_items
# -*- coding: utf-8 -*-
#
from django.shortcuts import get_object_or_404
from ..hands import UserGroup
from . import user_permission as uapi
from .mixin import UserGroupPermissionMixin
__all__ = [
'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi',
......@@ -17,15 +15,6 @@ __all__ = [
]
class UserGroupPermissionMixin:
obj = None
def get_object(self):
user_group_id = self.kwargs.get('pk', '')
user_group = get_object_or_404(UserGroup, id=user_group_id)
return user_group
class UserGroupGrantedAssetsApi(UserGroupPermissionMixin, uapi.UserGrantedAssetsApi):
pass
......
......@@ -51,8 +51,7 @@ class GetUserAssetPermissionActionsApi(UserAssetPermissionMixin,
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_id)
system_users_actions = self.util.get_asset_system_users_with_actions(
asset)
system_users_actions = self.util.get_asset_system_users_with_actions(asset)
actions = system_users_actions.get(system_user)
return {"actions": actions}
......@@ -103,8 +102,7 @@ class UserGrantedAssetSystemUsersApi(UserAssetPermissionMixin, ListAPIView):
def get_queryset(self):
asset_id = self.kwargs.get('asset_id')
asset = get_object_or_404(Asset, id=asset_id)
system_users_with_actions = self.util.get_asset_system_users_with_actions(
asset)
system_users_with_actions = self.util.get_asset_system_users_with_actions(asset)
system_users = []
for system_user, actions in system_users_with_actions.items():
system_user.actions = actions
......
# -*- coding: utf-8 -*-
#
from common.utils import lazyproperty
from common.tree import TreeNodeSerializer
from ..mixin import UserPermissionMixin
from ...utils import AssetPermissionUtilV2, ParserNode
from ...hands import Node
from common.tree import TreeNodeSerializer
from ...hands import Node, Asset
class UserAssetPermissionMixin(UserPermissionMixin):
util = None
tree = None
def initial(self, *args, **kwargs):
super().initial(*args, *kwargs)
@lazyproperty
def util(self):
cache_policy = self.request.query_params.get('cache_policy', '0')
system_user_id = self.request.query_params.get("system_user")
self.util = AssetPermissionUtilV2(self.obj, cache_policy=cache_policy)
util = AssetPermissionUtilV2(self.obj, cache_policy=cache_policy)
if system_user_id:
self.util.filter_permissions(system_users=system_user_id)
self.tree = self.util.get_user_tree()
util.filter_permissions(system_users=system_user_id)
return util
@lazyproperty
def tree(self):
return self.util.get_user_tree()
class UserNodeTreeMixin:
......@@ -41,7 +45,9 @@ class UserNodeTreeMixin:
queryset = self.parse_nodes_to_queryset(queryset)
return queryset
def get_serializer(self, queryset, many=True, **kwargs):
def get_serializer(self, queryset=None, many=True, **kwargs):
if queryset is None:
queryset = Node.objects.none()
queryset = self.get_serializer_queryset(queryset)
queryset.sort()
return super().get_serializer(queryset, many=many, **kwargs)
......@@ -64,7 +70,9 @@ class UserAssetTreeMixin:
_queryset = self.parse_assets_to_queryset(queryset, None)
return _queryset
def get_serializer(self, queryset, many=True, **kwargs):
def get_serializer(self, queryset=None, many=True, **kwargs):
if queryset is None:
queryset = Asset.objects.none()
queryset = self.get_serializer_queryset(queryset)
queryset.sort()
return super().get_serializer(queryset, many=many, **kwargs)
......@@ -7,7 +7,7 @@ from rest_framework.generics import (
)
from common.permissions import IsOrgAdminOrAppUser
from common.utils import get_logger
from common.utils import get_logger, timeit
from ...hands import Node
from ... import serializers
from .mixin import UserAssetPermissionMixin, UserAssetTreeMixin
......
......@@ -19,18 +19,6 @@ permission_m2m_senders = (
)
@on_transaction_commit
def on_permission_m2m_change(sender, action='', **kwargs):
if not action.startswith('post'):
return
logger.debug('Asset permission m2m changed, refresh user tree cache')
AssetPermissionUtilV2.expire_all_user_tree_cache()
for sender in permission_m2m_senders:
m2m_changed.connect(on_permission_m2m_change, sender=sender)
@receiver([post_save, post_delete], sender=AssetPermission)
@on_transaction_commit
def on_permission_change(sender, action='', **kwargs):
......
# coding: utf-8
import time
import pickle
import threading
from collections import defaultdict
from functools import reduce
from hashlib import md5
......@@ -12,7 +12,7 @@ from django.db.models import Q
from django.conf import settings
from orgs.utils import set_to_root_org
from common.utils import get_logger, timeit
from common.utils import get_logger, timeit, lazyproperty
from common.tree import TreeNode
from assets.utils import TreeService
from ..models import AssetPermission
......@@ -126,27 +126,30 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
'comment', 'is_active', 'os', 'org_id'
)
def __init__(self, obj, cache_policy='0'):
def __init__(self, obj=None, cache_policy='0'):
self.object = obj
self.cache_policy = cache_policy
self.obj_id = str(obj.id)
self.obj_id = str(obj.id) if obj else None
self._permissions = None
self._permissions_id = None # 标记_permission的唯一值
self._filter_id = 'None' # 当通过filter更改 permission是标记
self.change_org_if_need()
self._user_tree = None
self._user_tree_filter_id = 'None'
self.full_tree = Node.tree()
self.mutex = threading.Lock()
@staticmethod
def change_org_if_need():
set_to_root_org()
@lazyproperty
def full_tree(self):
return Node.tree()
@property
def permissions(self):
if self._permissions:
return self._permissions
if self.object is None:
return AssetPermission.objects.none()
object_cls = self.object.__class__.__name__
func = self.get_permissions_map[object_cls]
permissions = func(self.object)
......@@ -159,7 +162,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self._permissions = self.permissions.filter(**filters)
self._filter_id = md5(filters_json.encode()).hexdigest()
@property
@lazyproperty
def user_tree(self):
return self.get_user_tree()
......@@ -303,27 +306,26 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
@timeit
def get_user_tree(self):
# 使用锁,保证多次获取tree的时候顺序执行,可以使用缓存
with self.mutex:
user_tree = self.get_user_tree_from_local()
if user_tree:
return user_tree
user_tree = self.get_user_tree_from_cache_if_need()
if user_tree:
self.set_user_tree_to_local(user_tree)
return user_tree
user_tree = TreeService()
full_tree_root = self.full_tree.root_node()
user_tree.create_node(
tag=full_tree_root.tag,
identifier=full_tree_root.identifier
)
self.add_direct_nodes_to_user_tree(user_tree)
self.add_single_assets_node_to_user_tree(user_tree)
self.parse_user_tree_to_full_tree(user_tree)
self.add_empty_node_if_need(user_tree)
self.set_user_tree_to_cache_if_need(user_tree)
user_tree = self.get_user_tree_from_local()
if user_tree:
return user_tree
user_tree = self.get_user_tree_from_cache_if_need()
if user_tree:
self.set_user_tree_to_local(user_tree)
return user_tree
user_tree = TreeService()
full_tree_root = self.full_tree.root_node()
user_tree.create_node(
tag=full_tree_root.tag,
identifier=full_tree_root.identifier
)
self.add_direct_nodes_to_user_tree(user_tree)
self.add_single_assets_node_to_user_tree(user_tree)
self.parse_user_tree_to_full_tree(user_tree)
self.add_empty_node_if_need(user_tree)
self.set_user_tree_to_cache_if_need(user_tree)
self.set_user_tree_to_local(user_tree)
return user_tree
# Todo: 是否可以获取多个资产的系统用户
def get_asset_system_users_with_actions(self, asset):
......@@ -332,17 +334,13 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
for node in nodes:
ancestor_keys = node.get_ancestor_keys(with_self=True)
nodes_keys_related.update(set(ancestor_keys))
pattern = []
for key in nodes_keys_related:
pattern.append(r'^{0}$|^{0}:'.format(key))
pattern = '|'.join(list(pattern))
kwargs = {"assets": asset}
if pattern:
kwargs["nodes__key__regex"] = pattern
if nodes_keys_related:
kwargs["nodes__key__in"] = nodes_keys_related
queryset = self.permissions
if len(kwargs) == 1:
if kwargs == 1:
queryset = queryset.filter(**kwargs)
elif len(kwargs) > 1:
kwargs = [{k: v} for k, v in kwargs.items()]
......@@ -376,33 +374,11 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
nodes_keys = Node.clean_children_keys(nodes_keys)
return nodes_keys, assets_ids
@staticmethod
def filter_assets_by_or_kwargs(kwargs):
if len(kwargs) == 1:
queryset = Asset.objects.filter(**kwargs)
elif len(kwargs) > 1:
kwargs = [{k: v} for k, v in kwargs.items()]
args = [Q(**kw) for kw in kwargs]
args = reduce(lambda x, y: x | y, args)
queryset = Asset.objects.filter(args)
else:
queryset = Asset.objects.none()
return queryset
@timeit
def get_assets(self):
nodes_keys, assets_ids = self.get_permissions_nodes_and_assets()
pattern = set()
for key in nodes_keys:
pattern.add(r'^{0}$|^{0}:'.format(key))
pattern = '|'.join(list(pattern))
kwargs = {}
if assets_ids:
kwargs["id__in"] = assets_ids
if pattern:
kwargs["nodes__key__regex"] = pattern
queryset = self.filter_assets_by_or_kwargs(kwargs)
return queryset.valid().distinct()
queryset = Node.get_nodes_all_assets(nodes_keys, extra_assets_ids=assets_ids)
return queryset.valid()
def get_nodes_assets(self, node, deep=False):
if deep:
......@@ -410,7 +386,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
else:
assets_ids = self.user_tree.assets(node.key)
queryset = Asset.objects.filter(id__in=assets_ids)
return queryset.valid().distinct()
return queryset.valid()
def get_nodes(self):
return [n.identifier for n in self.user_tree.all_nodes_itr()]
......
......@@ -97,6 +97,8 @@ class LDAPUserListApi(generics.ListAPIView):
serializer_class = LDAPUserSerializer
def get_queryset(self):
if hasattr(self, 'swagger_fake_view'):
return []
util = LDAPUtil()
try:
users = util.search_user_items()
......
......@@ -153,6 +153,7 @@ function activeNav() {
} else {
$("#" + app).addClass('active');
$('#' + app + ' #' + resource).addClass('active');
$('#' + app + ' #' + resource.replace('-', '_')).addClass('active');
}
}
......@@ -1138,7 +1139,10 @@ function timeOffset(a, b) {
var start = safeDate(a);
var end = safeDate(b);
var offset = (end - start) / 1000;
return readableSecond(offset)
}
function readableSecond(offset) {
var days = offset / 3600 / 24;
var hours = offset / 3600;
var minutes = offset / 60;
......
......@@ -114,6 +114,9 @@
<ul class="nav nav-second-level">
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li>
<li id="command-execution"><a href="{% url 'ops:command-execution-start' %}">{% trans 'Batch command' %}</a></li>
{% if request.user.is_superuser %}
<li><a href="{% url 'flower-view' path='' %}" target="_blank" >{% trans 'Task monitor' %}</a></li>
{% endif %}
</ul>
</li>
{% endif %}
......@@ -171,7 +174,5 @@
<script>
$(document).ready(function () {
var current_org = '{{ CURRENT_ORG.name }}';
console.log(current_org);
})
</script>
\ No newline at end of file
......@@ -58,5 +58,5 @@ class TerminalSerializer(serializers.ModelSerializer):
class TerminalRegistrationSerializer(serializers.Serializer):
name = serializers.CharField(max_length=128)
comment = serializers.CharField(max_length=128)
comment = serializers.CharField(max_length=128, )
service_account = ServiceAccountSerializer(read_only=True)
......@@ -14,7 +14,7 @@ from common.permissions import (
IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser,
CanUpdateDeleteUser,
)
from common.mixins import IDInCacheFilterMixin
from common.mixins import CommonApiMixin
from common.utils import get_logger
from orgs.utils import current_org
from .. import serializers
......@@ -30,7 +30,7 @@ __all__ = [
]
class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
class UserViewSet(CommonApiMixin, BulkModelViewSet):
filter_fields = ('username', 'email', 'name', 'id')
search_fields = filter_fields
queryset = User.objects.exclude(role=User.ROLE_APP)
......
......@@ -72,16 +72,10 @@ function initTable() {
$(td).html(cellData);
}},
{targets: 3, createdCell: function (td, cellData) {
function success(systemUsers) {
var users = [];
$.each(systemUsers, function (id, data) {
var name = htmlEscape(data.name);
users.push(name);
});
$(td).html(users.join(','))
}
$(td).html("{% trans 'Loading' %}");
getGrantedAssetSystemUsers(cellData, success)
var innerHtml = '<a class="btn-show-system-users" data-aid="99999999"> {% trans "Show" %} </a>'
.replace('99999999', cellData);
$(td).html(innerHtml);
}},
],
ajax_url: assetTableUrl,
......@@ -185,5 +179,19 @@ $(document).ready(function () {
var val = $(this).text();
$("#user_assets_table_filter input").val(val);
assetTable.search(val).draw();
})
}).on('click', '.btn-show-system-users', function () {
var $this = $(this);
var assetId = $this.data('aid');
function success(systemUsers) {
var users = [];
$.each(systemUsers, function (id, data) {
var name = htmlEscape(data.name);
users.push(name);
});
$this.parent().html(users.join(','))
}
getGrantedAssetSystemUsers(assetId, success)
})
</script>
......@@ -33,9 +33,9 @@
{% block custom_foot_js %}
<script>
var assetTableUrl = "{% url 'api-perms:user-assets' pk=object.id %}?cache_policy=1";
var selectUrl = '{% url "api-perms:user-node-assets" pk=object.id node_id=DEFAULT_PK %}?all=1';
var selectUrl = '{% url "api-perms:user-node-assets" pk=object.id node_id=DEFAULT_PK %}?cache_policy=1&all=1';
var treeUrl = "{% url 'api-perms:user-nodes-children-as-tree' pk=object.id %}?cache_policy=1";
var systemUsersUrl = "{% url 'api-perms:user-asset-system-users' pk=object.id asset_id=DEFAULT_PK %}";
var systemUsersUrl = "{% url 'api-perms:user-asset-system-users' pk=object.id asset_id=DEFAULT_PK %}?cache_policy=1";
$(document).ready(function () {
initTree();
......
......@@ -34,9 +34,9 @@
{% block custom_foot_js %}
<script>
var assetTableUrl = "{% url 'api-perms:user-group-assets' pk=object.id %}?cache_policy=1";
var selectUrl = '{% url "api-perms:user-group-node-assets" pk=object.id node_id=DEFAULT_PK %}?all=1';
var selectUrl = '{% url "api-perms:user-group-node-assets" pk=object.id node_id=DEFAULT_PK %}?&cache_policy=1&all=1';
var treeUrl = "{% url 'api-perms:user-group-nodes-children-as-tree' pk=object.id %}?cache_policy=1";
var systemUsersUrl = "{% url 'api-perms:user-group-asset-system-users' pk=object.id asset_id=DEFAULT_PK %}";
var systemUsersUrl = "{% url 'api-perms:user-group-asset-system-users' pk=object.id asset_id=DEFAULT_PK %}?cache_policy=1";
var showAssetHref = true; // Need input default true
......
......@@ -200,9 +200,13 @@ def is_running(s, unlink=True):
def parse_service(s):
all_services = ['gunicorn', 'celery_ansible', 'celery_default', 'beat']
all_services = [
'gunicorn', 'celery_ansible', 'celery_default', 'beat', 'flower'
]
if s == 'all':
return all_services
elif s == 'gunicorn':
return ['gunicorn', 'flower']
elif s == "celery":
return ["celery_ansible", "celery_default"]
elif "," in s:
......@@ -240,17 +244,19 @@ def get_start_gunicorn_kwargs():
def get_start_celery_ansible_kwargs():
print("\n- Start Celery as Distributed Task Queue")
print("\n- Start Celery as Distributed Task Queue: Ansible")
return get_start_worker_kwargs('ansible', 4)
def get_start_celery_default_kwargs():
print("\n- Start Celery as Distributed Task Queue: Celery")
return get_start_worker_kwargs('celery', 2)
def get_start_worker_kwargs(queue, num):
# Todo: Must set this environment, otherwise not no ansible result return
os.environ.setdefault('PYTHONOPTIMIZE', '1')
os.environ.setdefault('ANSIBLE_FORCE_COLOR', 'True')
if os.getuid() == 0:
os.environ.setdefault('C_FORCE_ROOT', '1')
......@@ -261,6 +267,24 @@ def get_start_worker_kwargs(queue, num):
'-l', 'INFO',
'-c', str(num),
'-Q', queue,
'-n', '{}@%h'.format(queue)
]
return {"cmd": cmd, "cwd": APPS_DIR}
def get_start_flower_kwargs():
print("\n- Start Flower as Task Monitor")
if os.getuid() == 0:
os.environ.setdefault('C_FORCE_ROOT', '1')
cmd = [
'celery', 'flower',
'-A', 'ops',
'-l', 'INFO',
'--url_prefix=flower',
'--auto_refresh=False',
'--max_tasks=1000',
'--tasks_columns=uuid,name,args,state,received,started,runtime,worker'
]
return {"cmd": cmd, "cwd": APPS_DIR}
......@@ -333,6 +357,7 @@ def start_service(s):
"celery_ansible": get_start_celery_ansible_kwargs,
"celery_default": get_start_celery_default_kwargs,
"beat": get_start_beat_kwargs,
"flower": get_start_flower_kwargs,
}
kwargs = services_kwargs.get(s)()
......@@ -449,7 +474,7 @@ if __name__ == '__main__':
)
parser.add_argument(
"service", type=str, default="all", nargs="?",
choices=("all", "gunicorn", "celery", "beat", "celery,beat"),
choices=("all", "gunicorn", "celery", "beat", "celery,beat", "flower"),
help="The service to start",
)
parser.add_argument('-d', '--daemon', nargs="?", const=1)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment