diff --git a/apps/accounts/api/account/task.py b/apps/accounts/api/account/task.py index c4f6ebd9f..07e6db35a 100644 --- a/apps/accounts/api/account/task.py +++ b/apps/accounts/api/account/task.py @@ -1,11 +1,12 @@ +from django.db.models import Q from rest_framework.generics import CreateAPIView from accounts import serializers +from accounts.models import Account from accounts.permissions import AccountTaskActionPermission from accounts.tasks import ( remove_accounts_task, verify_accounts_connectivity_task, push_accounts_to_assets_task ) -from assets.exceptions import NotSupportedTemporarilyError from authentication.permissions import UserConfirmation, ConfirmType __all__ = [ @@ -26,25 +27,35 @@ class AccountsTaskCreateAPI(CreateAPIView): ] return super().get_permissions() - def perform_create(self, serializer): - data = serializer.validated_data - accounts = data.get('accounts', []) - params = data.get('params') + @staticmethod + def get_account_ids(data, action): + account_type = 'gather_accounts' if action == 'remove' else 'accounts' + accounts = data.get(account_type, []) account_ids = [str(a.id) for a in accounts] - if data['action'] == 'push': - task = push_accounts_to_assets_task.delay(account_ids, params) - elif data['action'] == 'remove': - gather_accounts = data.get('gather_accounts', []) - gather_account_ids = [str(a.id) for a in gather_accounts] - task = remove_accounts_task.delay(gather_account_ids) + if action == 'remove': + return account_ids + + assets = data.get('assets', []) + asset_ids = [str(a.id) for a in assets] + ids = Account.objects.filter( + Q(id__in=account_ids) | Q(asset_id__in=asset_ids) + ).distinct().values_list('id', flat=True) + return [str(_id) for _id in ids] + + def perform_create(self, serializer): + data = serializer.validated_data + action = data['action'] + ids = self.get_account_ids(data, action) + + if action == 'push': + task = push_accounts_to_assets_task.delay(ids, data.get('params')) + elif action == 'remove': + task = remove_accounts_task.delay(ids) + elif action == 'verify': + task = verify_accounts_connectivity_task.delay(ids) else: - account = accounts[0] - asset = account.asset - if not asset.auto_config['ansible_enabled'] or \ - not asset.auto_config['ping_enabled']: - raise NotSupportedTemporarilyError() - task = verify_accounts_connectivity_task.delay(account_ids) + raise ValueError(f"Invalid action: {action}") data = getattr(serializer, '_data', {}) data["task"] = task.id diff --git a/apps/accounts/automations/backup_account/handlers.py b/apps/accounts/automations/backup_account/handlers.py index 763428308..c47454685 100644 --- a/apps/accounts/automations/backup_account/handlers.py +++ b/apps/accounts/automations/backup_account/handlers.py @@ -145,9 +145,9 @@ class AccountBackupHandler: wb = Workbook(filename) for sheet, data in data_map.items(): ws = wb.add_worksheet(str(sheet)) - for row in data: - for col, _data in enumerate(row): - ws.write_string(0, col, _data) + for row_index, row_data in enumerate(data): + for col_index, col_data in enumerate(row_data): + ws.write_string(row_index, col_index, col_data) wb.close() files.append(filename) timedelta = round((time.time() - time_start), 2) diff --git a/apps/accounts/automations/change_secret/database/oracle/main.yml b/apps/accounts/automations/change_secret/database/oracle/main.yml index 03eb5d45f..5a94f3184 100644 --- a/apps/accounts/automations/change_secret/database/oracle/main.yml +++ b/apps/accounts/automations/change_secret/database/oracle/main.yml @@ -39,3 +39,4 @@ login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" login_database: "{{ jms_asset.spec_info.db_name }}" + mode: "{{ account.mode }}" diff --git a/apps/accounts/automations/change_secret/manager.py b/apps/accounts/automations/change_secret/manager.py index 74dfc717e..ee6aa436f 100644 --- a/apps/accounts/automations/change_secret/manager.py +++ b/apps/accounts/automations/change_secret/manager.py @@ -4,6 +4,7 @@ from copy import deepcopy from django.conf import settings from django.utils import timezone +from django.utils.translation import gettext_lazy as _ from xlsxwriter import Workbook from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy @@ -161,7 +162,8 @@ class ChangeSecretManager(AccountBasePlaybookManager): print("Account not found, deleted ?") return account.secret = recorder.new_secret - account.save(update_fields=['secret']) + account.date_updated = timezone.now() + account.save(update_fields=['secret', 'date_updated']) def on_host_error(self, host, error, result): recorder = self.name_recorder_mapper.get(host) @@ -182,17 +184,33 @@ class ChangeSecretManager(AccountBasePlaybookManager): return False return True + @staticmethod + def get_summary(recorders): + total, succeed, failed = 0, 0, 0 + for recorder in recorders: + if recorder.status == 'success': + succeed += 1 + else: + failed += 1 + total += 1 + + summary = _('Success: %s, Failed: %s, Total: %s') % (succeed, failed, total) + return summary + def run(self, *args, **kwargs): if self.secret_type and not self.check_secret(): return super().run(*args, **kwargs) + recorders = list(self.name_recorder_mapper.values()) + summary = self.get_summary(recorders) + print(summary, end='') + if self.record_id: return - recorders = self.name_recorder_mapper.values() - recorders = list(recorders) - self.send_recorder_mail(recorders) - def send_recorder_mail(self, recorders): + self.send_recorder_mail(recorders, summary) + + def send_recorder_mail(self, recorders, summary): recipients = self.execution.recipients if not recorders or not recipients: return @@ -212,7 +230,7 @@ class ChangeSecretManager(AccountBasePlaybookManager): attachment = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.zip') encrypt_and_compress_zip_file(attachment, password, [filename]) attachments = [attachment] - ChangeSecretExecutionTaskMsg(name, user).publish(attachments) + ChangeSecretExecutionTaskMsg(name, user, summary).publish(attachments) os.remove(filename) @staticmethod @@ -228,8 +246,8 @@ class ChangeSecretManager(AccountBasePlaybookManager): rows.insert(0, header) wb = Workbook(filename) ws = wb.add_worksheet('Sheet1') - for row in rows: - for col, data in enumerate(row): - ws.write_string(0, col, data) + for row_index, row_data in enumerate(rows): + for col_index, col_data in enumerate(row_data): + ws.write_string(row_index, col_index, col_data) wb.close() return True diff --git a/apps/accounts/automations/gather_accounts/host/windows/main.yml b/apps/accounts/automations/gather_accounts/host/windows/main.yml index d117e6a2e..6f36feb83 100644 --- a/apps/accounts/automations/gather_accounts/host/windows/main.yml +++ b/apps/accounts/automations/gather_accounts/host/windows/main.yml @@ -1,9 +1,10 @@ - hosts: demo gather_facts: no tasks: - - name: Gather posix account + - name: Gather windows account ansible.builtin.win_shell: net user register: result + ignore_errors: true - name: Define info by set_fact ansible.builtin.set_fact: diff --git a/apps/accounts/automations/push_account/database/oracle/main.yml b/apps/accounts/automations/push_account/database/oracle/main.yml index 03eb5d45f..5a94f3184 100644 --- a/apps/accounts/automations/push_account/database/oracle/main.yml +++ b/apps/accounts/automations/push_account/database/oracle/main.yml @@ -39,3 +39,4 @@ login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" login_database: "{{ jms_asset.spec_info.db_name }}" + mode: "{{ account.mode }}" diff --git a/apps/accounts/notifications.py b/apps/accounts/notifications.py index 404125fd3..0082f8b80 100644 --- a/apps/accounts/notifications.py +++ b/apps/accounts/notifications.py @@ -54,20 +54,23 @@ class AccountBackupByObjStorageExecutionTaskMsg(object): class ChangeSecretExecutionTaskMsg(object): subject = _('Notification of implementation result of encryption change plan') - def __init__(self, name: str, user: User): + def __init__(self, name: str, user: User, summary): self.name = name self.user = user + self.summary = summary @property def message(self): name = self.name if self.user.secret_key: - return _('{} - The encryption change task has been completed. ' - 'See the attachment for details').format(name) + default_message = _('{} - The encryption change task has been completed. ' + 'See the attachment for details').format(name) + else: - return _("{} - The encryption change task has been completed: the encryption " - "password has not been set - please go to personal information -> " - "file encryption password to set the encryption password").format(name) + default_message = _("{} - The encryption change task has been completed: the encryption " + "password has not been set - please go to personal information -> " + "set encryption password in preferences").format(name) + return self.summary + '\n' + default_message def publish(self, attachments=None): send_mail_attachment_async( diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 36a04f588..62a18c03e 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -60,7 +60,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): for data in initial_data: if not data.get('asset') and not self.instance: raise serializers.ValidationError({'asset': UniqueTogetherValidator.missing_message}) - asset = data.get('asset') or self.instance.asset + asset = data.get('asset') or getattr(self.instance, 'asset', None) self.from_template_if_need(data) self.set_uniq_name_if_need(data, asset) @@ -457,12 +457,14 @@ class AccountHistorySerializer(serializers.ModelSerializer): class AccountTaskSerializer(serializers.Serializer): ACTION_CHOICES = ( - ('test', 'test'), ('verify', 'verify'), ('push', 'push'), ('remove', 'remove'), ) action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True) + assets = serializers.PrimaryKeyRelatedField( + queryset=Asset.objects, required=False, allow_empty=True, many=True + ) accounts = serializers.PrimaryKeyRelatedField( queryset=Account.objects, required=False, allow_empty=True, many=True ) diff --git a/apps/accounts/signal_handlers.py b/apps/accounts/signal_handlers.py index d086a049c..3ae1fba5c 100644 --- a/apps/accounts/signal_handlers.py +++ b/apps/accounts/signal_handlers.py @@ -21,7 +21,8 @@ def on_account_pre_save(sender, instance, **kwargs): if instance.version == 0: instance.version = 1 else: - instance.version = instance.history.count() + history_account = instance.history.first() + instance.version = history_account.version + 1 if history_account else 0 @merge_delay_run(ttl=5) @@ -62,7 +63,7 @@ def create_accounts_activities(account, action='create'): def on_account_create_by_template(sender, instance, created=False, **kwargs): if not created or instance.source != 'template': return - push_accounts_if_need(accounts=(instance,)) + push_accounts_if_need.delay(accounts=(instance,)) create_accounts_activities(instance, action='create') diff --git a/apps/accounts/tasks/remove_account.py b/apps/accounts/tasks/remove_account.py index 44bb9a840..6b637ed96 100644 --- a/apps/accounts/tasks/remove_account.py +++ b/apps/accounts/tasks/remove_account.py @@ -55,7 +55,7 @@ def clean_historical_accounts(): history_model = Account.history.model history_id_mapper = defaultdict(list) - ids = history_model.objects.values('id').annotate(count=Count('id')) \ + ids = history_model.objects.values('id').annotate(count=Count('id', distinct=True)) \ .filter(count__gte=limit).values_list('id', flat=True) if not ids: diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index b6e532b36..0f66d4b9a 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -92,6 +92,7 @@ class AssetViewSet(SuggestionMixin, OrgBulkModelViewSet): model = Asset filterset_class = AssetFilterSet search_fields = ("name", "address", "comment") + ordering = ('name',) ordering_fields = ('name', 'address', 'connectivity', 'platform', 'date_updated', 'date_created') serializer_classes = ( ("default", serializers.AssetSerializer), diff --git a/apps/assets/migrations/0109_alter_asset_options.py b/apps/assets/migrations/0109_alter_asset_options.py index 4a1c93a15..9140eff74 100644 --- a/apps/assets/migrations/0109_alter_asset_options.py +++ b/apps/assets/migrations/0109_alter_asset_options.py @@ -12,6 +12,6 @@ class Migration(migrations.Migration): operations = [ migrations.AlterModelOptions( name='asset', - options={'ordering': ['name'], 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'), ('test_assetconnectivity', 'Can test asset connectivity'), ('match_asset', 'Can match asset'), ('change_assetnodes', 'Can change asset nodes')], 'verbose_name': 'Asset'}, + options={'ordering': [], 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'), ('test_assetconnectivity', 'Can test asset connectivity'), ('match_asset', 'Can match asset'), ('change_assetnodes', 'Can change asset nodes')], 'verbose_name': 'Asset'}, ), ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 558164df6..7d3fb5fd2 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -348,7 +348,7 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin, class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset") - ordering = ["name", ] + ordering = [] permissions = [ ('refresh_assethardwareinfo', _('Can refresh asset hardware info')), ('test_assetconnectivity', _('Can test asset connectivity')), diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 7a15b9349..5d9ec5c1c 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -429,7 +429,7 @@ class NodeAssetsMixin(NodeAllAssetsMappingMixin): @classmethod @timeit - def get_nodes_all_assets(cls, *nodes): + def get_nodes_all_assets(cls, *nodes, distinct=True): from .asset import Asset node_ids = set() descendant_node_query = Q() @@ -439,7 +439,10 @@ class NodeAssetsMixin(NodeAllAssetsMappingMixin): if descendant_node_query: _ids = Node.objects.order_by().filter(descendant_node_query).values_list('id', flat=True) node_ids.update(_ids) - return Asset.objects.order_by().filter(nodes__id__in=node_ids).distinct() + assets = Asset.objects.order_by().filter(nodes__id__in=node_ids) + if distinct: + assets = assets.distinct() + return assets def get_all_asset_ids(self): asset_ids = self.get_all_asset_ids_by_node_key(org_id=self.org_id, node_key=self.key) diff --git a/apps/assets/pagination.py b/apps/assets/pagination.py index 8ae42ef16..316029301 100644 --- a/apps/assets/pagination.py +++ b/apps/assets/pagination.py @@ -1,8 +1,8 @@ from rest_framework.pagination import LimitOffsetPagination from rest_framework.request import Request -from common.utils import get_logger from assets.models import Node +from common.utils import get_logger logger = get_logger(__name__) @@ -28,6 +28,7 @@ class AssetPaginationBase(LimitOffsetPagination): 'key', 'all', 'show_current_asset', 'cache_policy', 'display', 'draw', 'order', 'node', 'node_id', 'fields_size', + 'asset' } for k, v in self._request.query_params.items(): if k not in exclude_query_params and v is not None: diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index a2523502d..0e90ffadf 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -206,9 +206,12 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa """ Perform necessary eager loading of data. """ queryset = queryset.prefetch_related('domain', 'nodes', 'protocols', ) \ .prefetch_related('platform', 'platform__automation') \ - .prefetch_related('labels', 'labels__label') \ .annotate(category=F("platform__category")) \ .annotate(type=F("platform__type")) + if queryset.model is Asset: + queryset = queryset.prefetch_related('labels__label', 'labels') + else: + queryset = queryset.prefetch_related('asset_ptr__labels__label', 'asset_ptr__labels') return queryset @staticmethod diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 3bf4b65f5..3fa0c6c02 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -56,7 +56,14 @@ class DomainSerializer(ResourceLabelsMixin, BulkOrgResourceModelSerializer): class DomainListSerializer(DomainSerializer): class Meta(DomainSerializer.Meta): - fields = list(set(DomainSerializer.Meta.fields) - {'assets'}) + fields = list(set(DomainSerializer.Meta.fields + ['assets_amount']) - {'assets'}) + + @classmethod + def setup_eager_loading(cls, queryset): + queryset = queryset.annotate( + assets_amount=Count('assets', distinct=True), + ) + return queryset class DomainWithGatewaySerializer(serializers.ModelSerializer): diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 2cc7d73ef..4fb0418c6 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -191,7 +191,6 @@ class PlatformSerializer(ResourceLabelsMixin, WritableNestedModelSerializer): def add_type_choices(self, name, label): tp = self.fields['type'] tp.choices[name] = label - tp.choice_mapper[name] = label tp.choice_strings_to_values[name] = label @lazyproperty diff --git a/apps/assets/signal_handlers/asset.py b/apps/assets/signal_handlers/asset.py index 5ab4a4117..7ced509d6 100644 --- a/apps/assets/signal_handlers/asset.py +++ b/apps/assets/signal_handlers/asset.py @@ -63,13 +63,13 @@ def on_asset_create(sender, instance=None, created=False, **kwargs): return logger.info("Asset create signal recv: {}".format(instance)) - ensure_asset_has_node(assets=(instance,)) + ensure_asset_has_node.delay(assets=(instance,)) # 获取资产硬件信息 auto_config = instance.auto_config if auto_config.get('ping_enabled'): logger.debug('Asset {} ping enabled, test connectivity'.format(instance.name)) - test_assets_connectivity_handler(assets=(instance,)) + test_assets_connectivity_handler.delay(assets=(instance,)) if auto_config.get('gather_facts_enabled'): logger.debug('Asset {} gather facts enabled, gather facts'.format(instance.name)) gather_assets_facts_handler(assets=(instance,)) diff --git a/apps/assets/signal_handlers/node_assets_amount.py b/apps/assets/signal_handlers/node_assets_amount.py index ea2b3ba8a..5c4633dbd 100644 --- a/apps/assets/signal_handlers/node_assets_amount.py +++ b/apps/assets/signal_handlers/node_assets_amount.py @@ -2,14 +2,16 @@ # from operator import add, sub +from django.conf import settings from django.db.models.signals import m2m_changed from django.dispatch import receiver from assets.models import Asset, Node from common.const.signals import PRE_CLEAR, POST_ADD, PRE_REMOVE from common.decorators import on_transaction_commit, merge_delay_run +from common.signals import django_ready from common.utils import get_logger -from orgs.utils import tmp_to_org +from orgs.utils import tmp_to_org, tmp_to_root_org from ..tasks import check_node_assets_amount_task logger = get_logger(__file__) @@ -34,7 +36,7 @@ def on_node_asset_change(sender, action, instance, reverse, pk_set, **kwargs): node_ids = [instance.id] else: node_ids = list(pk_set) - update_nodes_assets_amount(node_ids=node_ids) + update_nodes_assets_amount.delay(node_ids=node_ids) @merge_delay_run(ttl=30) @@ -52,3 +54,18 @@ def update_nodes_assets_amount(node_ids=()): node.assets_amount = node.get_assets_amount() Node.objects.bulk_update(nodes, ['assets_amount']) + + +@receiver(django_ready) +def set_assets_size_to_setting(sender, **kwargs): + from assets.models import Asset + try: + with tmp_to_root_org(): + amount = Asset.objects.order_by().count() + except: + amount = 0 + + if amount > 20000: + settings.ASSET_SIZE = 'large' + elif amount > 2000: + settings.ASSET_SIZE = 'medium' diff --git a/apps/assets/signal_handlers/node_assets_mapping.py b/apps/assets/signal_handlers/node_assets_mapping.py index a53e534b6..1177f02d6 100644 --- a/apps/assets/signal_handlers/node_assets_mapping.py +++ b/apps/assets/signal_handlers/node_assets_mapping.py @@ -44,18 +44,18 @@ def on_node_post_create(sender, instance, created, update_fields, **kwargs): need_expire = False if need_expire: - expire_node_assets_mapping(org_ids=(instance.org_id,)) + expire_node_assets_mapping.delay(org_ids=(instance.org_id,)) @receiver(post_delete, sender=Node) def on_node_post_delete(sender, instance, **kwargs): - expire_node_assets_mapping(org_ids=(instance.org_id,)) + expire_node_assets_mapping.delay(org_ids=(instance.org_id,)) @receiver(m2m_changed, sender=Asset.nodes.through) def on_node_asset_change(sender, instance, action='pre_remove', **kwargs): if action.startswith('post'): - expire_node_assets_mapping(org_ids=(instance.org_id,)) + expire_node_assets_mapping.delay(org_ids=(instance.org_id,)) @receiver(django_ready) diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 408550ece..b56458758 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -2,6 +2,7 @@ from django.urls import path from rest_framework_bulk.routes import BulkRouter +from labels.api import LabelViewSet from .. import api app_name = 'assets' @@ -22,6 +23,7 @@ router.register(r'domains', api.DomainViewSet, 'domain') router.register(r'gateways', api.GatewayViewSet, 'gateway') router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') router.register(r'protocol-settings', api.PlatformProtocolViewSet, 'protocol-setting') +router.register(r'labels', LabelViewSet, 'label') urlpatterns = [ # path('assets//gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'), diff --git a/apps/assets/utils/k8s.py b/apps/assets/utils/k8s.py index 2b33ded89..dc3cd8bc6 100644 --- a/apps/assets/utils/k8s.py +++ b/apps/assets/utils/k8s.py @@ -4,7 +4,6 @@ from urllib.parse import urlencode, urlparse from kubernetes import client from kubernetes.client import api_client from kubernetes.client.api import core_v1_api -from kubernetes.client.exceptions import ApiException from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError from common.utils import get_logger @@ -88,8 +87,9 @@ class KubernetesClient: if hasattr(self, func_name): try: data = getattr(self, func_name)(*args) - except ApiException as e: - logger.error(e.reason) + except Exception as e: + logger.error(e) + raise e if self.server: self.server.stop() diff --git a/apps/audits/api.py b/apps/audits/api.py index 209bc30fd..5a665777b 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -20,6 +20,7 @@ from common.const.http import GET, POST from common.drf.filters import DatetimeRangeFilterBackend from common.permissions import IsServiceAccount from common.plugins.es import QuerySet as ESQuerySet +from common.sessions.cache import user_session_manager from common.storage.ftp_file import FTPFileStorageHandler from common.utils import is_uuid, get_logger, lazyproperty from orgs.mixins.api import OrgReadonlyModelViewSet, OrgModelViewSet @@ -289,8 +290,7 @@ class UserSessionViewSet(CommonApiMixin, viewsets.ModelViewSet): return Response(status=status.HTTP_200_OK) keys = queryset.values_list('key', flat=True) - session_store_cls = import_module(settings.SESSION_ENGINE).SessionStore for key in keys: - session_store_cls(key).delete() + user_session_manager.decrement_or_remove(key) queryset.delete() return Response(status=status.HTTP_200_OK) diff --git a/apps/audits/filters.py b/apps/audits/filters.py index d24cf72fd..078e68c2c 100644 --- a/apps/audits/filters.py +++ b/apps/audits/filters.py @@ -1,10 +1,9 @@ -from django.core.cache import cache from django_filters import rest_framework as drf_filters from rest_framework import filters from rest_framework.compat import coreapi, coreschema from common.drf.filters import BaseFilterSet -from notifications.ws import WS_SESSION_KEY +from common.sessions.cache import user_session_manager from orgs.utils import current_org from .models import UserSession @@ -41,13 +40,11 @@ class UserSessionFilterSet(BaseFilterSet): @staticmethod def filter_is_active(queryset, name, is_active): - redis_client = cache.client.get_client() - members = redis_client.smembers(WS_SESSION_KEY) - members = [member.decode('utf-8') for member in members] + keys = user_session_manager.get_active_keys() if is_active: - queryset = queryset.filter(key__in=members) + queryset = queryset.filter(key__in=keys) else: - queryset = queryset.exclude(key__in=members) + queryset = queryset.exclude(key__in=keys) return queryset class Meta: diff --git a/apps/audits/models.py b/apps/audits/models.py index 64a0ebc5b..c4b4486a5 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -4,15 +4,15 @@ from datetime import timedelta from importlib import import_module from django.conf import settings -from django.core.cache import caches, cache +from django.core.cache import caches from django.db import models from django.db.models import Q from django.utils import timezone from django.utils.translation import gettext, gettext_lazy as _ from common.db.encoder import ModelJSONFieldEncoder +from common.sessions.cache import user_session_manager from common.utils import lazyproperty, i18n_trans -from notifications.ws import WS_SESSION_KEY from ops.models import JobExecution from orgs.mixins.models import OrgModelMixin, Organization from orgs.utils import current_org @@ -278,8 +278,7 @@ class UserSession(models.Model): @property def is_active(self): - redis_client = cache.client.get_client() - return redis_client.sismember(WS_SESSION_KEY, self.key) + return user_session_manager.check_active(self.key) @property def date_expired(self): diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 076767946..b5bafac32 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -205,7 +205,7 @@ class RDPFileClientProtocolURLMixin: return data def get_smart_endpoint(self, protocol, asset=None): - endpoint = Endpoint.match_by_instance_label(asset, protocol) + endpoint = Endpoint.match_by_instance_label(asset, protocol, self.request) if not endpoint: target_ip = asset.get_target_ip() if asset else '' endpoint = EndpointRule.match_endpoint( diff --git a/apps/authentication/api/mfa.py b/apps/authentication/api/mfa.py index 049cd177a..2d3a9b072 100644 --- a/apps/authentication/api/mfa.py +++ b/apps/authentication/api/mfa.py @@ -90,6 +90,6 @@ class MFAChallengeVerifyApi(AuthMixin, CreateAPIView): return Response({'msg': 'ok'}) except errors.AuthFailedError as e: data = {"error": e.error, "msg": e.msg} - raise ValidationError(data) + return Response(data, status=401) except errors.NeedMoreInfoError as e: return Response(e.as_data(), status=200) diff --git a/apps/authentication/backends/drf.py b/apps/authentication/backends/drf.py index 86999037d..4cf2577c1 100644 --- a/apps/authentication/backends/drf.py +++ b/apps/authentication/backends/drf.py @@ -10,6 +10,7 @@ from rest_framework import authentication, exceptions from common.auth import signature from common.decorators import merge_delay_run from common.utils import get_object_or_none, get_request_ip_or_data, contains_ip +from users.models import User from ..models import AccessKey, PrivateToken @@ -19,22 +20,23 @@ def date_more_than(d, seconds): @merge_delay_run(ttl=60) def update_token_last_used(tokens=()): - for token in tokens: - token.date_last_used = timezone.now() - token.save(update_fields=['date_last_used']) + access_keys_ids = [token.id for token in tokens if isinstance(token, AccessKey)] + private_token_keys = [token.key for token in tokens if isinstance(token, PrivateToken)] + if len(access_keys_ids) > 0: + AccessKey.objects.filter(id__in=access_keys_ids).update(date_last_used=timezone.now()) + if len(private_token_keys) > 0: + PrivateToken.objects.filter(key__in=private_token_keys).update(date_last_used=timezone.now()) @merge_delay_run(ttl=60) def update_user_last_used(users=()): - for user in users: - user.date_api_key_last_used = timezone.now() - user.save(update_fields=['date_api_key_last_used']) + User.objects.filter(id__in=users).update(date_api_key_last_used=timezone.now()) def after_authenticate_update_date(user, token=None): - update_user_last_used(users=(user,)) + update_user_last_used.delay(users=(user.id,)) if token: - update_token_last_used(tokens=(token,)) + update_token_last_used.delay(tokens=(token,)) class AccessTokenAuthentication(authentication.BaseAuthentication): diff --git a/apps/authentication/backends/oauth2/backends.py b/apps/authentication/backends/oauth2/backends.py index 0c40d09bd..16a617b16 100644 --- a/apps/authentication/backends/oauth2/backends.py +++ b/apps/authentication/backends/oauth2/backends.py @@ -98,16 +98,19 @@ class OAuth2Backend(JMSModelBackend): access_token_url = '{url}{separator}{query}'.format( url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT, separator=separator, query=urlencode(query_dict) ) + # token_method -> get, post(post_data), post_json token_method = settings.AUTH_OAUTH2_ACCESS_TOKEN_METHOD.lower() - requests_func = getattr(requests, token_method, requests.get) logger.debug(log_prompt.format('Call the access token endpoint[method: %s]' % token_method)) headers = { 'Accept': 'application/json' } - if token_method == 'post': - access_token_response = requests_func(access_token_url, headers=headers, data=query_dict) + if token_method.startswith('post'): + body_key = 'json' if token_method.endswith('json') else 'data' + access_token_response = requests.post( + access_token_url, headers=headers, **{body_key: query_dict} + ) else: - access_token_response = requests_func(access_token_url, headers=headers) + access_token_response = requests.get(access_token_url, headers=headers) try: access_token_response.raise_for_status() access_token_response_data = access_token_response.json() diff --git a/apps/authentication/forms.py b/apps/authentication/forms.py index 90429bc82..5f56a4a98 100644 --- a/apps/authentication/forms.py +++ b/apps/authentication/forms.py @@ -18,7 +18,7 @@ class EncryptedField(forms.CharField): class UserLoginForm(forms.Form): days_auto_login = int(settings.SESSION_COOKIE_AGE / 3600 / 24) - disable_days_auto_login = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE \ + disable_days_auto_login = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE \ or days_auto_login < 1 username = forms.CharField( diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index eabb90263..e6182e9bb 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -142,23 +142,7 @@ class SessionCookieMiddleware(MiddlewareMixin): return response response.set_cookie(key, value) - @staticmethod - def set_cookie_session_expire(request, response): - if not request.session.get('auth_session_expiration_required'): - return - value = 'age' - if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE or \ - not request.session.get('auto_login', False): - value = 'close' - - age = request.session.get_expiry_age() - expire_timestamp = request.session.get_expiry_date().timestamp() - response.set_cookie('jms_session_expire_timestamp', expire_timestamp) - response.set_cookie('jms_session_expire', value, max_age=age) - request.session.pop('auth_session_expiration_required', None) - def process_response(self, request, response: HttpResponse): self.set_cookie_session_prefix(request, response) self.set_cookie_public_key(request, response) - self.set_cookie_session_expire(request, response) return response diff --git a/apps/authentication/signal_handlers.py b/apps/authentication/signal_handlers.py index 3ac92411f..943d751dd 100644 --- a/apps/authentication/signal_handlers.py +++ b/apps/authentication/signal_handlers.py @@ -37,9 +37,6 @@ def on_user_auth_login_success(sender, user, request, **kwargs): UserSession.objects.filter(key=session_key).delete() cache.set(lock_key, request.session.session_key, None) - # 标记登录,设置 cookie,前端可以控制刷新, Middleware 会拦截这个生成 cookie - request.session['auth_session_expiration_required'] = 1 - @receiver(cas_user_authenticated) def on_cas_user_login_success(sender, request, user, **kwargs): diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index 9b7f81e25..8e76795cb 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -70,11 +70,12 @@ class DingTalkQRMixin(DingTalkBaseMixin, View): self.request.session[DINGTALK_STATE_SESSION_KEY] = state params = { - 'appid': settings.DINGTALK_APPKEY, + 'client_id': settings.DINGTALK_APPKEY, 'response_type': 'code', - 'scope': 'snsapi_login', + 'scope': 'openid', 'state': state, 'redirect_uri': redirect_uri, + 'prompt': 'consent' } url = URL.QR_CONNECT + '?' + urlencode(params) return url diff --git a/apps/common/api/mixin.py b/apps/common/api/mixin.py index 50bb1efbe..e8266d928 100644 --- a/apps/common/api/mixin.py +++ b/apps/common/api/mixin.py @@ -104,9 +104,11 @@ class QuerySetMixin: page = super().paginate_queryset(queryset) serializer_class = self.get_serializer_class() if page and serializer_class and hasattr(serializer_class, 'setup_eager_loading'): - ids = [i.id for i in page] + ids = [str(obj.id) for obj in page] page = self.get_queryset().filter(id__in=ids) page = serializer_class.setup_eager_loading(page) + page_mapper = {str(obj.id): obj for obj in page} + page = [page_mapper.get(_id) for _id in ids if _id in page_mapper] return page diff --git a/apps/common/const/choices.py b/apps/common/const/choices.py index 6db9254b3..ee8519c34 100644 --- a/apps/common/const/choices.py +++ b/apps/common/const/choices.py @@ -19,3 +19,17 @@ class Status(models.TextChoices): failed = 'failed', _("Failed") error = 'error', _("Error") canceled = 'canceled', _("Canceled") + + +COUNTRY_CALLING_CODES = [ + {'name': 'China(中国)', 'value': '+86'}, + {'name': 'HongKong(中国香港)', 'value': '+852'}, + {'name': 'Macao(中国澳门)', 'value': '+853'}, + {'name': 'Taiwan(中国台湾)', 'value': '+886'}, + {'name': 'America(America)', 'value': '+1'}, {'name': 'Russia(Россия)', 'value': '+7'}, + {'name': 'France(français)', 'value': '+33'}, + {'name': 'Britain(Britain)', 'value': '+44'}, + {'name': 'Germany(Deutschland)', 'value': '+49'}, + {'name': 'Japan(日本)', 'value': '+81'}, {'name': 'Korea(한국)', 'value': '+82'}, + {'name': 'India(भारत)', 'value': '+91'} +] diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index 1691fad1d..0a620e1f0 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -362,11 +362,15 @@ class RelatedManager: if name is None or val is None: continue - if custom_attr_filter: + custom_filter_q = None + spec_attr_filter = getattr(to_model, "get_{}_filter_attr_q".format(name), None) + if spec_attr_filter: + custom_filter_q = spec_attr_filter(val, match) + elif custom_attr_filter: custom_filter_q = custom_attr_filter(name, val, match) - if custom_filter_q: - filters.append(custom_filter_q) - continue + if custom_filter_q: + filters.append(custom_filter_q) + continue if match == 'ip_in': q = cls.get_ip_in_q(name, val) @@ -464,11 +468,15 @@ class JSONManyToManyDescriptor: rule_value = rule.get('value', '') rule_match = rule.get('match', 'exact') - if custom_attr_filter: - q = custom_attr_filter(rule['name'], rule_value, rule_match) - if q: - custom_q &= q - continue + custom_filter_q = None + spec_attr_filter = getattr(to_model, "get_filter_{}_attr_q".format(rule['name']), None) + if spec_attr_filter: + custom_filter_q = spec_attr_filter(rule_value, rule_match) + elif custom_attr_filter: + custom_filter_q = custom_attr_filter(rule['name'], rule_value, rule_match) + if custom_filter_q: + custom_q &= custom_filter_q + continue if rule_match == 'in': res &= value in rule_value or '*' in rule_value @@ -517,7 +525,6 @@ class JSONManyToManyDescriptor: res &= rule_value.issubset(value) else: res &= bool(value & rule_value) - else: logging.error("unknown match: {}".format(rule['match'])) res &= False diff --git a/apps/common/decorators.py b/apps/common/decorators.py index 7269c9e0d..171a4ff33 100644 --- a/apps/common/decorators.py +++ b/apps/common/decorators.py @@ -3,6 +3,7 @@ import asyncio import functools import inspect +import os import threading import time from concurrent.futures import ThreadPoolExecutor @@ -101,7 +102,11 @@ def run_debouncer_func(cache_key, org, ttl, func, *args, **kwargs): first_run_time = current if current - first_run_time > ttl: + _loop_debouncer_func_args_cache.pop(cache_key, None) + _loop_debouncer_func_task_time_cache.pop(cache_key, None) executor.submit(run_func_partial, *args, **kwargs) + logger.debug('pid {} executor submit run {}'.format( + os.getpid(), func.__name__, )) return loop = _loop_thread.get_loop() @@ -133,13 +138,26 @@ class Debouncer(object): return await self.loop.run_in_executor(self.executor, func) +ignore_err_exceptions = ( + "(3101, 'Plugin instructed the server to rollback the current transaction.')", +) + + def _run_func_with_org(key, org, func, *args, **kwargs): from orgs.utils import set_current_org try: - set_current_org(org) - func(*args, **kwargs) + with transaction.atomic(): + set_current_org(org) + func(*args, **kwargs) except Exception as e: - logger.error('delay run error: %s' % e) + msg = str(e) + log_func = logger.error + if msg in ignore_err_exceptions: + log_func = logger.info + pid = os.getpid() + thread_name = threading.current_thread() + log_func('pid {} thread {} delay run {} error: {}'.format( + pid, thread_name, func.__name__, msg)) _loop_debouncer_func_task_cache.pop(key, None) _loop_debouncer_func_args_cache.pop(key, None) _loop_debouncer_func_task_time_cache.pop(key, None) @@ -181,6 +199,32 @@ def merge_delay_run(ttl=5, key=None): :return: """ + def delay(func, *args, **kwargs): + from orgs.utils import get_current_org + suffix_key_func = key if key else default_suffix_key + org = get_current_org() + func_name = f'{func.__module__}_{func.__name__}' + key_suffix = suffix_key_func(*args, **kwargs) + cache_key = f'MERGE_DELAY_RUN_{func_name}_{key_suffix}' + cache_kwargs = _loop_debouncer_func_args_cache.get(cache_key, {}) + + for k, v in kwargs.items(): + if not isinstance(v, (tuple, list, set)): + raise ValueError('func kwargs value must be list or tuple: %s %s' % (func.__name__, v)) + v = set(v) + if k not in cache_kwargs: + cache_kwargs[k] = v + else: + cache_kwargs[k] = cache_kwargs[k].union(v) + _loop_debouncer_func_args_cache[cache_key] = cache_kwargs + run_debouncer_func(cache_key, org, ttl, func, *args, **cache_kwargs) + + def apply(func, sync=False, *args, **kwargs): + if sync: + return func(*args, **kwargs) + else: + return delay(func, *args, **kwargs) + def inner(func): sigs = inspect.signature(func) if len(sigs.parameters) != 1: @@ -188,27 +232,12 @@ def merge_delay_run(ttl=5, key=None): param = list(sigs.parameters.values())[0] if not isinstance(param.default, tuple): raise ValueError('func default must be tuple: %s' % param.default) - suffix_key_func = key if key else default_suffix_key + func.delay = functools.partial(delay, func) + func.apply = functools.partial(apply, func) @functools.wraps(func) def wrapper(*args, **kwargs): - from orgs.utils import get_current_org - org = get_current_org() - func_name = f'{func.__module__}_{func.__name__}' - key_suffix = suffix_key_func(*args, **kwargs) - cache_key = f'MERGE_DELAY_RUN_{func_name}_{key_suffix}' - cache_kwargs = _loop_debouncer_func_args_cache.get(cache_key, {}) - - for k, v in kwargs.items(): - if not isinstance(v, (tuple, list, set)): - raise ValueError('func kwargs value must be list or tuple: %s %s' % (func.__name__, v)) - v = set(v) - if k not in cache_kwargs: - cache_kwargs[k] = v - else: - cache_kwargs[k] = cache_kwargs[k].union(v) - _loop_debouncer_func_args_cache[cache_key] = cache_kwargs - run_debouncer_func(cache_key, org, ttl, func, *args, **cache_kwargs) + return func(*args, **kwargs) return wrapper diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py index d187cd414..803849909 100644 --- a/apps/common/drf/filters.py +++ b/apps/common/drf/filters.py @@ -6,7 +6,7 @@ import logging from django.core.cache import cache from django.core.exceptions import ImproperlyConfigured -from django.db.models import Q, Count +from django.db.models import Q from django_filters import rest_framework as drf_filters from rest_framework import filters from rest_framework.compat import coreapi, coreschema @@ -180,36 +180,30 @@ class LabelFilterBackend(filters.BaseFilterBackend): ] @staticmethod - def filter_resources(resources, labels_id): + def parse_label_ids(labels_id): + from labels.models import Label label_ids = [i.strip() for i in labels_id.split(',')] + cleaned = [] args = [] for label_id in label_ids: kwargs = {} if ':' in label_id: k, v = label_id.split(':', 1) - kwargs['label__name'] = k.strip() + kwargs['name'] = k.strip() if v != '*': - kwargs['label__value'] = v.strip() + kwargs['value'] = v.strip() + args.append(kwargs) else: - kwargs['label_id'] = label_id - args.append(kwargs) + cleaned.append(label_id) - if len(args) == 1: - resources = resources.filter(**args[0]) - return resources - - q = Q() - for kwarg in args: - q |= Q(**kwarg) - - resources = resources.filter(q) \ - .values('res_id') \ - .order_by('res_id') \ - .annotate(count=Count('res_id')) \ - .values('res_id', 'count') \ - .filter(count=len(args)) - return resources + if len(args) != 0: + q = Q() + for kwarg in args: + q |= Q(**kwarg) + ids = Label.objects.filter(q).values_list('id', flat=True) + cleaned.extend(list(ids)) + return cleaned def filter_queryset(self, request, queryset, view): labels_id = request.query_params.get('labels') @@ -223,14 +217,15 @@ class LabelFilterBackend(filters.BaseFilterBackend): return queryset model = queryset.model.label_model() - labeled_resource_cls = model._labels.field.related_model + labeled_resource_cls = model.labels.field.related_model app_label = model._meta.app_label model_name = model._meta.model_name resources = labeled_resource_cls.objects.filter( res_type__app_label=app_label, res_type__model=model_name, ) - resources = self.filter_resources(resources, labels_id) + label_ids = self.parse_label_ids(labels_id) + resources = model.filter_resources_by_labels(resources, label_ids) res_ids = resources.values_list('res_id', flat=True) queryset = queryset.filter(id__in=set(res_ids)) return queryset diff --git a/apps/common/management/commands/services/services/celery_base.py b/apps/common/management/commands/services/services/celery_base.py index 8d197d4f8..4e63f4cbb 100644 --- a/apps/common/management/commands/services/services/celery_base.py +++ b/apps/common/management/commands/services/services/celery_base.py @@ -14,6 +14,7 @@ class CeleryBaseService(BaseService): print('\n- Start Celery as Distributed Task Queue: {}'.format(self.queue.capitalize())) ansible_config_path = os.path.join(settings.APPS_DIR, 'ops', 'ansible', 'ansible.cfg') ansible_modules_path = os.path.join(settings.APPS_DIR, 'ops', 'ansible', 'modules') + os.environ.setdefault('LC_ALL', 'C.UTF-8') os.environ.setdefault('PYTHONOPTIMIZE', '1') os.environ.setdefault('ANSIBLE_FORCE_COLOR', 'True') os.environ.setdefault('ANSIBLE_CONFIG', ansible_config_path) diff --git a/apps/common/sdk/im/dingtalk/__init__.py b/apps/common/sdk/im/dingtalk/__init__.py index 65d466008..37a3c2dad 100644 --- a/apps/common/sdk/im/dingtalk/__init__.py +++ b/apps/common/sdk/im/dingtalk/__init__.py @@ -28,9 +28,10 @@ class ErrorCode: class URL: - QR_CONNECT = 'https://oapi.dingtalk.com/connect/qrconnect' + QR_CONNECT = 'https://login.dingtalk.com/oauth2/auth' OAUTH_CONNECT = 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize' - GET_USER_INFO_BY_CODE = 'https://oapi.dingtalk.com/sns/getuserinfo_bycode' + GET_USER_ACCESSTOKEN = 'https://api.dingtalk.com/v1.0/oauth2/userAccessToken' + GET_USER_INFO = 'https://api.dingtalk.com/v1.0/contact/users/me' GET_TOKEN = 'https://oapi.dingtalk.com/gettoken' SEND_MESSAGE_BY_TEMPLATE = 'https://oapi.dingtalk.com/topapi/message/corpconversation/sendbytemplate' SEND_MESSAGE = 'https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2' @@ -72,8 +73,9 @@ class DingTalkRequests(BaseRequest): def get(self, url, params=None, with_token=False, with_sign=False, check_errcode_is_0=True, - **kwargs): + **kwargs) -> dict: pass + get = as_request(get) def post(self, url, json=None, params=None, @@ -81,6 +83,7 @@ class DingTalkRequests(BaseRequest): check_errcode_is_0=True, **kwargs) -> dict: pass + post = as_request(post) def _add_sign(self, kwargs: dict): @@ -123,17 +126,22 @@ class DingTalk: ) def get_userinfo_bycode(self, code): - # https://developers.dingtalk.com/document/app/obtain-the-user-information-based-on-the-sns-temporary-authorization?spm=ding_open_doc.document.0.0.3a256573y8Y7yg#topic-1995619 body = { - "tmp_auth_code": code + 'clientId': self._appid, + 'clientSecret': self._appsecret, + 'code': code, + 'grantType': 'authorization_code' } + data = self._request.post(URL.GET_USER_ACCESSTOKEN, json=body, check_errcode_is_0=False) + token = data['accessToken'] - data = self._request.post(URL.GET_USER_INFO_BY_CODE, json=body, with_sign=True) - return data['user_info'] + user = self._request.get(URL.GET_USER_INFO, + headers={'x-acs-dingtalk-access-token': token}, check_errcode_is_0=False) + return user def get_user_id_by_code(self, code): user_info = self.get_userinfo_bycode(code) - unionid = user_info['unionid'] + unionid = user_info['unionId'] userid = self.get_userid_by_unionid(unionid) return userid, None diff --git a/apps/common/serializers/mixin.py b/apps/common/serializers/mixin.py index d51849f07..81fac91d9 100644 --- a/apps/common/serializers/mixin.py +++ b/apps/common/serializers/mixin.py @@ -394,20 +394,20 @@ class CommonBulkModelSerializer(CommonBulkSerializerMixin, serializers.ModelSeri class ResourceLabelsMixin(serializers.Serializer): - labels = LabelRelatedField(many=True, label=_('Labels'), required=False, allow_null=True) + labels = LabelRelatedField(many=True, label=_('Labels'), required=False, allow_null=True, source='res_labels') def update(self, instance, validated_data): - labels = validated_data.pop('labels', None) + labels = validated_data.pop('res_labels', None) res = super().update(instance, validated_data) if labels is not None: - instance.labels.set(labels, bulk=False) + instance.res_labels.set(labels, bulk=False) return res def create(self, validated_data): - labels = validated_data.pop('labels', None) + labels = validated_data.pop('res_labels', None) instance = super().create(validated_data) if labels is not None: - instance.labels.set(labels, bulk=False) + instance.res_labels.set(labels, bulk=False) return instance @classmethod diff --git a/apps/common/sessions/__init__.py b/apps/common/sessions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/common/sessions/cache.py b/apps/common/sessions/cache.py new file mode 100644 index 000000000..805d6738a --- /dev/null +++ b/apps/common/sessions/cache.py @@ -0,0 +1,56 @@ +import re + +from django.contrib.sessions.backends.cache import ( + SessionStore as DjangoSessionStore +) +from django.core.cache import cache + +from jumpserver.utils import get_current_request + + +class SessionStore(DjangoSessionStore): + ignore_urls = [ + r'^/api/v1/users/profile/' + ] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.ignore_pattern = re.compile('|'.join(self.ignore_urls)) + + def save(self, *args, **kwargs): + request = get_current_request() + if request is None or not self.ignore_pattern.match(request.path): + super().save(*args, **kwargs) + + +class RedisUserSessionManager: + JMS_SESSION_KEY = 'jms_session_key' + + def __init__(self): + self.client = cache.client.get_client() + + def add_or_increment(self, session_key): + self.client.hincrby(self.JMS_SESSION_KEY, session_key, 1) + + def decrement_or_remove(self, session_key): + new_count = self.client.hincrby(self.JMS_SESSION_KEY, session_key, -1) + if new_count <= 0: + self.client.hdel(self.JMS_SESSION_KEY, session_key) + + def check_active(self, session_key): + count = self.client.hget(self.JMS_SESSION_KEY, session_key) + count = 0 if count is None else int(count.decode('utf-8')) + return count > 0 + + def get_active_keys(self): + session_keys = [] + for k, v in self.client.hgetall(self.JMS_SESSION_KEY).items(): + count = int(v.decode('utf-8')) + if count <= 0: + continue + key = k.decode('utf-8') + session_keys.append(key) + return session_keys + + +user_session_manager = RedisUserSessionManager() diff --git a/apps/common/signal_handlers.py b/apps/common/signal_handlers.py index ad765657e..6c5ceae1a 100644 --- a/apps/common/signal_handlers.py +++ b/apps/common/signal_handlers.py @@ -69,7 +69,7 @@ def digest_sql_query(): for query in queries: sql = query['sql'] - print(" # {}: {}".format(query['time'], sql[:1000])) + print(" # {}: {}".format(query['time'], sql[:1000])) if len(queries) < 3: continue print("- Table: {}".format(table_name)) diff --git a/apps/i18n/core/en/LC_MESSAGES/django.po b/apps/i18n/core/en/LC_MESSAGES/django.po index f85a0fffd..6b67ef0f5 100644 --- a/apps/i18n/core/en/LC_MESSAGES/django.po +++ b/apps/i18n/core/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-02 16:51+0800\n" +"POT-Creation-Date: 2024-02-04 19:41+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -22,6 +22,11 @@ msgstr "" msgid "The parameter 'action' must be [{}]" msgstr "" +#: accounts/automations/change_secret/manager.py:197 +#, python-format +msgid "Success: %s, Failed: %s, Total: %s" +msgstr "" + #: accounts/const/account.py:6 #: accounts/serializers/automations/change_secret.py:32 #: assets/models/_user.py:24 audits/signal_handlers/login_log.py:34 @@ -203,8 +208,8 @@ msgstr "" #: notifications/backends/__init__.py:10 settings/serializers/msg.py:22 #: settings/serializers/msg.py:64 users/forms/profile.py:102 #: users/forms/profile.py:109 users/models/user.py:802 -#: users/templates/users/forgot_password.html:117 -#: users/views/profile/reset.py:93 +#: users/templates/users/forgot_password.html:160 +#: users/views/profile/reset.py:94 msgid "Email" msgstr "" @@ -360,10 +365,11 @@ msgstr "" #: accounts/models/automations/backup_account.py:119 #: assets/models/automations/base.py:115 audits/models.py:65 -#: ops/models/base.py:55 ops/models/celery.py:65 ops/models/job.py:235 +#: ops/models/base.py:55 ops/models/celery.py:86 ops/models/job.py:236 #: ops/templates/ops/celery_task_log.html:75 -#: perms/models/asset_permission.py:78 terminal/models/applet/host.py:141 -#: terminal/models/session/session.py:44 +#: perms/models/asset_permission.py:78 +#: settings/templates/ldap/_msg_import_ldap_user.html:5 +#: terminal/models/applet/host.py:141 terminal/models/session/session.py:44 #: tickets/models/ticket/apply_application.py:30 #: tickets/models/ticket/apply_asset.py:19 msgid "Date start" @@ -372,6 +378,7 @@ msgstr "" #: accounts/models/automations/backup_account.py:122 #: authentication/templates/authentication/_msg_oauth_bind.html:11 #: notifications/notifications.py:186 +#: settings/templates/ldap/_msg_import_ldap_user.html:3 msgid "Time" msgstr "" @@ -381,7 +388,7 @@ msgstr "" #: accounts/models/automations/backup_account.py:130 #: accounts/serializers/account/backup.py:48 -#: accounts/serializers/automations/base.py:54 +#: accounts/serializers/automations/base.py:55 #: assets/models/automations/base.py:122 #: assets/serializers/automations/base.py:39 msgid "Trigger mode" @@ -447,6 +454,7 @@ msgstr "" #: accounts/models/automations/gather_account.py:58 #: accounts/serializers/account/backup.py:40 #: accounts/serializers/automations/change_secret.py:56 +#: settings/serializers/auth/ldap.py:81 msgid "Recipient" msgstr "" @@ -468,14 +476,14 @@ msgstr "" #: accounts/models/automations/change_secret.py:42 #: assets/models/automations/base.py:116 ops/models/base.py:56 -#: ops/models/celery.py:66 ops/models/job.py:236 +#: ops/models/celery.py:87 ops/models/job.py:237 #: terminal/models/applet/host.py:142 msgid "Date finished" msgstr "" #: accounts/models/automations/change_secret.py:43 #: assets/models/automations/base.py:113 audits/models.py:208 -#: audits/serializers.py:54 ops/models/base.py:49 ops/models/job.py:227 +#: audits/serializers.py:54 ops/models/base.py:49 ops/models/job.py:228 #: terminal/models/applet/applet.py:320 terminal/models/applet/host.py:140 #: terminal/models/component/status.py:30 #: terminal/models/virtualapp/virtualapp.py:99 @@ -492,6 +500,7 @@ msgstr "" #: authentication/templates/authentication/passkey.html:173 #: authentication/views/base.py:42 authentication/views/base.py:43 #: authentication/views/base.py:44 common/const/choices.py:20 +#: settings/templates/ldap/_msg_import_ldap_user.html:26 msgid "Error" msgstr "" @@ -597,17 +606,17 @@ msgstr "" #: assets/models/domain.py:19 assets/models/group.py:17 #: assets/models/label.py:18 assets/models/platform.py:16 #: assets/models/platform.py:95 assets/serializers/asset/common.py:149 -#: assets/serializers/platform.py:118 assets/serializers/platform.py:229 +#: assets/serializers/platform.py:118 assets/serializers/platform.py:228 #: authentication/backends/passkey/models.py:10 #: authentication/serializers/connect_token_secret.py:113 #: authentication/serializers/connect_token_secret.py:168 labels/models.py:11 #: ops/mixin.py:21 ops/models/adhoc.py:20 ops/models/celery.py:15 -#: ops/models/celery.py:59 ops/models/job.py:136 ops/models/playbook.py:28 +#: ops/models/celery.py:80 ops/models/job.py:137 ops/models/playbook.py:28 #: ops/serializers/job.py:18 orgs/models.py:82 #: perms/models/asset_permission.py:61 rbac/models/role.py:29 #: settings/models.py:33 settings/models.py:181 settings/serializers/msg.py:89 #: terminal/models/applet/applet.py:33 terminal/models/component/endpoint.py:12 -#: terminal/models/component/endpoint.py:95 +#: terminal/models/component/endpoint.py:109 #: terminal/models/component/storage.py:26 terminal/models/component/task.py:13 #: terminal/models/component/terminal.py:84 #: terminal/models/virtualapp/provider.py:10 @@ -628,7 +637,7 @@ msgstr "" #: assets/models/label.py:22 #: authentication/serializers/connect_token_secret.py:117 #: terminal/models/applet/applet.py:40 -#: terminal/models/component/endpoint.py:106 +#: terminal/models/component/endpoint.py:120 #: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:173 msgid "Is active" msgstr "Active" @@ -704,20 +713,20 @@ msgstr "" msgid "Notification of implementation result of encryption change plan" msgstr "" -#: accounts/notifications.py:65 +#: accounts/notifications.py:66 msgid "" "{} - The encryption change task has been completed. See the attachment for " "details" msgstr "" -#: accounts/notifications.py:68 +#: accounts/notifications.py:70 msgid "" "{} - The encryption change task has been completed: the encryption password " -"has not been set - please go to personal information -> file encryption " -"password to set the encryption password" +"has not been set - please go to personal information -> set encryption " +"password in preferences" msgstr "" -#: accounts/notifications.py:79 +#: accounts/notifications.py:82 msgid "Gather account change information" msgstr "" @@ -736,21 +745,21 @@ msgstr "" #: accounts/serializers/account/account.py:195 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:96 #: assets/serializers/asset/common.py:125 assets/serializers/cagegory.py:12 -#: assets/serializers/platform.py:140 assets/serializers/platform.py:230 +#: assets/serializers/platform.py:140 assets/serializers/platform.py:229 #: perms/serializers/user_permission.py:26 settings/models.py:35 #: tickets/models/ticket/apply_application.py:13 users/models/preference.py:12 msgid "Category" msgstr "" #: accounts/serializers/account/account.py:196 -#: accounts/serializers/automations/base.py:53 acls/models/command_acl.py:24 +#: accounts/serializers/automations/base.py:54 acls/models/command_acl.py:24 #: acls/serializers/command_acl.py:19 applications/models.py:14 #: assets/models/_user.py:50 assets/models/automations/base.py:20 #: assets/models/cmd_filter.py:74 assets/models/platform.py:97 #: assets/serializers/asset/common.py:126 assets/serializers/platform.py:120 #: assets/serializers/platform.py:139 audits/serializers.py:53 #: audits/serializers.py:170 -#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:144 +#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:145 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:39 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:146 terminal/serializers/applet.py:29 @@ -770,7 +779,7 @@ msgstr "" msgid "Has secret" msgstr "" -#: accounts/serializers/account/account.py:261 ops/models/celery.py:62 +#: accounts/serializers/account/account.py:261 ops/models/celery.py:83 #: tickets/models/comment.py:13 tickets/models/ticket/general.py:45 #: tickets/models/ticket/general.py:279 tickets/serializers/super_ticket.py:14 #: tickets/serializers/ticket/ticket.py:21 @@ -787,7 +796,7 @@ msgstr "" #: assets/models/automations/base.py:19 #: assets/serializers/automations/base.py:20 assets/serializers/domain.py:30 #: authentication/api/connection_token.py:404 ops/models/base.py:17 -#: ops/models/job.py:146 ops/serializers/job.py:19 +#: ops/models/job.py:147 ops/serializers/job.py:19 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 msgid "Assets" msgstr "" @@ -840,7 +849,7 @@ msgid "Date" msgstr "" #: accounts/serializers/account/backup.py:38 -#: accounts/serializers/automations/base.py:36 +#: accounts/serializers/automations/base.py:37 msgid "Executed amount" msgstr "Executions" @@ -858,7 +867,7 @@ msgid "Key password" msgstr "Passphrase" #: accounts/serializers/account/base.py:78 -#: assets/serializers/asset/common.py:381 +#: assets/serializers/asset/common.py:384 msgid "Spec info" msgstr "" @@ -909,11 +918,11 @@ msgstr "" #: accounts/serializers/account/virtual.py:19 assets/models/_user.py:27 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/group.py:20 common/db/models.py:36 ops/models/adhoc.py:26 -#: ops/models/job.py:152 ops/models/playbook.py:31 rbac/models/role.py:37 +#: ops/models/job.py:153 ops/models/playbook.py:31 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:45 #: terminal/models/applet/applet.py:321 terminal/models/applet/host.py:143 #: terminal/models/component/endpoint.py:25 -#: terminal/models/component/endpoint.py:105 +#: terminal/models/component/endpoint.py:119 #: terminal/models/session/session.py:46 #: terminal/models/virtualapp/virtualapp.py:28 tickets/models/comment.py:32 #: tickets/models/ticket/general.py:297 users/models/user.py:836 @@ -933,11 +942,11 @@ msgstr "" msgid "Nodes" msgstr "" -#: accounts/serializers/automations/base.py:43 +#: accounts/serializers/automations/base.py:44 msgid "Name already exists" msgstr "" -#: accounts/serializers/automations/base.py:52 +#: accounts/serializers/automations/base.py:53 #: assets/models/automations/base.py:118 #: assets/serializers/automations/base.py:38 msgid "Automation snapshot" @@ -972,17 +981,17 @@ msgstr "Success" # msgid "Success" # msgstr "" -#: accounts/signal_handlers.py:46 +#: accounts/signal_handlers.py:47 #, python-format msgid "Push related accounts to assets: %s, by system" msgstr "" -#: accounts/signal_handlers.py:55 +#: accounts/signal_handlers.py:56 #, python-format msgid "Add account: %s" msgstr "" -#: accounts/signal_handlers.py:57 +#: accounts/signal_handlers.py:58 #, python-format msgid "Delete account: %s" msgstr "" @@ -1053,7 +1062,7 @@ msgstr "" msgid "App Acls" msgstr "" -#: acls/const.py:6 audits/const.py:36 terminal/const.py:11 tickets/const.py:45 +#: acls/const.py:6 audits/const.py:36 terminal/const.py:11 tickets/const.py:46 #: tickets/templates/tickets/approve_check_password.html:47 msgid "Reject" msgstr "" @@ -1075,13 +1084,13 @@ msgid "Notifications" msgstr "" #: acls/models/base.py:37 assets/models/_user.py:51 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:98 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:112 #: xpack/plugins/cloud/models.py:282 msgid "Priority" msgstr "" #: acls/models/base.py:38 assets/models/_user.py:51 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:99 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:113 #: xpack/plugins/cloud/models.py:283 msgid "1-100, the lower the value will be match first" msgstr "" @@ -1095,7 +1104,7 @@ msgstr "" #: authentication/models/connection_token.py:53 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:82 terminal/models/session/sharing.py:29 -#: tickets/const.py:37 +#: tickets/const.py:38 msgid "Active" msgstr "Active" @@ -1154,7 +1163,7 @@ msgstr "" msgid "Command acl" msgstr "" -#: acls/models/command_acl.py:112 tickets/const.py:11 +#: acls/models/command_acl.py:112 tickets/const.py:12 msgid "Command confirm" msgstr "" @@ -1175,7 +1184,7 @@ msgstr "" msgid "Login acl" msgstr "" -#: acls/models/login_acl.py:27 tickets/const.py:10 +#: acls/models/login_acl.py:27 tickets/const.py:11 msgid "Login confirm" msgstr "" @@ -1183,7 +1192,7 @@ msgstr "" msgid "Login asset acl" msgstr "" -#: acls/models/login_asset_acl.py:22 tickets/const.py:12 +#: acls/models/login_asset_acl.py:22 tickets/const.py:13 msgid "Login asset confirm" msgstr "" @@ -1298,7 +1307,7 @@ msgstr "" msgid "Can match application" msgstr "" -#: assets/api/asset/asset.py:179 +#: assets/api/asset/asset.py:180 msgid "Cannot create asset directly, you should create a host or other" msgstr "" @@ -1436,7 +1445,7 @@ msgid "Kubernetes" msgstr "" #: assets/const/device.py:7 terminal/models/applet/applet.py:26 -#: tickets/const.py:8 +#: tickets/const.py:9 msgid "General" msgstr "" @@ -1596,7 +1605,7 @@ msgstr "" #: assets/models/_user.py:28 assets/models/automations/base.py:114 #: assets/models/cmd_filter.py:41 assets/models/group.py:19 #: audits/models.py:267 common/db/models.py:34 ops/models/base.py:54 -#: ops/models/job.py:234 users/models/user.py:1042 +#: ops/models/job.py:235 users/models/user.py:1042 msgid "Date created" msgstr "" @@ -1710,13 +1719,13 @@ msgid "Domain" msgstr "" #: assets/models/asset/common.py:165 assets/models/automations/base.py:18 -#: assets/models/cmd_filter.py:32 assets/models/node.py:546 +#: assets/models/cmd_filter.py:32 assets/models/node.py:549 #: perms/models/asset_permission.py:72 perms/serializers/permission.py:37 #: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:330 msgid "Node" msgstr "" -#: assets/models/asset/common.py:167 assets/serializers/asset/common.py:382 +#: assets/models/asset/common.py:167 assets/serializers/asset/common.py:385 #: assets/serializers/asset/host.py:11 msgid "Gathered info" msgstr "" @@ -1765,7 +1774,7 @@ msgstr "" msgid "Proxy" msgstr "" -#: assets/models/automations/base.py:22 ops/models/job.py:230 +#: assets/models/automations/base.py:22 ops/models/job.py:231 #: settings/serializers/auth/sms.py:103 msgid "Parameters" msgstr "" @@ -1850,7 +1859,7 @@ msgstr "" msgid "System" msgstr "" -#: assets/models/label.py:19 assets/models/node.py:532 +#: assets/models/label.py:19 assets/models/node.py:535 #: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18 #: assets/serializers/cagegory.py:24 #: authentication/models/connection_token.py:29 @@ -1873,23 +1882,23 @@ msgstr "" msgid "New node" msgstr "" -#: assets/models/node.py:460 audits/backends/db.py:65 audits/backends/db.py:66 +#: assets/models/node.py:463 audits/backends/db.py:65 audits/backends/db.py:66 msgid "empty" msgstr "" -#: assets/models/node.py:531 perms/models/perm_node.py:28 +#: assets/models/node.py:534 perms/models/perm_node.py:28 msgid "Key" msgstr "" -#: assets/models/node.py:533 assets/serializers/node.py:20 +#: assets/models/node.py:536 assets/serializers/node.py:20 msgid "Full value" msgstr "" -#: assets/models/node.py:537 perms/models/perm_node.py:30 +#: assets/models/node.py:540 perms/models/perm_node.py:30 msgid "Parent key" msgstr "" -#: assets/models/node.py:549 +#: assets/models/node.py:552 msgid "Can match node" msgstr "" @@ -2049,23 +2058,23 @@ msgid "Node path" msgstr "" #: assets/serializers/asset/common.py:148 -#: assets/serializers/asset/common.py:383 +#: assets/serializers/asset/common.py:386 msgid "Auto info" msgstr "" -#: assets/serializers/asset/common.py:242 +#: assets/serializers/asset/common.py:245 msgid "Platform not exist" msgstr "" -#: assets/serializers/asset/common.py:278 +#: assets/serializers/asset/common.py:281 msgid "port out of range (0-65535)" msgstr "" -#: assets/serializers/asset/common.py:285 +#: assets/serializers/asset/common.py:288 msgid "Protocol is required: {}" msgstr "" -#: assets/serializers/asset/common.py:313 +#: assets/serializers/asset/common.py:316 msgid "Invalid data" msgstr "" @@ -2214,7 +2223,7 @@ msgstr "" msgid "type is required" msgstr "" -#: assets/serializers/platform.py:205 +#: assets/serializers/platform.py:204 msgid "Protocols is required" msgstr "" @@ -2356,14 +2365,14 @@ msgstr "" msgid "Change password" msgstr "" -#: audits/const.py:37 tickets/const.py:46 +#: audits/const.py:37 tickets/const.py:47 msgid "Approve" msgstr "" #: audits/const.py:38 #: authentication/templates/authentication/_access_key_modal.html:155 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: templates/_modal.html:22 tickets/const.py:44 +#: templates/_modal.html:22 tickets/const.py:45 msgid "Close" msgstr "" @@ -2508,16 +2517,16 @@ msgstr "" msgid "Session key" msgstr "" -#: audits/models.py:306 +#: audits/models.py:305 msgid "User session" msgstr "" -#: audits/models.py:308 +#: audits/models.py:307 msgid "Offline user session" msgstr "" #: audits/serializers.py:33 ops/models/adhoc.py:25 ops/models/base.py:16 -#: ops/models/base.py:53 ops/models/job.py:145 ops/models/job.py:233 +#: ops/models/base.py:53 ops/models/job.py:146 ops/models/job.py:234 #: ops/models/playbook.py:30 terminal/models/session/sharing.py:25 msgid "Creator" msgstr "" @@ -2587,7 +2596,7 @@ msgstr "" msgid "Slack" msgstr "" -#: audits/signal_handlers/login_log.py:40 authentication/views/dingtalk.py:160 +#: audits/signal_handlers/login_log.py:40 authentication/views/dingtalk.py:161 #: authentication/views/login.py:83 notifications/backends/__init__.py:12 #: settings/serializers/auth/dingtalk.py:10 users/models/user.py:750 #: users/models/user.py:856 @@ -2604,7 +2613,7 @@ msgstr "" msgid "Passkey" msgstr "" -#: audits/tasks.py:101 users/signal_handlers.py:166 +#: audits/tasks.py:101 msgid "Clean audits session task log" msgstr "" @@ -2654,11 +2663,11 @@ msgid "Current user not support mfa type: {}" msgstr "" #: authentication/api/password.py:33 terminal/api/session/session.py:305 -#: users/views/profile/reset.py:62 +#: users/views/profile/reset.py:63 msgid "User does not exist: {}" msgstr "" -#: authentication/api/password.py:33 users/views/profile/reset.py:163 +#: authentication/api/password.py:33 users/views/profile/reset.py:164 msgid "No user matched" msgstr "" @@ -2670,8 +2679,8 @@ msgstr "" #: authentication/api/password.py:65 #: authentication/templates/authentication/login.html:361 -#: users/templates/users/forgot_password.html:27 -#: users/templates/users/forgot_password.html:28 +#: users/templates/users/forgot_password.html:41 +#: users/templates/users/forgot_password.html:42 #: users/templates/users/forgot_password_previewing.html:13 #: users/templates/users/forgot_password_previewing.html:14 msgid "Forgot password" @@ -2682,24 +2691,24 @@ msgid "App Authentication" msgstr "" #: authentication/backends/custom.py:59 -#: authentication/backends/oauth2/backends.py:170 +#: authentication/backends/oauth2/backends.py:173 msgid "User invalid, disabled or expired" msgstr "" -#: authentication/backends/drf.py:50 +#: authentication/backends/drf.py:52 msgid "Invalid token header. No credentials provided." msgstr "" -#: authentication/backends/drf.py:53 +#: authentication/backends/drf.py:55 msgid "Invalid token header. Sign string should not contain spaces." msgstr "" -#: authentication/backends/drf.py:59 +#: authentication/backends/drf.py:61 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "" -#: authentication/backends/drf.py:72 +#: authentication/backends/drf.py:74 msgid "Invalid token or cache refreshed." msgstr "" @@ -2865,8 +2874,8 @@ msgstr "" msgid "WeCom is not bound" msgstr "" -#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:212 -#: authentication/views/dingtalk.py:254 +#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:213 +#: authentication/views/dingtalk.py:255 msgid "DingTalk is not bound" msgstr "" @@ -2975,8 +2984,8 @@ msgstr "" #: authentication/mfa/sms.py:12 authentication/serializers/password_mfa.py:16 #: authentication/serializers/password_mfa.py:24 #: settings/serializers/auth/sms.py:32 users/forms/profile.py:104 -#: users/forms/profile.py:109 users/templates/users/forgot_password.html:112 -#: users/views/profile/reset.py:99 +#: users/forms/profile.py:109 users/templates/users/forgot_password.html:155 +#: users/views/profile/reset.py:100 msgid "SMS" msgstr "" @@ -2992,7 +3001,7 @@ msgstr "" msgid "Clear phone number to disable" msgstr "" -#: authentication/middleware.py:94 settings/utils/ldap.py:676 +#: authentication/middleware.py:94 settings/utils/ldap.py:679 msgid "Authentication failed (before login check failed): {}" msgstr "" @@ -3015,7 +3024,7 @@ msgid "Please change your password" msgstr "" #: authentication/models/access_key.py:22 -#: terminal/models/component/endpoint.py:96 +#: terminal/models/component/endpoint.py:110 msgid "IP group" msgstr "" @@ -3172,7 +3181,7 @@ msgid "Is expired" msgstr "Expired" #: authentication/serializers/password_mfa.py:29 -#: users/templates/users/forgot_password.html:108 +#: users/templates/users/forgot_password.html:151 msgid "The {} cannot be empty" msgstr "" @@ -3254,7 +3263,7 @@ msgstr "" #: authentication/templates/authentication/_msg_reset_password_code.html:9 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:459 +#: jumpserver/conf.py:460 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:32 @@ -3310,7 +3319,7 @@ msgstr "" #: authentication/templates/authentication/_msg_reset_password_code.html:12 #: terminal/models/session/sharing.py:27 terminal/models/session/sharing.py:97 #: terminal/templates/terminal/_msg_session_sharing.html:12 -#: users/forms/profile.py:107 users/templates/users/forgot_password.html:66 +#: users/forms/profile.py:107 users/templates/users/forgot_password.html:97 msgid "Verify code" msgstr "" @@ -3354,7 +3363,7 @@ msgid "" msgstr "" #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 -#: templates/flash_message_standalone.html:28 tickets/const.py:17 +#: templates/flash_message_standalone.html:28 tickets/const.py:18 msgid "Cancel" msgstr "" @@ -3379,7 +3388,7 @@ msgstr "" #: authentication/templates/authentication/login_mfa.html:19 #: users/templates/users/user_otp_check_password.html:12 #: users/templates/users/user_otp_enable_bind.html:24 -#: users/templates/users/user_otp_enable_install_app.html:29 +#: users/templates/users/user_otp_enable_install_app.html:31 #: users/templates/users/user_verify_mfa.html:30 msgid "Next" msgstr "" @@ -3443,7 +3452,7 @@ msgstr "" msgid "DingTalk Error, Please contact your system administrator" msgstr "" -#: authentication/views/dingtalk.py:45 authentication/views/dingtalk.py:211 +#: authentication/views/dingtalk.py:45 authentication/views/dingtalk.py:212 msgid "DingTalk Error" msgstr "" @@ -3457,27 +3466,27 @@ msgstr "" msgid "DingTalk is already bound" msgstr "" -#: authentication/views/dingtalk.py:129 +#: authentication/views/dingtalk.py:130 msgid "Invalid user_id" msgstr "" -#: authentication/views/dingtalk.py:145 +#: authentication/views/dingtalk.py:146 msgid "DingTalk query user failed" msgstr "" -#: authentication/views/dingtalk.py:154 +#: authentication/views/dingtalk.py:155 msgid "The DingTalk is already bound to another user" msgstr "" -#: authentication/views/dingtalk.py:161 +#: authentication/views/dingtalk.py:162 msgid "Binding DingTalk successfully" msgstr "" -#: authentication/views/dingtalk.py:213 authentication/views/dingtalk.py:248 +#: authentication/views/dingtalk.py:214 authentication/views/dingtalk.py:249 msgid "Failed to get user from DingTalk" msgstr "" -#: authentication/views/dingtalk.py:255 +#: authentication/views/dingtalk.py:256 msgid "Please login with a password and then bind the DingTalk" msgstr "" @@ -3575,8 +3584,8 @@ msgstr "" msgid "Ready" msgstr "" -#: common/const/choices.py:16 terminal/const.py:77 tickets/const.py:29 -#: tickets/const.py:39 +#: common/const/choices.py:16 terminal/const.py:77 tickets/const.py:30 +#: tickets/const.py:40 msgid "Pending" msgstr "" @@ -3630,22 +3639,22 @@ msgstr "" msgid "Encrypt field using Secret Key" msgstr "" -#: common/db/fields.py:573 +#: common/db/fields.py:580 msgid "" "Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or " "{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', " "'match': 'exact', 'value': '1.1.1.1'}}" msgstr "" -#: common/db/fields.py:580 +#: common/db/fields.py:587 msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\"" msgstr "" -#: common/db/fields.py:583 +#: common/db/fields.py:590 msgid "Invalid ids for ids, should be a list" msgstr "" -#: common/db/fields.py:585 common/db/fields.py:590 +#: common/db/fields.py:592 common/db/fields.py:597 #: common/serializers/fields.py:133 tickets/serializers/ticket/common.py:58 #: xpack/plugins/cloud/serializers/account_attrs.py:56 #: xpack/plugins/cloud/serializers/account_attrs.py:79 @@ -3653,11 +3662,11 @@ msgstr "" msgid "This field is required." msgstr "" -#: common/db/fields.py:588 common/db/fields.py:593 +#: common/db/fields.py:595 common/db/fields.py:600 msgid "Invalid attrs, should be a list of dict" msgstr "" -#: common/db/fields.py:595 +#: common/db/fields.py:602 msgid "Invalid attrs, should be has name and value" msgstr "" @@ -3876,16 +3885,16 @@ msgstr "" msgid "The mobile phone number format is incorrect" msgstr "" -#: jumpserver/conf.py:453 +#: jumpserver/conf.py:454 #, python-brace-format msgid "The verification code is: {code}" msgstr "" -#: jumpserver/conf.py:458 +#: jumpserver/conf.py:459 msgid "Create account successfully" msgstr "" -#: jumpserver/conf.py:460 +#: jumpserver/conf.py:461 msgid "Your account has been created successfully" msgstr "" @@ -3979,15 +3988,15 @@ msgstr "" msgid "Skip hosts below:" msgstr "" -#: ops/api/celery.py:65 ops/api/celery.py:80 +#: ops/api/celery.py:66 ops/api/celery.py:81 msgid "Waiting task start" msgstr "" -#: ops/api/celery.py:203 +#: ops/api/celery.py:246 msgid "Task {} not found" msgstr "" -#: ops/api/celery.py:208 +#: ops/api/celery.py:251 msgid "Task {} args or kwargs error" msgstr "" @@ -4069,7 +4078,7 @@ msgstr "" msgid "Adhoc" msgstr "" -#: ops/const.py:39 ops/models/job.py:143 +#: ops/const.py:39 ops/models/job.py:144 msgid "Playbook" msgstr "" @@ -4159,11 +4168,11 @@ msgstr "" msgid "Pattern" msgstr "" -#: ops/models/adhoc.py:23 ops/models/job.py:140 +#: ops/models/adhoc.py:23 ops/models/job.py:141 msgid "Module" msgstr "" -#: ops/models/adhoc.py:24 ops/models/celery.py:60 ops/models/job.py:138 +#: ops/models/adhoc.py:24 ops/models/celery.py:81 ops/models/job.py:139 #: terminal/models/component/task.py:14 msgid "Args" msgstr "" @@ -4180,12 +4189,12 @@ msgstr "" msgid "Date last run" msgstr "" -#: ops/models/base.py:51 ops/models/job.py:231 +#: ops/models/base.py:51 ops/models/job.py:232 #: xpack/plugins/cloud/models.py:202 msgid "Result" msgstr "" -#: ops/models/base.py:52 ops/models/job.py:232 +#: ops/models/base.py:52 ops/models/job.py:233 msgid "Summary" msgstr "" @@ -4193,68 +4202,68 @@ msgstr "" msgid "Date last publish" msgstr "" -#: ops/models/celery.py:49 +#: ops/models/celery.py:70 msgid "Celery Task" msgstr "" -#: ops/models/celery.py:52 +#: ops/models/celery.py:73 msgid "Can view task monitor" msgstr "" -#: ops/models/celery.py:61 terminal/models/component/task.py:15 +#: ops/models/celery.py:82 terminal/models/component/task.py:15 msgid "Kwargs" msgstr "" -#: ops/models/celery.py:63 terminal/models/session/sharing.py:128 -#: tickets/const.py:25 +#: ops/models/celery.py:84 terminal/models/session/sharing.py:128 +#: tickets/const.py:26 msgid "Finished" msgstr "" -#: ops/models/celery.py:64 +#: ops/models/celery.py:85 msgid "Date published" msgstr "" -#: ops/models/celery.py:89 +#: ops/models/celery.py:110 msgid "Celery Task Execution" msgstr "" -#: ops/models/job.py:141 +#: ops/models/job.py:142 msgid "Chdir" msgstr "" -#: ops/models/job.py:142 +#: ops/models/job.py:143 msgid "Timeout (Seconds)" msgstr "" -#: ops/models/job.py:147 +#: ops/models/job.py:148 msgid "Use Parameter Define" msgstr "" -#: ops/models/job.py:148 +#: ops/models/job.py:149 msgid "Parameters define" msgstr "" -#: ops/models/job.py:149 +#: ops/models/job.py:150 msgid "Runas" msgstr "" -#: ops/models/job.py:151 +#: ops/models/job.py:152 msgid "Runas policy" msgstr "" -#: ops/models/job.py:215 +#: ops/models/job.py:216 msgid "Job" msgstr "" -#: ops/models/job.py:238 +#: ops/models/job.py:239 msgid "Material" msgstr "" -#: ops/models/job.py:240 +#: ops/models/job.py:241 msgid "Material Type" msgstr "" -#: ops/models/job.py:557 +#: ops/models/job.py:558 msgid "Job Execution" msgstr "" @@ -4323,6 +4332,7 @@ msgid "Is finished" msgstr "Finished" #: ops/serializers/job.py:69 +#: settings/templates/ldap/_msg_import_ldap_user.html:7 msgid "Time cost" msgstr "" @@ -4609,7 +4619,7 @@ msgstr "" msgid "The role has been bound to users, can't be destroy" msgstr "" -#: rbac/api/role.py:100 +#: rbac/api/role.py:102 msgid "Internal role, can't be update" msgstr "" @@ -4920,6 +4930,10 @@ msgstr "" msgid "Chat prompt" msgstr "" +#: settings/notifications.py:23 +msgid "Notification of Synchronized LDAP User Task Results" +msgstr "" + #: settings/serializers/auth/base.py:10 msgid "Authentication" msgstr "" @@ -5077,7 +5091,7 @@ msgstr "" msgid "Search paged size (piece)" msgstr "" -#: settings/serializers/auth/ldap.py:81 +#: settings/serializers/auth/ldap.py:84 msgid "Enable LDAP auth" msgstr "" @@ -5974,112 +5988,133 @@ msgstr "" msgid "Enable SSH Client" msgstr "" -#: settings/tasks/ldap.py:24 +#: settings/tasks/ldap.py:28 msgid "Periodic import ldap user" msgstr "" -#: settings/tasks/ldap.py:45 +#: settings/tasks/ldap.py:66 msgid "Registration periodic import ldap user task" msgstr "" -#: settings/utils/ldap.py:491 +#: settings/templates/ldap/_msg_import_ldap_user.html:2 +msgid "Sync task Finish" +msgstr "" + +#: settings/templates/ldap/_msg_import_ldap_user.html:6 +#: terminal/models/session/session.py:45 +msgid "Date end" +msgstr "" + +#: settings/templates/ldap/_msg_import_ldap_user.html:9 +msgid "Synced Organization" +msgstr "" + +#: settings/templates/ldap/_msg_import_ldap_user.html:15 +msgid "Synced User" +msgstr "" + +#: settings/templates/ldap/_msg_import_ldap_user.html:22 +msgid "No user synchronization required" +msgstr "" + +#: settings/utils/ldap.py:494 msgid "ldap:// or ldaps:// protocol is used." msgstr "" -#: settings/utils/ldap.py:502 +#: settings/utils/ldap.py:505 msgid "Host or port is disconnected: {}" msgstr "" -#: settings/utils/ldap.py:504 +#: settings/utils/ldap.py:507 msgid "The port is not the port of the LDAP service: {}" msgstr "" -#: settings/utils/ldap.py:506 +#: settings/utils/ldap.py:509 msgid "Please add certificate: {}" msgstr "" -#: settings/utils/ldap.py:510 settings/utils/ldap.py:537 -#: settings/utils/ldap.py:567 settings/utils/ldap.py:595 +#: settings/utils/ldap.py:513 settings/utils/ldap.py:540 +#: settings/utils/ldap.py:570 settings/utils/ldap.py:598 msgid "Unknown error: {}" msgstr "" -#: settings/utils/ldap.py:524 +#: settings/utils/ldap.py:527 msgid "Bind DN or Password incorrect" msgstr "" -#: settings/utils/ldap.py:531 +#: settings/utils/ldap.py:534 msgid "Please enter Bind DN: {}" msgstr "" -#: settings/utils/ldap.py:533 +#: settings/utils/ldap.py:536 msgid "Please enter Password: {}" msgstr "" -#: settings/utils/ldap.py:535 +#: settings/utils/ldap.py:538 msgid "Please enter correct Bind DN and Password: {}" msgstr "" -#: settings/utils/ldap.py:553 +#: settings/utils/ldap.py:556 msgid "Invalid User OU or User search filter: {}" msgstr "" -#: settings/utils/ldap.py:584 +#: settings/utils/ldap.py:587 msgid "LDAP User attr map not include: {}" msgstr "" -#: settings/utils/ldap.py:591 +#: settings/utils/ldap.py:594 msgid "LDAP User attr map is not dict" msgstr "" -#: settings/utils/ldap.py:610 +#: settings/utils/ldap.py:613 msgid "LDAP authentication is not enabled" msgstr "" -#: settings/utils/ldap.py:628 +#: settings/utils/ldap.py:631 msgid "Error (Invalid LDAP server): {}" msgstr "" -#: settings/utils/ldap.py:630 +#: settings/utils/ldap.py:633 msgid "Error (Invalid Bind DN): {}" msgstr "" -#: settings/utils/ldap.py:632 +#: settings/utils/ldap.py:635 msgid "Error (Invalid LDAP User attr map): {}" msgstr "" -#: settings/utils/ldap.py:634 +#: settings/utils/ldap.py:637 msgid "Error (Invalid User OU or User search filter): {}" msgstr "" -#: settings/utils/ldap.py:636 +#: settings/utils/ldap.py:639 msgid "Error (Not enabled LDAP authentication): {}" msgstr "" -#: settings/utils/ldap.py:638 +#: settings/utils/ldap.py:641 msgid "Error (Unknown): {}" msgstr "" -#: settings/utils/ldap.py:641 +#: settings/utils/ldap.py:644 msgid "Succeed: Match {} s user" msgstr "" -#: settings/utils/ldap.py:652 +#: settings/utils/ldap.py:655 msgid "Please test the connection first" msgstr "" -#: settings/utils/ldap.py:674 +#: settings/utils/ldap.py:677 msgid "Authentication failed (configuration incorrect): {}" msgstr "" -#: settings/utils/ldap.py:678 +#: settings/utils/ldap.py:681 msgid "Authentication failed (username or password incorrect): {}" msgstr "" -#: settings/utils/ldap.py:680 +#: settings/utils/ldap.py:683 msgid "Authentication failed (Unknown): {}" msgstr "" -#: settings/utils/ldap.py:683 +#: settings/utils/ldap.py:686 msgid "Authentication success: {}" msgstr "" @@ -6203,12 +6238,12 @@ msgid "Send verification code" msgstr "" #: templates/_mfa_login_field.html:106 -#: users/templates/users/forgot_password.html:130 +#: users/templates/users/forgot_password.html:174 msgid "Wait: " msgstr "" #: templates/_mfa_login_field.html:116 -#: users/templates/users/forgot_password.html:146 +#: users/templates/users/forgot_password.html:190 msgid "The verification code has been sent" msgstr "" @@ -6565,14 +6600,14 @@ msgid "SQLServer port" msgstr "" #: terminal/models/component/endpoint.py:30 -#: terminal/models/component/endpoint.py:103 +#: terminal/models/component/endpoint.py:117 #: terminal/serializers/endpoint.py:73 terminal/serializers/storage.py:41 #: terminal/serializers/storage.py:53 terminal/serializers/storage.py:83 #: terminal/serializers/storage.py:93 terminal/serializers/storage.py:101 msgid "Endpoint" msgstr "" -#: terminal/models/component/endpoint.py:109 +#: terminal/models/component/endpoint.py:123 msgid "Endpoint rule" msgstr "" @@ -6662,10 +6697,6 @@ msgstr "" msgid "Replay" msgstr "" -#: terminal/models/session/session.py:45 -msgid "Date end" -msgstr "" - #: terminal/models/session/session.py:47 terminal/serializers/session.py:65 msgid "Command amount" msgstr "" @@ -7163,54 +7194,66 @@ msgstr "" msgid "App Tickets" msgstr "" -#: tickets/const.py:9 +#: tickets/const.py:10 msgid "Apply for asset" msgstr "" -#: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 +#: tickets/const.py:17 tickets/const.py:25 tickets/const.py:44 msgid "Open" msgstr "" -#: tickets/const.py:18 tickets/const.py:31 +#: tickets/const.py:19 tickets/const.py:32 msgid "Reopen" msgstr "" -#: tickets/const.py:19 tickets/const.py:32 +#: tickets/const.py:20 tickets/const.py:33 msgid "Approved" msgstr "" -#: tickets/const.py:20 tickets/const.py:33 +#: tickets/const.py:21 tickets/const.py:34 msgid "Rejected" msgstr "" -#: tickets/const.py:30 tickets/const.py:38 +#: tickets/const.py:31 tickets/const.py:39 msgid "Closed" msgstr "" -#: tickets/const.py:50 +#: tickets/const.py:51 msgid "One level" msgstr "" -#: tickets/const.py:51 +#: tickets/const.py:52 msgid "Two level" msgstr "" -#: tickets/const.py:55 +#: tickets/const.py:56 msgid "Org admin" msgstr "" -#: tickets/const.py:56 +#: tickets/const.py:57 msgid "Custom user" msgstr "" -#: tickets/const.py:57 +#: tickets/const.py:58 msgid "Super admin" msgstr "" -#: tickets/const.py:58 +#: tickets/const.py:59 msgid "Super admin and org admin" msgstr "" +#: tickets/const.py:63 +msgid "All assets" +msgstr "" + +#: tickets/const.py:64 +msgid "Permed assets" +msgstr "" + +#: tickets/const.py:65 +msgid "Permed valid assets" +msgstr "" + #: tickets/errors.py:9 msgid "Ticket already closed" msgstr "" @@ -7719,7 +7762,7 @@ msgstr "" msgid "Reset password" msgstr "" -#: users/notifications.py:85 users/views/profile/reset.py:230 +#: users/notifications.py:85 users/views/profile/reset.py:231 msgid "Reset password success" msgstr "" @@ -7901,6 +7944,10 @@ msgid "" "administrator." msgstr "" +#: users/signal_handlers.py:166 +msgid "Clean up expired user sessions" +msgstr "" + #: users/tasks.py:25 msgid "Check password expired" msgstr "" @@ -7975,28 +8022,28 @@ msgstr "" msgid "click here to set your password" msgstr "" -#: users/templates/users/forgot_password.html:32 +#: users/templates/users/forgot_password.html:46 msgid "Input your email account, that will send a email to your" msgstr "" -#: users/templates/users/forgot_password.html:35 +#: users/templates/users/forgot_password.html:49 msgid "" "Enter your mobile number and a verification code will be sent to your phone" msgstr "" -#: users/templates/users/forgot_password.html:57 +#: users/templates/users/forgot_password.html:71 msgid "Email account" msgstr "" -#: users/templates/users/forgot_password.html:61 +#: users/templates/users/forgot_password.html:92 msgid "Mobile number" msgstr "" -#: users/templates/users/forgot_password.html:69 +#: users/templates/users/forgot_password.html:100 msgid "Send" msgstr "" -#: users/templates/users/forgot_password.html:73 +#: users/templates/users/forgot_password.html:104 #: users/templates/users/forgot_password_previewing.html:30 msgid "Submit" msgstr "" @@ -8086,7 +8133,7 @@ msgstr "" msgid "iPhone downloads" msgstr "" -#: users/templates/users/user_otp_enable_install_app.html:26 +#: users/templates/users/user_otp_enable_install_app.html:27 msgid "" "After installation, click the next step to enter the binding page (if " "installed, go to the next step directly)." @@ -8111,31 +8158,31 @@ msgstr "" msgid "Open MFA Authenticator and enter the 6-bit dynamic code" msgstr "" -#: users/views/profile/otp.py:85 +#: users/views/profile/otp.py:106 msgid "Already bound" msgstr "" -#: users/views/profile/otp.py:86 +#: users/views/profile/otp.py:107 msgid "MFA already bound, disable first, then bound" msgstr "" -#: users/views/profile/otp.py:113 +#: users/views/profile/otp.py:134 msgid "OTP enable success" msgstr "" -#: users/views/profile/otp.py:114 +#: users/views/profile/otp.py:135 msgid "OTP enable success, return login page" msgstr "" -#: users/views/profile/otp.py:156 +#: users/views/profile/otp.py:177 msgid "Disable OTP" msgstr "" -#: users/views/profile/otp.py:162 +#: users/views/profile/otp.py:183 msgid "OTP disable success" msgstr "" -#: users/views/profile/otp.py:163 +#: users/views/profile/otp.py:184 msgid "OTP disable success, return login page" msgstr "" @@ -8143,29 +8190,29 @@ msgstr "" msgid "Password invalid" msgstr "" -#: users/views/profile/reset.py:65 +#: users/views/profile/reset.py:66 msgid "" "Non-local users can log in only from third-party platforms and cannot change " "their passwords: {}" msgstr "" -#: users/views/profile/reset.py:185 users/views/profile/reset.py:196 +#: users/views/profile/reset.py:186 users/views/profile/reset.py:197 msgid "Token invalid or expired" msgstr "" -#: users/views/profile/reset.py:201 +#: users/views/profile/reset.py:202 msgid "User auth from {}, go there change password" msgstr "" -#: users/views/profile/reset.py:208 +#: users/views/profile/reset.py:209 msgid "* Your password does not meet the requirements" msgstr "" -#: users/views/profile/reset.py:214 +#: users/views/profile/reset.py:215 msgid "* The new password cannot be the last {} passwords" msgstr "" -#: users/views/profile/reset.py:231 +#: users/views/profile/reset.py:232 msgid "Reset password success, return to login page" msgstr "" diff --git a/apps/i18n/core/ja/LC_MESSAGES/django.po b/apps/i18n/core/ja/LC_MESSAGES/django.po index 06b27d1a8..1416a0476 100644 --- a/apps/i18n/core/ja/LC_MESSAGES/django.po +++ b/apps/i18n/core/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-02 11:06+0800\n" +"POT-Creation-Date: 2024-02-04 19:41+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -22,6 +22,11 @@ msgstr "" msgid "The parameter 'action' must be [{}]" msgstr "パラメータ 'action' は [{}] でなければなりません。" +#: accounts/automations/change_secret/manager.py:197 +#, python-format +msgid "Success: %s, Failed: %s, Total: %s" +msgstr "成功: %s、失敗: %s、合計: %s" + #: accounts/const/account.py:6 #: accounts/serializers/automations/change_secret.py:32 #: assets/models/_user.py:24 audits/signal_handlers/login_log.py:34 @@ -203,8 +208,8 @@ msgstr "作成のみ" #: notifications/backends/__init__.py:10 settings/serializers/msg.py:22 #: settings/serializers/msg.py:64 users/forms/profile.py:102 #: users/forms/profile.py:109 users/models/user.py:802 -#: users/templates/users/forgot_password.html:117 -#: users/views/profile/reset.py:93 +#: users/templates/users/forgot_password.html:160 +#: users/views/profile/reset.py:94 msgid "Email" msgstr "メール" @@ -360,10 +365,11 @@ msgstr "アカウントバックアップ計画" #: accounts/models/automations/backup_account.py:119 #: assets/models/automations/base.py:115 audits/models.py:65 -#: ops/models/base.py:55 ops/models/celery.py:65 ops/models/job.py:235 +#: ops/models/base.py:55 ops/models/celery.py:86 ops/models/job.py:236 #: ops/templates/ops/celery_task_log.html:75 -#: perms/models/asset_permission.py:78 terminal/models/applet/host.py:141 -#: terminal/models/session/session.py:44 +#: perms/models/asset_permission.py:78 +#: settings/templates/ldap/_msg_import_ldap_user.html:5 +#: terminal/models/applet/host.py:141 terminal/models/session/session.py:44 #: tickets/models/ticket/apply_application.py:30 #: tickets/models/ticket/apply_asset.py:19 msgid "Date start" @@ -372,6 +378,7 @@ msgstr "開始日" #: accounts/models/automations/backup_account.py:122 #: authentication/templates/authentication/_msg_oauth_bind.html:11 #: notifications/notifications.py:186 +#: settings/templates/ldap/_msg_import_ldap_user.html:3 msgid "Time" msgstr "時間" @@ -381,7 +388,7 @@ msgstr "アカウントのバックアップスナップショット" #: accounts/models/automations/backup_account.py:130 #: accounts/serializers/account/backup.py:48 -#: accounts/serializers/automations/base.py:54 +#: accounts/serializers/automations/base.py:55 #: assets/models/automations/base.py:122 #: assets/serializers/automations/base.py:39 msgid "Trigger mode" @@ -447,6 +454,7 @@ msgstr "SSHキープッシュ方式" #: accounts/models/automations/gather_account.py:58 #: accounts/serializers/account/backup.py:40 #: accounts/serializers/automations/change_secret.py:56 +#: settings/serializers/auth/ldap.py:81 msgid "Recipient" msgstr "受信者" @@ -468,14 +476,14 @@ msgstr "開始日" #: accounts/models/automations/change_secret.py:42 #: assets/models/automations/base.py:116 ops/models/base.py:56 -#: ops/models/celery.py:66 ops/models/job.py:236 +#: ops/models/celery.py:87 ops/models/job.py:237 #: terminal/models/applet/host.py:142 msgid "Date finished" msgstr "終了日" #: accounts/models/automations/change_secret.py:43 #: assets/models/automations/base.py:113 audits/models.py:208 -#: audits/serializers.py:54 ops/models/base.py:49 ops/models/job.py:227 +#: audits/serializers.py:54 ops/models/base.py:49 ops/models/job.py:228 #: terminal/models/applet/applet.py:320 terminal/models/applet/host.py:140 #: terminal/models/component/status.py:30 #: terminal/models/virtualapp/virtualapp.py:99 @@ -492,6 +500,7 @@ msgstr "ステータス" #: authentication/templates/authentication/passkey.html:173 #: authentication/views/base.py:42 authentication/views/base.py:43 #: authentication/views/base.py:44 common/const/choices.py:20 +#: settings/templates/ldap/_msg_import_ldap_user.html:26 msgid "Error" msgstr "間違い" @@ -595,17 +604,17 @@ msgstr "パスワードルール" #: assets/models/domain.py:19 assets/models/group.py:17 #: assets/models/label.py:18 assets/models/platform.py:16 #: assets/models/platform.py:95 assets/serializers/asset/common.py:149 -#: assets/serializers/platform.py:118 assets/serializers/platform.py:229 +#: assets/serializers/platform.py:118 assets/serializers/platform.py:228 #: authentication/backends/passkey/models.py:10 #: authentication/serializers/connect_token_secret.py:113 #: authentication/serializers/connect_token_secret.py:168 labels/models.py:11 #: ops/mixin.py:21 ops/models/adhoc.py:20 ops/models/celery.py:15 -#: ops/models/celery.py:59 ops/models/job.py:136 ops/models/playbook.py:28 +#: ops/models/celery.py:80 ops/models/job.py:137 ops/models/playbook.py:28 #: ops/serializers/job.py:18 orgs/models.py:82 #: perms/models/asset_permission.py:61 rbac/models/role.py:29 #: settings/models.py:33 settings/models.py:181 settings/serializers/msg.py:89 #: terminal/models/applet/applet.py:33 terminal/models/component/endpoint.py:12 -#: terminal/models/component/endpoint.py:95 +#: terminal/models/component/endpoint.py:109 #: terminal/models/component/storage.py:26 terminal/models/component/task.py:13 #: terminal/models/component/terminal.py:84 #: terminal/models/virtualapp/provider.py:10 @@ -626,7 +635,7 @@ msgstr "特権アカウント" #: assets/models/label.py:22 #: authentication/serializers/connect_token_secret.py:117 #: terminal/models/applet/applet.py:40 -#: terminal/models/component/endpoint.py:106 +#: terminal/models/component/endpoint.py:120 #: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:173 msgid "Is active" msgstr "アクティブです。" @@ -710,22 +719,22 @@ msgstr "" msgid "Notification of implementation result of encryption change plan" msgstr "暗号化変更プランの実装結果の通知" -#: accounts/notifications.py:65 +#: accounts/notifications.py:66 msgid "" "{} - The encryption change task has been completed. See the attachment for " "details" msgstr "{} -暗号化変更タスクが完了しました。詳細は添付ファイルをご覧ください" -#: accounts/notifications.py:68 +#: accounts/notifications.py:70 msgid "" "{} - The encryption change task has been completed: the encryption password " -"has not been set - please go to personal information -> file encryption " -"password to set the encryption password" +"has not been set - please go to personal information -> set encryption " +"password in preferences" msgstr "" "{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人" -"情報にアクセスしてください-> ファイル暗号化パスワードを設定してください" +"情報にアクセスしてください-> 環境設定で暗号化パスワードを設定する" -#: accounts/notifications.py:79 +#: accounts/notifications.py:82 msgid "Gather account change information" msgstr "アカウント変更情報" @@ -746,21 +755,21 @@ msgstr "アカウントの存在ポリシー" #: accounts/serializers/account/account.py:195 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:96 #: assets/serializers/asset/common.py:125 assets/serializers/cagegory.py:12 -#: assets/serializers/platform.py:140 assets/serializers/platform.py:230 +#: assets/serializers/platform.py:140 assets/serializers/platform.py:229 #: perms/serializers/user_permission.py:26 settings/models.py:35 #: tickets/models/ticket/apply_application.py:13 users/models/preference.py:12 msgid "Category" msgstr "カテゴリ" #: accounts/serializers/account/account.py:196 -#: accounts/serializers/automations/base.py:53 acls/models/command_acl.py:24 +#: accounts/serializers/automations/base.py:54 acls/models/command_acl.py:24 #: acls/serializers/command_acl.py:19 applications/models.py:14 #: assets/models/_user.py:50 assets/models/automations/base.py:20 #: assets/models/cmd_filter.py:74 assets/models/platform.py:97 #: assets/serializers/asset/common.py:126 assets/serializers/platform.py:120 #: assets/serializers/platform.py:139 audits/serializers.py:53 #: audits/serializers.py:170 -#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:144 +#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:145 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:39 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:146 terminal/serializers/applet.py:29 @@ -780,7 +789,7 @@ msgstr "資産が存在しません" msgid "Has secret" msgstr "エスクローされたパスワード" -#: accounts/serializers/account/account.py:261 ops/models/celery.py:62 +#: accounts/serializers/account/account.py:261 ops/models/celery.py:83 #: tickets/models/comment.py:13 tickets/models/ticket/general.py:45 #: tickets/models/ticket/general.py:279 tickets/serializers/super_ticket.py:14 #: tickets/serializers/ticket/ticket.py:21 @@ -797,7 +806,7 @@ msgstr "編集済み" #: assets/models/automations/base.py:19 #: assets/serializers/automations/base.py:20 assets/serializers/domain.py:30 #: authentication/api/connection_token.py:404 ops/models/base.py:17 -#: ops/models/job.py:146 ops/serializers/job.py:19 +#: ops/models/job.py:147 ops/serializers/job.py:19 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 msgid "Assets" msgstr "資産" @@ -850,7 +859,7 @@ msgid "Date" msgstr "日付" #: accounts/serializers/account/backup.py:38 -#: accounts/serializers/automations/base.py:36 +#: accounts/serializers/automations/base.py:37 msgid "Executed amount" msgstr "実行回数" @@ -868,7 +877,7 @@ msgid "Key password" msgstr "キーパスワード" #: accounts/serializers/account/base.py:78 -#: assets/serializers/asset/common.py:381 +#: assets/serializers/asset/common.py:384 msgid "Spec info" msgstr "特別情報" @@ -921,11 +930,11 @@ msgstr "关联平台,可以配置推送参数,如果不关联,则使用默 #: accounts/serializers/account/virtual.py:19 assets/models/_user.py:27 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/group.py:20 common/db/models.py:36 ops/models/adhoc.py:26 -#: ops/models/job.py:152 ops/models/playbook.py:31 rbac/models/role.py:37 +#: ops/models/job.py:153 ops/models/playbook.py:31 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:45 #: terminal/models/applet/applet.py:321 terminal/models/applet/host.py:143 #: terminal/models/component/endpoint.py:25 -#: terminal/models/component/endpoint.py:105 +#: terminal/models/component/endpoint.py:119 #: terminal/models/session/session.py:46 #: terminal/models/virtualapp/virtualapp.py:28 tickets/models/comment.py:32 #: tickets/models/ticket/general.py:297 users/models/user.py:836 @@ -949,11 +958,11 @@ msgstr "" msgid "Nodes" msgstr "ノード" -#: accounts/serializers/automations/base.py:43 +#: accounts/serializers/automations/base.py:44 msgid "Name already exists" msgstr "名前は既に存在します。" -#: accounts/serializers/automations/base.py:52 +#: accounts/serializers/automations/base.py:53 #: assets/models/automations/base.py:118 #: assets/serializers/automations/base.py:38 msgid "Automation snapshot" @@ -984,17 +993,17 @@ msgstr "自動タスク実行履歴" msgid "Success" msgstr "成功" -#: accounts/signal_handlers.py:46 +#: accounts/signal_handlers.py:47 #, python-format msgid "Push related accounts to assets: %s, by system" msgstr "関連するアカウントをアセットにプッシュ: %s, by system" -#: accounts/signal_handlers.py:55 +#: accounts/signal_handlers.py:56 #, python-format msgid "Add account: %s" msgstr "アカウントを追加: %s" -#: accounts/signal_handlers.py:57 +#: accounts/signal_handlers.py:58 #, python-format msgid "Delete account: %s" msgstr "アカウントを削除: %s" @@ -1068,7 +1077,7 @@ msgstr "秘密鍵が無効またはpassphraseエラー" msgid "App Acls" msgstr "Acls" -#: acls/const.py:6 audits/const.py:36 terminal/const.py:11 tickets/const.py:45 +#: acls/const.py:6 audits/const.py:36 terminal/const.py:11 tickets/const.py:46 #: tickets/templates/tickets/approve_check_password.html:47 msgid "Reject" msgstr "拒否" @@ -1090,13 +1099,13 @@ msgid "Notifications" msgstr "通知" #: acls/models/base.py:37 assets/models/_user.py:51 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:98 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:112 #: xpack/plugins/cloud/models.py:282 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:38 assets/models/_user.py:51 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:99 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:113 #: xpack/plugins/cloud/models.py:283 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" @@ -1110,7 +1119,7 @@ msgstr "レビュー担当者" #: authentication/models/connection_token.py:53 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:82 terminal/models/session/sharing.py:29 -#: tickets/const.py:37 +#: tickets/const.py:38 msgid "Active" msgstr "アクティブ" @@ -1169,7 +1178,7 @@ msgstr "生成された正規表現が正しくありません: {}" msgid "Command acl" msgstr "コマンドフィルタリング" -#: acls/models/command_acl.py:112 tickets/const.py:11 +#: acls/models/command_acl.py:112 tickets/const.py:12 msgid "Command confirm" msgstr "コマンドの確認" @@ -1190,7 +1199,7 @@ msgstr "ルール" msgid "Login acl" msgstr "ログインacl" -#: acls/models/login_acl.py:27 tickets/const.py:10 +#: acls/models/login_acl.py:27 tickets/const.py:11 msgid "Login confirm" msgstr "ログイン確認" @@ -1198,7 +1207,7 @@ msgstr "ログイン確認" msgid "Login asset acl" msgstr "ログインasset acl" -#: acls/models/login_asset_acl.py:22 tickets/const.py:12 +#: acls/models/login_asset_acl.py:22 tickets/const.py:13 msgid "Login asset confirm" msgstr "ログイン資産の確認" @@ -1325,7 +1334,7 @@ msgstr "アプリケーション" msgid "Can match application" msgstr "アプリケーションを一致させることができます" -#: assets/api/asset/asset.py:179 +#: assets/api/asset/asset.py:180 msgid "Cannot create asset directly, you should create a host or other" msgstr "" "資産を直接作成することはできません。ホストまたはその他を作成する必要がありま" @@ -1467,7 +1476,7 @@ msgid "Kubernetes" msgstr "Kubernetes" #: assets/const/device.py:7 terminal/models/applet/applet.py:26 -#: tickets/const.py:8 +#: tickets/const.py:9 msgid "General" msgstr "一般" @@ -1632,7 +1641,7 @@ msgstr "SSHパブリックキー" #: assets/models/_user.py:28 assets/models/automations/base.py:114 #: assets/models/cmd_filter.py:41 assets/models/group.py:19 #: audits/models.py:267 common/db/models.py:34 ops/models/base.py:54 -#: ops/models/job.py:234 users/models/user.py:1042 +#: ops/models/job.py:235 users/models/user.py:1042 msgid "Date created" msgstr "作成された日付" @@ -1746,13 +1755,13 @@ msgid "Domain" msgstr "ドメイン" #: assets/models/asset/common.py:165 assets/models/automations/base.py:18 -#: assets/models/cmd_filter.py:32 assets/models/node.py:546 +#: assets/models/cmd_filter.py:32 assets/models/node.py:549 #: perms/models/asset_permission.py:72 perms/serializers/permission.py:37 #: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:330 msgid "Node" msgstr "ノード" -#: assets/models/asset/common.py:167 assets/serializers/asset/common.py:382 +#: assets/models/asset/common.py:167 assets/serializers/asset/common.py:385 #: assets/serializers/asset/host.py:11 msgid "Gathered info" msgstr "資産ハードウェア情報の収集" @@ -1801,7 +1810,7 @@ msgstr "証明書チェックを無視" msgid "Proxy" msgstr "プロキシー" -#: assets/models/automations/base.py:22 ops/models/job.py:230 +#: assets/models/automations/base.py:22 ops/models/job.py:231 #: settings/serializers/auth/sms.py:103 msgid "Parameters" msgstr "パラメータ" @@ -1886,7 +1895,7 @@ msgstr "デフォルトアセットグループ" msgid "System" msgstr "システム" -#: assets/models/label.py:19 assets/models/node.py:532 +#: assets/models/label.py:19 assets/models/node.py:535 #: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18 #: assets/serializers/cagegory.py:24 #: authentication/models/connection_token.py:29 @@ -1909,23 +1918,23 @@ msgstr "ラベル" msgid "New node" msgstr "新しいノード" -#: assets/models/node.py:460 audits/backends/db.py:65 audits/backends/db.py:66 +#: assets/models/node.py:463 audits/backends/db.py:65 audits/backends/db.py:66 msgid "empty" msgstr "空" -#: assets/models/node.py:531 perms/models/perm_node.py:28 +#: assets/models/node.py:534 perms/models/perm_node.py:28 msgid "Key" msgstr "キー" -#: assets/models/node.py:533 assets/serializers/node.py:20 +#: assets/models/node.py:536 assets/serializers/node.py:20 msgid "Full value" msgstr "フルバリュー" -#: assets/models/node.py:537 perms/models/perm_node.py:30 +#: assets/models/node.py:540 perms/models/perm_node.py:30 msgid "Parent key" msgstr "親キー" -#: assets/models/node.py:549 +#: assets/models/node.py:552 msgid "Can match node" msgstr "ノードを一致させることができます" @@ -2087,23 +2096,23 @@ msgid "Node path" msgstr "ノードパスです" #: assets/serializers/asset/common.py:148 -#: assets/serializers/asset/common.py:383 +#: assets/serializers/asset/common.py:386 msgid "Auto info" msgstr "自動情報" -#: assets/serializers/asset/common.py:242 +#: assets/serializers/asset/common.py:245 msgid "Platform not exist" msgstr "プラットフォームが存在しません" -#: assets/serializers/asset/common.py:278 +#: assets/serializers/asset/common.py:281 msgid "port out of range (0-65535)" msgstr "ポート番号が範囲外です (0-65535)" -#: assets/serializers/asset/common.py:285 +#: assets/serializers/asset/common.py:288 msgid "Protocol is required: {}" msgstr "プロトコルが必要です: {}" -#: assets/serializers/asset/common.py:313 +#: assets/serializers/asset/common.py:316 msgid "Invalid data" msgstr "無効なデータ" @@ -2259,7 +2268,7 @@ msgstr "デフォルト ドメイン" msgid "type is required" msgstr "タイプ このフィールドは必須です." -#: assets/serializers/platform.py:205 +#: assets/serializers/platform.py:204 msgid "Protocols is required" msgstr "同意が必要です" @@ -2405,14 +2414,14 @@ msgstr "ログイン" msgid "Change password" msgstr "パスワードを変更する" -#: audits/const.py:37 tickets/const.py:46 +#: audits/const.py:37 tickets/const.py:47 msgid "Approve" msgstr "承認" #: audits/const.py:38 #: authentication/templates/authentication/_access_key_modal.html:155 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: templates/_modal.html:22 tickets/const.py:44 +#: templates/_modal.html:22 tickets/const.py:45 msgid "Close" msgstr "閉じる" @@ -2557,16 +2566,16 @@ msgstr "ユーザーログインログ" msgid "Session key" msgstr "セッションID" -#: audits/models.py:306 +#: audits/models.py:305 msgid "User session" msgstr "ユーザーセッション" -#: audits/models.py:308 +#: audits/models.py:307 msgid "Offline user session" msgstr "オフラインユーザセッション" #: audits/serializers.py:33 ops/models/adhoc.py:25 ops/models/base.py:16 -#: ops/models/base.py:53 ops/models/job.py:145 ops/models/job.py:233 +#: ops/models/base.py:53 ops/models/job.py:146 ops/models/job.py:234 #: ops/models/playbook.py:30 terminal/models/session/sharing.py:25 msgid "Creator" msgstr "作成者" @@ -2636,7 +2645,7 @@ msgstr "本を飛ばす" msgid "Slack" msgstr "" -#: audits/signal_handlers/login_log.py:40 authentication/views/dingtalk.py:160 +#: audits/signal_handlers/login_log.py:40 authentication/views/dingtalk.py:161 #: authentication/views/login.py:83 notifications/backends/__init__.py:12 #: settings/serializers/auth/dingtalk.py:10 users/models/user.py:750 #: users/models/user.py:856 @@ -2653,9 +2662,9 @@ msgstr "仮パスワード" msgid "Passkey" msgstr "Passkey" -#: audits/tasks.py:101 users/signal_handlers.py:166 +#: audits/tasks.py:101 msgid "Clean audits session task log" -msgstr "監査セッション タスク ログのクリーンアップ" +msgstr "資産監査セッションタスクログのクリーンアップ" #: audits/tasks.py:114 msgid "Upload FTP file to external storage" @@ -2705,11 +2714,11 @@ msgid "Current user not support mfa type: {}" msgstr "現在のユーザーはmfaタイプをサポートしていません: {}" #: authentication/api/password.py:33 terminal/api/session/session.py:305 -#: users/views/profile/reset.py:62 +#: users/views/profile/reset.py:63 msgid "User does not exist: {}" msgstr "ユーザーが存在しない: {}" -#: authentication/api/password.py:33 users/views/profile/reset.py:163 +#: authentication/api/password.py:33 users/views/profile/reset.py:164 msgid "No user matched" msgstr "ユーザーにマッチしなかった" @@ -2723,8 +2732,8 @@ msgstr "" #: authentication/api/password.py:65 #: authentication/templates/authentication/login.html:361 -#: users/templates/users/forgot_password.html:27 -#: users/templates/users/forgot_password.html:28 +#: users/templates/users/forgot_password.html:41 +#: users/templates/users/forgot_password.html:42 #: users/templates/users/forgot_password_previewing.html:13 #: users/templates/users/forgot_password_previewing.html:14 msgid "Forgot password" @@ -2737,25 +2746,25 @@ msgid "App Authentication" msgstr "認証" #: authentication/backends/custom.py:59 -#: authentication/backends/oauth2/backends.py:170 +#: authentication/backends/oauth2/backends.py:173 msgid "User invalid, disabled or expired" msgstr "ユーザーが無効、無効、または期限切れです" -#: authentication/backends/drf.py:50 +#: authentication/backends/drf.py:52 msgid "Invalid token header. No credentials provided." msgstr "無効なトークンヘッダー。資格情報は提供されていません。" -#: authentication/backends/drf.py:53 +#: authentication/backends/drf.py:55 msgid "Invalid token header. Sign string should not contain spaces." msgstr "無効なトークンヘッダー。記号文字列にはスペースを含めないでください。" -#: authentication/backends/drf.py:59 +#: authentication/backends/drf.py:61 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "" "無効なトークンヘッダー。署名文字列に無効な文字を含めることはできません。" -#: authentication/backends/drf.py:72 +#: authentication/backends/drf.py:74 msgid "Invalid token or cache refreshed." msgstr "無効なトークンまたはキャッシュの更新。" @@ -2930,8 +2939,8 @@ msgstr "企業の微信はすでにバインドされています" msgid "WeCom is not bound" msgstr "企業の微信をバインドしていません" -#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:212 -#: authentication/views/dingtalk.py:254 +#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:213 +#: authentication/views/dingtalk.py:255 msgid "DingTalk is not bound" msgstr "DingTalkはバインドされていません" @@ -3042,8 +3051,8 @@ msgstr "メッセージ検証コードが無効" #: authentication/mfa/sms.py:12 authentication/serializers/password_mfa.py:16 #: authentication/serializers/password_mfa.py:24 #: settings/serializers/auth/sms.py:32 users/forms/profile.py:104 -#: users/forms/profile.py:109 users/templates/users/forgot_password.html:112 -#: users/views/profile/reset.py:99 +#: users/forms/profile.py:109 users/templates/users/forgot_password.html:155 +#: users/views/profile/reset.py:100 msgid "SMS" msgstr "メッセージ" @@ -3059,7 +3068,7 @@ msgstr "電話番号を設定して有効にする" msgid "Clear phone number to disable" msgstr "無効にする電話番号をクリアする" -#: authentication/middleware.py:94 settings/utils/ldap.py:676 +#: authentication/middleware.py:94 settings/utils/ldap.py:679 msgid "Authentication failed (before login check failed): {}" msgstr "認証に失敗しました (ログインチェックが失敗する前): {}" @@ -3084,7 +3093,7 @@ msgid "Please change your password" msgstr "パスワードを変更してください" #: authentication/models/access_key.py:22 -#: terminal/models/component/endpoint.py:96 +#: terminal/models/component/endpoint.py:110 msgid "IP group" msgstr "IP グループ" @@ -3241,7 +3250,7 @@ msgid "Is expired" msgstr "期限切れです" #: authentication/serializers/password_mfa.py:29 -#: users/templates/users/forgot_password.html:108 +#: users/templates/users/forgot_password.html:151 msgid "The {} cannot be empty" msgstr "{} 空にしてはならない" @@ -3321,7 +3330,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password_code.html:9 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:459 +#: jumpserver/conf.py:460 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:32 @@ -3381,7 +3390,7 @@ msgstr "新しいものを要求する" #: authentication/templates/authentication/_msg_reset_password_code.html:12 #: terminal/models/session/sharing.py:27 terminal/models/session/sharing.py:97 #: terminal/templates/terminal/_msg_session_sharing.html:12 -#: users/forms/profile.py:107 users/templates/users/forgot_password.html:66 +#: users/forms/profile.py:107 users/templates/users/forgot_password.html:97 msgid "Verify code" msgstr "コードの確認" @@ -3429,7 +3438,7 @@ msgstr "" "能性があります" #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 -#: templates/flash_message_standalone.html:28 tickets/const.py:17 +#: templates/flash_message_standalone.html:28 tickets/const.py:18 msgid "Cancel" msgstr "キャンセル" @@ -3456,7 +3465,7 @@ msgstr "MFA マルチファクタ認証" #: authentication/templates/authentication/login_mfa.html:19 #: users/templates/users/user_otp_check_password.html:12 #: users/templates/users/user_otp_enable_bind.html:24 -#: users/templates/users/user_otp_enable_install_app.html:29 +#: users/templates/users/user_otp_enable_install_app.html:31 #: users/templates/users/user_verify_mfa.html:30 msgid "Next" msgstr "次へ" @@ -3522,7 +3531,7 @@ msgstr "バインド%s成功" msgid "DingTalk Error, Please contact your system administrator" msgstr "DingTalkエラー、システム管理者に連絡してください" -#: authentication/views/dingtalk.py:45 authentication/views/dingtalk.py:211 +#: authentication/views/dingtalk.py:45 authentication/views/dingtalk.py:212 msgid "DingTalk Error" msgstr "DingTalkエラー" @@ -3536,27 +3545,27 @@ msgstr "システム設定が正しくありません。管理者に連絡して msgid "DingTalk is already bound" msgstr "DingTalkはすでにバインドされています" -#: authentication/views/dingtalk.py:129 +#: authentication/views/dingtalk.py:130 msgid "Invalid user_id" msgstr "無効なuser_id" -#: authentication/views/dingtalk.py:145 +#: authentication/views/dingtalk.py:146 msgid "DingTalk query user failed" msgstr "DingTalkクエリユーザーが失敗しました" -#: authentication/views/dingtalk.py:154 +#: authentication/views/dingtalk.py:155 msgid "The DingTalk is already bound to another user" msgstr "DingTalkはすでに別のユーザーにバインドされています" -#: authentication/views/dingtalk.py:161 +#: authentication/views/dingtalk.py:162 msgid "Binding DingTalk successfully" msgstr "DingTalkのバインドに成功" -#: authentication/views/dingtalk.py:213 authentication/views/dingtalk.py:248 +#: authentication/views/dingtalk.py:214 authentication/views/dingtalk.py:249 msgid "Failed to get user from DingTalk" msgstr "DingTalkからユーザーを取得できませんでした" -#: authentication/views/dingtalk.py:255 +#: authentication/views/dingtalk.py:256 msgid "Please login with a password and then bind the DingTalk" msgstr "パスワードでログインし、DingTalkをバインドしてください" @@ -3656,8 +3665,8 @@ msgstr "タイミングトリガー" msgid "Ready" msgstr "の準備を" -#: common/const/choices.py:16 terminal/const.py:77 tickets/const.py:29 -#: tickets/const.py:39 +#: common/const/choices.py:16 terminal/const.py:77 tickets/const.py:30 +#: tickets/const.py:40 msgid "Pending" msgstr "未定" @@ -3711,7 +3720,7 @@ msgstr "テキストフィールドへのマーシャルデータ" msgid "Encrypt field using Secret Key" msgstr "Secret Keyを使用したフィールドの暗号化" -#: common/db/fields.py:573 +#: common/db/fields.py:580 msgid "" "Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or " "{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', " @@ -3721,15 +3730,15 @@ msgstr "" "{'type':'ids','ids':[]}或 #タイプ:属性、属性:[#名前:ip、照合:正確、" "値:1.1.1.1}" -#: common/db/fields.py:580 +#: common/db/fields.py:587 msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\"" msgstr "無効なタイプです。all、ids、またはattrsでなければなりません" -#: common/db/fields.py:583 +#: common/db/fields.py:590 msgid "Invalid ids for ids, should be a list" msgstr "無効なID、リストでなければなりません" -#: common/db/fields.py:585 common/db/fields.py:590 +#: common/db/fields.py:592 common/db/fields.py:597 #: common/serializers/fields.py:133 tickets/serializers/ticket/common.py:58 #: xpack/plugins/cloud/serializers/account_attrs.py:56 #: xpack/plugins/cloud/serializers/account_attrs.py:79 @@ -3737,11 +3746,11 @@ msgstr "無効なID、リストでなければなりません" msgid "This field is required." msgstr "このフィールドは必須です。" -#: common/db/fields.py:588 common/db/fields.py:593 +#: common/db/fields.py:595 common/db/fields.py:600 msgid "Invalid attrs, should be a list of dict" msgstr "無効な属性、dictリストでなければなりません" -#: common/db/fields.py:595 +#: common/db/fields.py:602 msgid "Invalid attrs, should be has name and value" msgstr "名前と値が必要な無効な属性" @@ -3962,16 +3971,16 @@ msgstr "特殊文字を含むべきではない" msgid "The mobile phone number format is incorrect" msgstr "携帯電話番号の形式が正しくありません" -#: jumpserver/conf.py:453 +#: jumpserver/conf.py:454 #, python-brace-format msgid "The verification code is: {code}" msgstr "認証コードは: {code}" -#: jumpserver/conf.py:458 +#: jumpserver/conf.py:459 msgid "Create account successfully" msgstr "アカウントを正常に作成" -#: jumpserver/conf.py:460 +#: jumpserver/conf.py:461 msgid "Your account has been created successfully" msgstr "アカウントが正常に作成されました" @@ -4079,15 +4088,15 @@ msgstr "Ansible 無効" msgid "Skip hosts below:" msgstr "次のホストをスキップします: " -#: ops/api/celery.py:65 ops/api/celery.py:80 +#: ops/api/celery.py:66 ops/api/celery.py:81 msgid "Waiting task start" msgstr "タスク開始待ち" -#: ops/api/celery.py:203 +#: ops/api/celery.py:246 msgid "Task {} not found" msgstr "タスクは存在しません" -#: ops/api/celery.py:208 +#: ops/api/celery.py:251 msgid "Task {} args or kwargs error" msgstr "タスク実行パラメータエラー" @@ -4173,7 +4182,7 @@ msgstr "VCS" msgid "Adhoc" msgstr "コマンド#コマンド#" -#: ops/const.py:39 ops/models/job.py:143 +#: ops/const.py:39 ops/models/job.py:144 msgid "Playbook" msgstr "Playbook" @@ -4271,11 +4280,11 @@ msgstr "定期的または定期的に設定を行う必要があります" msgid "Pattern" msgstr "パターン" -#: ops/models/adhoc.py:23 ops/models/job.py:140 +#: ops/models/adhoc.py:23 ops/models/job.py:141 msgid "Module" msgstr "モジュール" -#: ops/models/adhoc.py:24 ops/models/celery.py:60 ops/models/job.py:138 +#: ops/models/adhoc.py:24 ops/models/celery.py:81 ops/models/job.py:139 #: terminal/models/component/task.py:14 msgid "Args" msgstr "アルグ" @@ -4294,12 +4303,12 @@ msgstr "最後の実行" msgid "Date last run" msgstr "最終実行日" -#: ops/models/base.py:51 ops/models/job.py:231 +#: ops/models/base.py:51 ops/models/job.py:232 #: xpack/plugins/cloud/models.py:202 msgid "Result" msgstr "結果" -#: ops/models/base.py:52 ops/models/job.py:232 +#: ops/models/base.py:52 ops/models/job.py:233 msgid "Summary" msgstr "概要" @@ -4307,68 +4316,68 @@ msgstr "概要" msgid "Date last publish" msgstr "発売日" -#: ops/models/celery.py:49 +#: ops/models/celery.py:70 msgid "Celery Task" msgstr "Celery タスク#タスク#" -#: ops/models/celery.py:52 +#: ops/models/celery.py:73 msgid "Can view task monitor" msgstr "タスクモニターを表示できます" -#: ops/models/celery.py:61 terminal/models/component/task.py:15 +#: ops/models/celery.py:82 terminal/models/component/task.py:15 msgid "Kwargs" msgstr "クワーグ" -#: ops/models/celery.py:63 terminal/models/session/sharing.py:128 -#: tickets/const.py:25 +#: ops/models/celery.py:84 terminal/models/session/sharing.py:128 +#: tickets/const.py:26 msgid "Finished" msgstr "終了" -#: ops/models/celery.py:64 +#: ops/models/celery.py:85 msgid "Date published" msgstr "発売日" -#: ops/models/celery.py:89 +#: ops/models/celery.py:110 msgid "Celery Task Execution" msgstr "Celery タスク実行" -#: ops/models/job.py:141 +#: ops/models/job.py:142 msgid "Chdir" msgstr "実行ディレクトリ" -#: ops/models/job.py:142 +#: ops/models/job.py:143 msgid "Timeout (Seconds)" msgstr "タイムアウト(秒)" -#: ops/models/job.py:147 +#: ops/models/job.py:148 msgid "Use Parameter Define" msgstr "パラメータ定義を使用する" -#: ops/models/job.py:148 +#: ops/models/job.py:149 msgid "Parameters define" msgstr "パラメータ定義" -#: ops/models/job.py:149 +#: ops/models/job.py:150 msgid "Runas" msgstr "ユーザーとして実行" -#: ops/models/job.py:151 +#: ops/models/job.py:152 msgid "Runas policy" msgstr "ユーザー ポリシー" -#: ops/models/job.py:215 +#: ops/models/job.py:216 msgid "Job" msgstr "ジョブ#ジョブ#" -#: ops/models/job.py:238 +#: ops/models/job.py:239 msgid "Material" msgstr "Material" -#: ops/models/job.py:240 +#: ops/models/job.py:241 msgid "Material Type" msgstr "Material を選択してオプションを設定します。" -#: ops/models/job.py:557 +#: ops/models/job.py:558 msgid "Job Execution" msgstr "ジョブ実行" @@ -4443,6 +4452,7 @@ msgid "Is finished" msgstr "終了しました" #: ops/serializers/job.py:69 +#: settings/templates/ldap/_msg_import_ldap_user.html:7 msgid "Time cost" msgstr "時を過ごす" @@ -4743,7 +4753,7 @@ msgstr "内部の役割は、破壊することはできません" msgid "The role has been bound to users, can't be destroy" msgstr "ロールはユーザーにバインドされており、破壊することはできません" -#: rbac/api/role.py:100 +#: rbac/api/role.py:102 msgid "Internal role, can't be update" msgstr "内部ロール、更新できません" @@ -5061,7 +5071,15 @@ msgstr "他の設定を変えることができます" msgid "Chat prompt" msgstr "チャットのヒント" +#: settings/notifications.py:23 +#, fuzzy +#| msgid "Notification of account backup route task results" +msgid "Notification of Synchronized LDAP User Task Results" +msgstr "アカウントバックアップルートタスクの結果の通知" + #: settings/serializers/auth/base.py:10 +#, fuzzy +#| msgid "Authentication" msgid "Authentication" msgstr "認証" @@ -5220,7 +5238,7 @@ msgstr "接続タイムアウト (秒)" msgid "Search paged size (piece)" msgstr "ページサイズを検索 (じょう)" -#: settings/serializers/auth/ldap.py:81 +#: settings/serializers/auth/ldap.py:84 msgid "Enable LDAP auth" msgstr "LDAP認証の有効化" @@ -6174,112 +6192,133 @@ msgstr "Razor の有効化" msgid "Enable SSH Client" msgstr "SSH Clientの有効化" -#: settings/tasks/ldap.py:24 +#: settings/tasks/ldap.py:28 msgid "Periodic import ldap user" msgstr "LDAP ユーザーを定期的にインポートする" -#: settings/tasks/ldap.py:45 +#: settings/tasks/ldap.py:66 msgid "Registration periodic import ldap user task" msgstr "登録サイクルLDAPユーザータスクのインポート" -#: settings/utils/ldap.py:491 +#: settings/templates/ldap/_msg_import_ldap_user.html:2 +msgid "Sync task Finish" +msgstr "同期タスクが完了しました" + +#: settings/templates/ldap/_msg_import_ldap_user.html:6 +#: terminal/models/session/session.py:45 +msgid "Date end" +msgstr "終了日" + +#: settings/templates/ldap/_msg_import_ldap_user.html:9 +msgid "Synced Organization" +msgstr "組織が同期されました" + +#: settings/templates/ldap/_msg_import_ldap_user.html:15 +msgid "Synced User" +msgstr "同期されたユーザー" + +#: settings/templates/ldap/_msg_import_ldap_user.html:22 +msgid "No user synchronization required" +msgstr "ユーザーの同期は必要ありません" + +#: settings/utils/ldap.py:494 msgid "ldap:// or ldaps:// protocol is used." msgstr "ldap:// または ldaps:// プロトコルが使用されます。" -#: settings/utils/ldap.py:502 +#: settings/utils/ldap.py:505 msgid "Host or port is disconnected: {}" msgstr "ホストまたはポートが切断されました: {}" -#: settings/utils/ldap.py:504 +#: settings/utils/ldap.py:507 msgid "The port is not the port of the LDAP service: {}" msgstr "ポートはLDAPサービスのポートではありません: {}" -#: settings/utils/ldap.py:506 +#: settings/utils/ldap.py:509 msgid "Please add certificate: {}" msgstr "証明書を追加してください: {}" -#: settings/utils/ldap.py:510 settings/utils/ldap.py:537 -#: settings/utils/ldap.py:567 settings/utils/ldap.py:595 +#: settings/utils/ldap.py:513 settings/utils/ldap.py:540 +#: settings/utils/ldap.py:570 settings/utils/ldap.py:598 msgid "Unknown error: {}" msgstr "不明なエラー: {}" -#: settings/utils/ldap.py:524 +#: settings/utils/ldap.py:527 msgid "Bind DN or Password incorrect" msgstr "DNまたはパスワードのバインドが正しくありません" -#: settings/utils/ldap.py:531 +#: settings/utils/ldap.py:534 msgid "Please enter Bind DN: {}" msgstr "バインドDN: {} を入力してください" -#: settings/utils/ldap.py:533 +#: settings/utils/ldap.py:536 msgid "Please enter Password: {}" msgstr "パスワードを入力してください: {}" -#: settings/utils/ldap.py:535 +#: settings/utils/ldap.py:538 msgid "Please enter correct Bind DN and Password: {}" msgstr "正しいバインドDNとパスワードを入力してください: {}" -#: settings/utils/ldap.py:553 +#: settings/utils/ldap.py:556 msgid "Invalid User OU or User search filter: {}" msgstr "無効なユーザー OU またはユーザー検索フィルター: {}" -#: settings/utils/ldap.py:584 +#: settings/utils/ldap.py:587 msgid "LDAP User attr map not include: {}" msgstr "LDAP ユーザーattrマップは含まれません: {}" -#: settings/utils/ldap.py:591 +#: settings/utils/ldap.py:594 msgid "LDAP User attr map is not dict" msgstr "LDAPユーザーattrマップはdictではありません" -#: settings/utils/ldap.py:610 +#: settings/utils/ldap.py:613 msgid "LDAP authentication is not enabled" msgstr "LDAP 認証が有効になっていない" -#: settings/utils/ldap.py:628 +#: settings/utils/ldap.py:631 msgid "Error (Invalid LDAP server): {}" msgstr "エラー (LDAPサーバーが無効): {}" -#: settings/utils/ldap.py:630 +#: settings/utils/ldap.py:633 msgid "Error (Invalid Bind DN): {}" msgstr "エラー (DNのバインドが無効): {}" -#: settings/utils/ldap.py:632 +#: settings/utils/ldap.py:635 msgid "Error (Invalid LDAP User attr map): {}" msgstr "エラー (LDAPユーザーattrマップが無効): {}" -#: settings/utils/ldap.py:634 +#: settings/utils/ldap.py:637 msgid "Error (Invalid User OU or User search filter): {}" msgstr "エラー (ユーザーOUまたはユーザー検索フィルターが無効): {}" -#: settings/utils/ldap.py:636 +#: settings/utils/ldap.py:639 msgid "Error (Not enabled LDAP authentication): {}" msgstr "エラー (LDAP認証が有効化されていません): {}" -#: settings/utils/ldap.py:638 +#: settings/utils/ldap.py:641 msgid "Error (Unknown): {}" msgstr "エラー (不明): {}" -#: settings/utils/ldap.py:641 +#: settings/utils/ldap.py:644 msgid "Succeed: Match {} s user" msgstr "成功: {} 人のユーザーに一致" -#: settings/utils/ldap.py:652 +#: settings/utils/ldap.py:655 msgid "Please test the connection first" msgstr "まず接続をテストしてください" -#: settings/utils/ldap.py:674 +#: settings/utils/ldap.py:677 msgid "Authentication failed (configuration incorrect): {}" msgstr "認証に失敗しました (設定が正しくありません): {}" -#: settings/utils/ldap.py:678 +#: settings/utils/ldap.py:681 msgid "Authentication failed (username or password incorrect): {}" msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません): {}" -#: settings/utils/ldap.py:680 +#: settings/utils/ldap.py:683 msgid "Authentication failed (Unknown): {}" msgstr "認証に失敗しました (不明): {}" -#: settings/utils/ldap.py:683 +#: settings/utils/ldap.py:686 msgid "Authentication success: {}" msgstr "認証成功: {}" @@ -6426,12 +6465,12 @@ msgid "Send verification code" msgstr "確認コードを送信" #: templates/_mfa_login_field.html:106 -#: users/templates/users/forgot_password.html:130 +#: users/templates/users/forgot_password.html:174 msgid "Wait: " msgstr "待つ:" #: templates/_mfa_login_field.html:116 -#: users/templates/users/forgot_password.html:146 +#: users/templates/users/forgot_password.html:190 msgid "The verification code has been sent" msgstr "確認コードが送信されました" @@ -6797,14 +6836,14 @@ msgid "SQLServer port" msgstr "SQLServer ポート" #: terminal/models/component/endpoint.py:30 -#: terminal/models/component/endpoint.py:103 +#: terminal/models/component/endpoint.py:117 #: terminal/serializers/endpoint.py:73 terminal/serializers/storage.py:41 #: terminal/serializers/storage.py:53 terminal/serializers/storage.py:83 #: terminal/serializers/storage.py:93 terminal/serializers/storage.py:101 msgid "Endpoint" msgstr "エンドポイント" -#: terminal/models/component/endpoint.py:109 +#: terminal/models/component/endpoint.py:123 msgid "Endpoint rule" msgstr "エンドポイントルール" @@ -6894,10 +6933,6 @@ msgstr "ログイン元" msgid "Replay" msgstr "リプレイ" -#: terminal/models/session/session.py:45 -msgid "Date end" -msgstr "終了日" - #: terminal/models/session/session.py:47 terminal/serializers/session.py:65 msgid "Command amount" msgstr "コマンド量" @@ -7430,54 +7465,66 @@ msgstr "応募者" msgid "App Tickets" msgstr "チケット" -#: tickets/const.py:9 +#: tickets/const.py:10 msgid "Apply for asset" msgstr "資産の申請" -#: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 +#: tickets/const.py:17 tickets/const.py:25 tickets/const.py:44 msgid "Open" msgstr "オープン" -#: tickets/const.py:18 tickets/const.py:31 +#: tickets/const.py:19 tickets/const.py:32 msgid "Reopen" msgstr "再オープン" -#: tickets/const.py:19 tickets/const.py:32 +#: tickets/const.py:20 tickets/const.py:33 msgid "Approved" msgstr "承認済み" -#: tickets/const.py:20 tickets/const.py:33 +#: tickets/const.py:21 tickets/const.py:34 msgid "Rejected" msgstr "拒否" -#: tickets/const.py:30 tickets/const.py:38 +#: tickets/const.py:31 tickets/const.py:39 msgid "Closed" msgstr "クローズ" -#: tickets/const.py:50 +#: tickets/const.py:51 msgid "One level" msgstr "1つのレベル" -#: tickets/const.py:51 +#: tickets/const.py:52 msgid "Two level" msgstr "2つのレベル" -#: tickets/const.py:55 +#: tickets/const.py:56 msgid "Org admin" msgstr "Org admin" -#: tickets/const.py:56 +#: tickets/const.py:57 msgid "Custom user" msgstr "カスタムユーザー" -#: tickets/const.py:57 +#: tickets/const.py:58 msgid "Super admin" msgstr "スーパー管理者" -#: tickets/const.py:58 +#: tickets/const.py:59 msgid "Super admin and org admin" msgstr "スーパーadminとorg admin" +#: tickets/const.py:63 +msgid "All assets" +msgstr "すべての資産" + +#: tickets/const.py:64 +msgid "Permed assets" +msgstr "許可された資産" + +#: tickets/const.py:65 +msgid "Permed valid assets" +msgstr "有効な許可を受けた資産" + #: tickets/errors.py:9 msgid "Ticket already closed" msgstr "チケットはすでに閉じています" @@ -7994,7 +8041,7 @@ msgstr "ユーザーパスワード履歴" msgid "Reset password" msgstr "パスワードのリセット" -#: users/notifications.py:85 users/views/profile/reset.py:230 +#: users/notifications.py:85 users/views/profile/reset.py:231 msgid "Reset password success" msgstr "パスワードのリセット成功" @@ -8189,6 +8236,10 @@ msgstr "" "管理者は「既存のユーザーのみログインを許可」をオンにしており、現在のユーザー" "はユーザーリストにありません。管理者に連絡してください。" +#: users/signal_handlers.py:166 +msgid "Clean up expired user sessions" +msgstr "期限切れのユーザー・セッションのパージ" + #: users/tasks.py:25 msgid "Check password expired" msgstr "パスワードの有効期限が切れていることを確認する" @@ -8267,28 +8318,28 @@ msgstr "あなたのssh公開鍵はサイト管理者によってリセットさ msgid "click here to set your password" msgstr "ここをクリックしてパスワードを設定してください" -#: users/templates/users/forgot_password.html:32 +#: users/templates/users/forgot_password.html:46 msgid "Input your email account, that will send a email to your" msgstr "あなたのメールを入力し、それはあなたにメールを送信します" -#: users/templates/users/forgot_password.html:35 +#: users/templates/users/forgot_password.html:49 msgid "" "Enter your mobile number and a verification code will be sent to your phone" msgstr "携帯電話番号を入力すると、認証コードが携帯電話に送信されます" -#: users/templates/users/forgot_password.html:57 +#: users/templates/users/forgot_password.html:71 msgid "Email account" msgstr "メールアドレス" -#: users/templates/users/forgot_password.html:61 +#: users/templates/users/forgot_password.html:92 msgid "Mobile number" msgstr "携帯番号" -#: users/templates/users/forgot_password.html:69 +#: users/templates/users/forgot_password.html:100 msgid "Send" msgstr "送信" -#: users/templates/users/forgot_password.html:73 +#: users/templates/users/forgot_password.html:104 #: users/templates/users/forgot_password_previewing.html:30 msgid "Submit" msgstr "送信" @@ -8382,7 +8433,7 @@ msgstr "Androidのダウンロード" msgid "iPhone downloads" msgstr "IPhoneのダウンロード" -#: users/templates/users/user_otp_enable_install_app.html:26 +#: users/templates/users/user_otp_enable_install_app.html:27 msgid "" "After installation, click the next step to enter the binding page (if " "installed, go to the next step directly)." @@ -8410,32 +8461,32 @@ msgstr "" msgid "Open MFA Authenticator and enter the 6-bit dynamic code" msgstr "MFA Authenticatorを開き、6ビットの動的コードを入力します" -#: users/views/profile/otp.py:85 +#: users/views/profile/otp.py:106 msgid "Already bound" msgstr "すでにバインド済み" -#: users/views/profile/otp.py:86 +#: users/views/profile/otp.py:107 msgid "MFA already bound, disable first, then bound" msgstr "" "MFAはすでにバインドされており、最初に無効にしてからバインドされています。" -#: users/views/profile/otp.py:113 +#: users/views/profile/otp.py:134 msgid "OTP enable success" msgstr "OTP有効化成功" -#: users/views/profile/otp.py:114 +#: users/views/profile/otp.py:135 msgid "OTP enable success, return login page" msgstr "OTP有効化成功、ログインページを返す" -#: users/views/profile/otp.py:156 +#: users/views/profile/otp.py:177 msgid "Disable OTP" msgstr "OTPの無効化" -#: users/views/profile/otp.py:162 +#: users/views/profile/otp.py:183 msgid "OTP disable success" msgstr "OTP無効化成功" -#: users/views/profile/otp.py:163 +#: users/views/profile/otp.py:184 msgid "OTP disable success, return login page" msgstr "OTP無効化成功、ログインページを返す" @@ -8443,7 +8494,7 @@ msgstr "OTP無効化成功、ログインページを返す" msgid "Password invalid" msgstr "パスワード無効" -#: users/views/profile/reset.py:65 +#: users/views/profile/reset.py:66 msgid "" "Non-local users can log in only from third-party platforms and cannot change " "their passwords: {}" @@ -8451,23 +8502,23 @@ msgstr "" "ローカル以外のユーザーは、サードパーティ プラットフォームからのログインのみが" "許可され、パスワードの変更はサポートされていません: {}" -#: users/views/profile/reset.py:185 users/views/profile/reset.py:196 +#: users/views/profile/reset.py:186 users/views/profile/reset.py:197 msgid "Token invalid or expired" msgstr "トークンが無効または期限切れ" -#: users/views/profile/reset.py:201 +#: users/views/profile/reset.py:202 msgid "User auth from {}, go there change password" msgstr "ユーザー認証ソース {}, 対応するシステムにパスワードを変更してください" -#: users/views/profile/reset.py:208 +#: users/views/profile/reset.py:209 msgid "* Your password does not meet the requirements" msgstr "* パスワードが要件を満たしていない" -#: users/views/profile/reset.py:214 +#: users/views/profile/reset.py:215 msgid "* The new password cannot be the last {} passwords" msgstr "* 新しいパスワードを最後の {} パスワードにすることはできません" -#: users/views/profile/reset.py:231 +#: users/views/profile/reset.py:232 msgid "Reset password success, return to login page" msgstr "パスワードの成功をリセットし、ログインページに戻る" @@ -9179,30 +9230,9 @@ msgstr "エンタープライズプロフェッショナル版" msgid "Ultimate edition" msgstr "エンタープライズ・フラッグシップ・エディション" -#~ msgid "Applications" -#~ msgstr "アプリケーション" - -#~ msgid "Cycle perform" -#~ msgstr "サイクル実行" - -#~ msgid "Regularly perform" -#~ msgstr "定期的に実行する" - #~ msgid "SMTP port" #~ msgstr "SMTPポート" -#~ msgid "SMTP account" -#~ msgstr "SMTPアカウント" - -#~ msgid "SMTP password" -#~ msgstr "SMTPパスワード" - -#~ msgid "Select users" -#~ msgstr "ユーザーの選択" - -#~ msgid "For security, only list several users" -#~ msgstr "セキュリティのために、複数のユーザーのみをリストします" - #~ msgid "Password can not contains `{{` or `}}`" #~ msgstr "パスワードには `{` または `}` 文字を含めることはできません" diff --git a/apps/i18n/core/zh/LC_MESSAGES/django.po b/apps/i18n/core/zh/LC_MESSAGES/django.po index de9800978..65fb79f98 100644 --- a/apps/i18n/core/zh/LC_MESSAGES/django.po +++ b/apps/i18n/core/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-02 11:06+0800\n" +"POT-Creation-Date: 2024-02-04 19:41+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -21,6 +21,11 @@ msgstr "" msgid "The parameter 'action' must be [{}]" msgstr "参数 'action' 必须是 [{}]" +#: accounts/automations/change_secret/manager.py:197 +#, python-format +msgid "Success: %s, Failed: %s, Total: %s" +msgstr "成功: %s, 失败: %s, 总数: %s" + #: accounts/const/account.py:6 #: accounts/serializers/automations/change_secret.py:32 #: assets/models/_user.py:24 audits/signal_handlers/login_log.py:34 @@ -202,8 +207,8 @@ msgstr "仅创建" #: notifications/backends/__init__.py:10 settings/serializers/msg.py:22 #: settings/serializers/msg.py:64 users/forms/profile.py:102 #: users/forms/profile.py:109 users/models/user.py:802 -#: users/templates/users/forgot_password.html:117 -#: users/views/profile/reset.py:93 +#: users/templates/users/forgot_password.html:160 +#: users/views/profile/reset.py:94 msgid "Email" msgstr "邮箱" @@ -359,10 +364,11 @@ msgstr "账号备份计划" #: accounts/models/automations/backup_account.py:119 #: assets/models/automations/base.py:115 audits/models.py:65 -#: ops/models/base.py:55 ops/models/celery.py:65 ops/models/job.py:235 +#: ops/models/base.py:55 ops/models/celery.py:86 ops/models/job.py:236 #: ops/templates/ops/celery_task_log.html:75 -#: perms/models/asset_permission.py:78 terminal/models/applet/host.py:141 -#: terminal/models/session/session.py:44 +#: perms/models/asset_permission.py:78 +#: settings/templates/ldap/_msg_import_ldap_user.html:5 +#: terminal/models/applet/host.py:141 terminal/models/session/session.py:44 #: tickets/models/ticket/apply_application.py:30 #: tickets/models/ticket/apply_asset.py:19 msgid "Date start" @@ -371,6 +377,7 @@ msgstr "开始日期" #: accounts/models/automations/backup_account.py:122 #: authentication/templates/authentication/_msg_oauth_bind.html:11 #: notifications/notifications.py:186 +#: settings/templates/ldap/_msg_import_ldap_user.html:3 msgid "Time" msgstr "时间" @@ -380,7 +387,7 @@ msgstr "账号备份快照" #: accounts/models/automations/backup_account.py:130 #: accounts/serializers/account/backup.py:48 -#: accounts/serializers/automations/base.py:54 +#: accounts/serializers/automations/base.py:55 #: assets/models/automations/base.py:122 #: assets/serializers/automations/base.py:39 msgid "Trigger mode" @@ -446,6 +453,7 @@ msgstr "SSH 密钥推送方式" #: accounts/models/automations/gather_account.py:58 #: accounts/serializers/account/backup.py:40 #: accounts/serializers/automations/change_secret.py:56 +#: settings/serializers/auth/ldap.py:81 msgid "Recipient" msgstr "收件人" @@ -467,14 +475,14 @@ msgstr "开始日期" #: accounts/models/automations/change_secret.py:42 #: assets/models/automations/base.py:116 ops/models/base.py:56 -#: ops/models/celery.py:66 ops/models/job.py:236 +#: ops/models/celery.py:87 ops/models/job.py:237 #: terminal/models/applet/host.py:142 msgid "Date finished" msgstr "结束日期" #: accounts/models/automations/change_secret.py:43 #: assets/models/automations/base.py:113 audits/models.py:208 -#: audits/serializers.py:54 ops/models/base.py:49 ops/models/job.py:227 +#: audits/serializers.py:54 ops/models/base.py:49 ops/models/job.py:228 #: terminal/models/applet/applet.py:320 terminal/models/applet/host.py:140 #: terminal/models/component/status.py:30 #: terminal/models/virtualapp/virtualapp.py:99 @@ -491,6 +499,7 @@ msgstr "状态" #: authentication/templates/authentication/passkey.html:173 #: authentication/views/base.py:42 authentication/views/base.py:43 #: authentication/views/base.py:44 common/const/choices.py:20 +#: settings/templates/ldap/_msg_import_ldap_user.html:26 msgid "Error" msgstr "错误" @@ -594,17 +603,17 @@ msgstr "密码规则" #: assets/models/domain.py:19 assets/models/group.py:17 #: assets/models/label.py:18 assets/models/platform.py:16 #: assets/models/platform.py:95 assets/serializers/asset/common.py:149 -#: assets/serializers/platform.py:118 assets/serializers/platform.py:229 +#: assets/serializers/platform.py:118 assets/serializers/platform.py:228 #: authentication/backends/passkey/models.py:10 #: authentication/serializers/connect_token_secret.py:113 #: authentication/serializers/connect_token_secret.py:168 labels/models.py:11 #: ops/mixin.py:21 ops/models/adhoc.py:20 ops/models/celery.py:15 -#: ops/models/celery.py:59 ops/models/job.py:136 ops/models/playbook.py:28 +#: ops/models/celery.py:80 ops/models/job.py:137 ops/models/playbook.py:28 #: ops/serializers/job.py:18 orgs/models.py:82 #: perms/models/asset_permission.py:61 rbac/models/role.py:29 #: settings/models.py:33 settings/models.py:181 settings/serializers/msg.py:89 #: terminal/models/applet/applet.py:33 terminal/models/component/endpoint.py:12 -#: terminal/models/component/endpoint.py:95 +#: terminal/models/component/endpoint.py:109 #: terminal/models/component/storage.py:26 terminal/models/component/task.py:13 #: terminal/models/component/terminal.py:84 #: terminal/models/virtualapp/provider.py:10 @@ -625,7 +634,7 @@ msgstr "特权账号" #: assets/models/label.py:22 #: authentication/serializers/connect_token_secret.py:117 #: terminal/models/applet/applet.py:40 -#: terminal/models/component/endpoint.py:106 +#: terminal/models/component/endpoint.py:120 #: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:173 msgid "Is active" msgstr "激活" @@ -708,22 +717,22 @@ msgstr "" msgid "Notification of implementation result of encryption change plan" msgstr "改密计划任务结果通知" -#: accounts/notifications.py:65 +#: accounts/notifications.py:66 msgid "" "{} - The encryption change task has been completed. See the attachment for " "details" msgstr "{} - 改密任务已完成, 详情见附件" -#: accounts/notifications.py:68 +#: accounts/notifications.py:70 msgid "" "{} - The encryption change task has been completed: the encryption password " -"has not been set - please go to personal information -> file encryption " -"password to set the encryption password" +"has not been set - please go to personal information -> set encryption " +"password in preferences" msgstr "" -"{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加" -"密密码" +"{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 偏好设置中设置加密密" +"码" -#: accounts/notifications.py:79 +#: accounts/notifications.py:82 msgid "Gather account change information" msgstr "账号变更信息" @@ -742,21 +751,21 @@ msgstr "账号存在策略" #: accounts/serializers/account/account.py:195 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:96 #: assets/serializers/asset/common.py:125 assets/serializers/cagegory.py:12 -#: assets/serializers/platform.py:140 assets/serializers/platform.py:230 +#: assets/serializers/platform.py:140 assets/serializers/platform.py:229 #: perms/serializers/user_permission.py:26 settings/models.py:35 #: tickets/models/ticket/apply_application.py:13 users/models/preference.py:12 msgid "Category" msgstr "类别" #: accounts/serializers/account/account.py:196 -#: accounts/serializers/automations/base.py:53 acls/models/command_acl.py:24 +#: accounts/serializers/automations/base.py:54 acls/models/command_acl.py:24 #: acls/serializers/command_acl.py:19 applications/models.py:14 #: assets/models/_user.py:50 assets/models/automations/base.py:20 #: assets/models/cmd_filter.py:74 assets/models/platform.py:97 #: assets/serializers/asset/common.py:126 assets/serializers/platform.py:120 #: assets/serializers/platform.py:139 audits/serializers.py:53 #: audits/serializers.py:170 -#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:144 +#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:145 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:39 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:146 terminal/serializers/applet.py:29 @@ -776,7 +785,7 @@ msgstr "资产不存在" msgid "Has secret" msgstr "已托管密码" -#: accounts/serializers/account/account.py:261 ops/models/celery.py:62 +#: accounts/serializers/account/account.py:261 ops/models/celery.py:83 #: tickets/models/comment.py:13 tickets/models/ticket/general.py:45 #: tickets/models/ticket/general.py:279 tickets/serializers/super_ticket.py:14 #: tickets/serializers/ticket/ticket.py:21 @@ -793,7 +802,7 @@ msgstr "已修改" #: assets/models/automations/base.py:19 #: assets/serializers/automations/base.py:20 assets/serializers/domain.py:30 #: authentication/api/connection_token.py:404 ops/models/base.py:17 -#: ops/models/job.py:146 ops/serializers/job.py:19 +#: ops/models/job.py:147 ops/serializers/job.py:19 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 msgid "Assets" msgstr "资产" @@ -846,7 +855,7 @@ msgid "Date" msgstr "日期" #: accounts/serializers/account/backup.py:38 -#: accounts/serializers/automations/base.py:36 +#: accounts/serializers/automations/base.py:37 msgid "Executed amount" msgstr "执行次数" @@ -864,7 +873,7 @@ msgid "Key password" msgstr "密钥密码" #: accounts/serializers/account/base.py:78 -#: assets/serializers/asset/common.py:381 +#: assets/serializers/asset/common.py:384 msgid "Spec info" msgstr "特殊信息" @@ -917,11 +926,11 @@ msgstr "关联平台,可配置推送参数,如果不关联,将使用默认 #: accounts/serializers/account/virtual.py:19 assets/models/_user.py:27 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/group.py:20 common/db/models.py:36 ops/models/adhoc.py:26 -#: ops/models/job.py:152 ops/models/playbook.py:31 rbac/models/role.py:37 +#: ops/models/job.py:153 ops/models/playbook.py:31 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:45 #: terminal/models/applet/applet.py:321 terminal/models/applet/host.py:143 #: terminal/models/component/endpoint.py:25 -#: terminal/models/component/endpoint.py:105 +#: terminal/models/component/endpoint.py:119 #: terminal/models/session/session.py:46 #: terminal/models/virtualapp/virtualapp.py:28 tickets/models/comment.py:32 #: tickets/models/ticket/general.py:297 users/models/user.py:836 @@ -944,11 +953,11 @@ msgstr "" msgid "Nodes" msgstr "节点" -#: accounts/serializers/automations/base.py:43 +#: accounts/serializers/automations/base.py:44 msgid "Name already exists" msgstr "名称已存在" -#: accounts/serializers/automations/base.py:52 +#: accounts/serializers/automations/base.py:53 #: assets/models/automations/base.py:118 #: assets/serializers/automations/base.py:38 msgid "Automation snapshot" @@ -979,17 +988,17 @@ msgstr "自动化任务执行历史" msgid "Success" msgstr "成功" -#: accounts/signal_handlers.py:46 +#: accounts/signal_handlers.py:47 #, python-format msgid "Push related accounts to assets: %s, by system" msgstr "推送账号到资产: %s, 由系统执行" -#: accounts/signal_handlers.py:55 +#: accounts/signal_handlers.py:56 #, python-format msgid "Add account: %s" msgstr "添加账号: %s" -#: accounts/signal_handlers.py:57 +#: accounts/signal_handlers.py:58 #, python-format msgid "Delete account: %s" msgstr "删除账号: %s" @@ -1060,7 +1069,7 @@ msgstr "密钥不合法或密钥密码错误" msgid "App Acls" msgstr "访问控制" -#: acls/const.py:6 audits/const.py:36 terminal/const.py:11 tickets/const.py:45 +#: acls/const.py:6 audits/const.py:36 terminal/const.py:11 tickets/const.py:46 #: tickets/templates/tickets/approve_check_password.html:47 msgid "Reject" msgstr "拒绝" @@ -1082,13 +1091,13 @@ msgid "Notifications" msgstr "通知" #: acls/models/base.py:37 assets/models/_user.py:51 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:98 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:112 #: xpack/plugins/cloud/models.py:282 msgid "Priority" msgstr "优先级" #: acls/models/base.py:38 assets/models/_user.py:51 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:99 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:113 #: xpack/plugins/cloud/models.py:283 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -1102,7 +1111,7 @@ msgstr "审批人" #: authentication/models/connection_token.py:53 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:82 terminal/models/session/sharing.py:29 -#: tickets/const.py:37 +#: tickets/const.py:38 msgid "Active" msgstr "激活中" @@ -1161,7 +1170,7 @@ msgstr "生成的正则表达式有误" msgid "Command acl" msgstr "命令过滤" -#: acls/models/command_acl.py:112 tickets/const.py:11 +#: acls/models/command_acl.py:112 tickets/const.py:12 msgid "Command confirm" msgstr "命令复核" @@ -1182,7 +1191,7 @@ msgstr "规则" msgid "Login acl" msgstr "登录访问控制" -#: acls/models/login_acl.py:27 tickets/const.py:10 +#: acls/models/login_acl.py:27 tickets/const.py:11 msgid "Login confirm" msgstr "登录复核" @@ -1190,7 +1199,7 @@ msgstr "登录复核" msgid "Login asset acl" msgstr "登录资产访问控制" -#: acls/models/login_asset_acl.py:22 tickets/const.py:12 +#: acls/models/login_asset_acl.py:22 tickets/const.py:13 msgid "Login asset confirm" msgstr "登录资产复核" @@ -1313,7 +1322,7 @@ msgstr "应用程序" msgid "Can match application" msgstr "匹配应用" -#: assets/api/asset/asset.py:179 +#: assets/api/asset/asset.py:180 msgid "Cannot create asset directly, you should create a host or other" msgstr "不能直接创建资产, 你应该创建主机或其他资产" @@ -1451,7 +1460,7 @@ msgid "Kubernetes" msgstr "Kubernetes" #: assets/const/device.py:7 terminal/models/applet/applet.py:26 -#: tickets/const.py:8 +#: tickets/const.py:9 msgid "General" msgstr "一般" @@ -1616,7 +1625,7 @@ msgstr "SSH公钥" #: assets/models/_user.py:28 assets/models/automations/base.py:114 #: assets/models/cmd_filter.py:41 assets/models/group.py:19 #: audits/models.py:267 common/db/models.py:34 ops/models/base.py:54 -#: ops/models/job.py:234 users/models/user.py:1042 +#: ops/models/job.py:235 users/models/user.py:1042 msgid "Date created" msgstr "创建日期" @@ -1730,13 +1739,13 @@ msgid "Domain" msgstr "网域" #: assets/models/asset/common.py:165 assets/models/automations/base.py:18 -#: assets/models/cmd_filter.py:32 assets/models/node.py:546 +#: assets/models/cmd_filter.py:32 assets/models/node.py:549 #: perms/models/asset_permission.py:72 perms/serializers/permission.py:37 #: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:330 msgid "Node" msgstr "节点" -#: assets/models/asset/common.py:167 assets/serializers/asset/common.py:382 +#: assets/models/asset/common.py:167 assets/serializers/asset/common.py:385 #: assets/serializers/asset/host.py:11 msgid "Gathered info" msgstr "收集资产硬件信息" @@ -1785,7 +1794,7 @@ msgstr "忽略证书校验" msgid "Proxy" msgstr "代理" -#: assets/models/automations/base.py:22 ops/models/job.py:230 +#: assets/models/automations/base.py:22 ops/models/job.py:231 #: settings/serializers/auth/sms.py:103 msgid "Parameters" msgstr "参数" @@ -1870,7 +1879,7 @@ msgstr "默认资产组" msgid "System" msgstr "系统" -#: assets/models/label.py:19 assets/models/node.py:532 +#: assets/models/label.py:19 assets/models/node.py:535 #: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18 #: assets/serializers/cagegory.py:24 #: authentication/models/connection_token.py:29 @@ -1893,23 +1902,23 @@ msgstr "标签" msgid "New node" msgstr "新节点" -#: assets/models/node.py:460 audits/backends/db.py:65 audits/backends/db.py:66 +#: assets/models/node.py:463 audits/backends/db.py:65 audits/backends/db.py:66 msgid "empty" msgstr "空" -#: assets/models/node.py:531 perms/models/perm_node.py:28 +#: assets/models/node.py:534 perms/models/perm_node.py:28 msgid "Key" msgstr "键" -#: assets/models/node.py:533 assets/serializers/node.py:20 +#: assets/models/node.py:536 assets/serializers/node.py:20 msgid "Full value" msgstr "全称" -#: assets/models/node.py:537 perms/models/perm_node.py:30 +#: assets/models/node.py:540 perms/models/perm_node.py:30 msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:549 +#: assets/models/node.py:552 msgid "Can match node" msgstr "可以匹配节点" @@ -2069,23 +2078,23 @@ msgid "Node path" msgstr "节点路径" #: assets/serializers/asset/common.py:148 -#: assets/serializers/asset/common.py:383 +#: assets/serializers/asset/common.py:386 msgid "Auto info" msgstr "自动化信息" -#: assets/serializers/asset/common.py:242 +#: assets/serializers/asset/common.py:245 msgid "Platform not exist" msgstr "平台不存在" -#: assets/serializers/asset/common.py:278 +#: assets/serializers/asset/common.py:281 msgid "port out of range (0-65535)" msgstr "端口超出范围 (0-65535)" -#: assets/serializers/asset/common.py:285 +#: assets/serializers/asset/common.py:288 msgid "Protocol is required: {}" msgstr "协议是必填的: {}" -#: assets/serializers/asset/common.py:313 +#: assets/serializers/asset/common.py:316 msgid "Invalid data" msgstr "无效的数据" @@ -2236,7 +2245,7 @@ msgstr "默认网域" msgid "type is required" msgstr "类型 该字段是必填项。" -#: assets/serializers/platform.py:205 +#: assets/serializers/platform.py:204 msgid "Protocols is required" msgstr "协议是必填的" @@ -2378,14 +2387,14 @@ msgstr "登录" msgid "Change password" msgstr "改密" -#: audits/const.py:37 tickets/const.py:46 +#: audits/const.py:37 tickets/const.py:47 msgid "Approve" msgstr "同意" #: audits/const.py:38 #: authentication/templates/authentication/_access_key_modal.html:155 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: templates/_modal.html:22 tickets/const.py:44 +#: templates/_modal.html:22 tickets/const.py:45 msgid "Close" msgstr "关闭" @@ -2530,16 +2539,16 @@ msgstr "用户登录日志" msgid "Session key" msgstr "会话标识" -#: audits/models.py:306 +#: audits/models.py:305 msgid "User session" msgstr "用户会话" -#: audits/models.py:308 +#: audits/models.py:307 msgid "Offline user session" msgstr "下线用户会话" #: audits/serializers.py:33 ops/models/adhoc.py:25 ops/models/base.py:16 -#: ops/models/base.py:53 ops/models/job.py:145 ops/models/job.py:233 +#: ops/models/base.py:53 ops/models/job.py:146 ops/models/job.py:234 #: ops/models/playbook.py:30 terminal/models/session/sharing.py:25 msgid "Creator" msgstr "创建者" @@ -2609,7 +2618,7 @@ msgstr "飞书" msgid "Slack" msgstr "" -#: audits/signal_handlers/login_log.py:40 authentication/views/dingtalk.py:160 +#: audits/signal_handlers/login_log.py:40 authentication/views/dingtalk.py:161 #: authentication/views/login.py:83 notifications/backends/__init__.py:12 #: settings/serializers/auth/dingtalk.py:10 users/models/user.py:750 #: users/models/user.py:856 @@ -2626,9 +2635,9 @@ msgstr "临时密码" msgid "Passkey" msgstr "Passkey" -#: audits/tasks.py:101 users/signal_handlers.py:166 +#: audits/tasks.py:101 msgid "Clean audits session task log" -msgstr "清理审计会话任务日志" +msgstr "清理资产审计会话任务日志" #: audits/tasks.py:114 msgid "Upload FTP file to external storage" @@ -2676,11 +2685,11 @@ msgid "Current user not support mfa type: {}" msgstr "当前用户不支持 MFA 类型: {}" #: authentication/api/password.py:33 terminal/api/session/session.py:305 -#: users/views/profile/reset.py:62 +#: users/views/profile/reset.py:63 msgid "User does not exist: {}" msgstr "用户不存在: {}" -#: authentication/api/password.py:33 users/views/profile/reset.py:163 +#: authentication/api/password.py:33 users/views/profile/reset.py:164 msgid "No user matched" msgstr "没有匹配到用户" @@ -2692,8 +2701,8 @@ msgstr "用户来自 {} 请去相应系统修改密码" #: authentication/api/password.py:65 #: authentication/templates/authentication/login.html:361 -#: users/templates/users/forgot_password.html:27 -#: users/templates/users/forgot_password.html:28 +#: users/templates/users/forgot_password.html:41 +#: users/templates/users/forgot_password.html:42 #: users/templates/users/forgot_password_previewing.html:13 #: users/templates/users/forgot_password_previewing.html:14 msgid "Forgot password" @@ -2704,24 +2713,24 @@ msgid "App Authentication" msgstr "认证管理" #: authentication/backends/custom.py:59 -#: authentication/backends/oauth2/backends.py:170 +#: authentication/backends/oauth2/backends.py:173 msgid "User invalid, disabled or expired" msgstr "用户无效,已禁用或已过期" -#: authentication/backends/drf.py:50 +#: authentication/backends/drf.py:52 msgid "Invalid token header. No credentials provided." msgstr "无效的令牌头。没有提供任何凭据。" -#: authentication/backends/drf.py:53 +#: authentication/backends/drf.py:55 msgid "Invalid token header. Sign string should not contain spaces." msgstr "无效的令牌头。符号字符串不应包含空格。" -#: authentication/backends/drf.py:59 +#: authentication/backends/drf.py:61 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "无效的令牌头。符号字符串不应包含无效字符。" -#: authentication/backends/drf.py:72 +#: authentication/backends/drf.py:74 msgid "Invalid token or cache refreshed." msgstr "刷新的令牌或缓存无效。" @@ -2890,8 +2899,8 @@ msgstr "企业微信已经绑定" msgid "WeCom is not bound" msgstr "没有绑定企业微信" -#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:212 -#: authentication/views/dingtalk.py:254 +#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:213 +#: authentication/views/dingtalk.py:255 msgid "DingTalk is not bound" msgstr "钉钉没有绑定" @@ -3000,8 +3009,8 @@ msgstr "短信验证码校验失败" #: authentication/mfa/sms.py:12 authentication/serializers/password_mfa.py:16 #: authentication/serializers/password_mfa.py:24 #: settings/serializers/auth/sms.py:32 users/forms/profile.py:104 -#: users/forms/profile.py:109 users/templates/users/forgot_password.html:112 -#: users/views/profile/reset.py:99 +#: users/forms/profile.py:109 users/templates/users/forgot_password.html:155 +#: users/views/profile/reset.py:100 msgid "SMS" msgstr "短信" @@ -3017,7 +3026,7 @@ msgstr "设置手机号码启用" msgid "Clear phone number to disable" msgstr "清空手机号码禁用" -#: authentication/middleware.py:94 settings/utils/ldap.py:676 +#: authentication/middleware.py:94 settings/utils/ldap.py:679 msgid "Authentication failed (before login check failed): {}" msgstr "认证失败 (登录前检查失败): {}" @@ -3040,7 +3049,7 @@ msgid "Please change your password" msgstr "请修改密码" #: authentication/models/access_key.py:22 -#: terminal/models/component/endpoint.py:96 +#: terminal/models/component/endpoint.py:110 msgid "IP group" msgstr "IPグループ" @@ -3197,7 +3206,7 @@ msgid "Is expired" msgstr "已过期" #: authentication/serializers/password_mfa.py:29 -#: users/templates/users/forgot_password.html:108 +#: users/templates/users/forgot_password.html:151 msgid "The {} cannot be empty" msgstr "{} 不能为空" @@ -3277,7 +3286,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password_code.html:9 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:459 +#: jumpserver/conf.py:460 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:32 @@ -3333,7 +3342,7 @@ msgstr "重新申请" #: authentication/templates/authentication/_msg_reset_password_code.html:12 #: terminal/models/session/sharing.py:27 terminal/models/session/sharing.py:97 #: terminal/templates/terminal/_msg_session_sharing.html:12 -#: users/forms/profile.py:107 users/templates/users/forgot_password.html:66 +#: users/forms/profile.py:107 users/templates/users/forgot_password.html:97 msgid "Verify code" msgstr "验证码" @@ -3377,7 +3386,7 @@ msgid "" msgstr "如果这次公钥更新不是由你发起的,那么你的账号可能存在安全问题" #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 -#: templates/flash_message_standalone.html:28 tickets/const.py:17 +#: templates/flash_message_standalone.html:28 tickets/const.py:18 msgid "Cancel" msgstr "取消" @@ -3402,7 +3411,7 @@ msgstr "MFA 多因子认证" #: authentication/templates/authentication/login_mfa.html:19 #: users/templates/users/user_otp_check_password.html:12 #: users/templates/users/user_otp_enable_bind.html:24 -#: users/templates/users/user_otp_enable_install_app.html:29 +#: users/templates/users/user_otp_enable_install_app.html:31 #: users/templates/users/user_verify_mfa.html:30 msgid "Next" msgstr "下一步" @@ -3466,7 +3475,7 @@ msgstr "绑定 %s 成功" msgid "DingTalk Error, Please contact your system administrator" msgstr "钉钉错误,请联系系统管理员" -#: authentication/views/dingtalk.py:45 authentication/views/dingtalk.py:211 +#: authentication/views/dingtalk.py:45 authentication/views/dingtalk.py:212 msgid "DingTalk Error" msgstr "钉钉错误" @@ -3480,27 +3489,27 @@ msgstr "企业配置错误,请联系系统管理员" msgid "DingTalk is already bound" msgstr "钉钉已经绑定" -#: authentication/views/dingtalk.py:129 +#: authentication/views/dingtalk.py:130 msgid "Invalid user_id" msgstr "无效的 user_id" -#: authentication/views/dingtalk.py:145 +#: authentication/views/dingtalk.py:146 msgid "DingTalk query user failed" msgstr "钉钉查询用户失败" -#: authentication/views/dingtalk.py:154 +#: authentication/views/dingtalk.py:155 msgid "The DingTalk is already bound to another user" msgstr "该钉钉已经绑定其他用户" -#: authentication/views/dingtalk.py:161 +#: authentication/views/dingtalk.py:162 msgid "Binding DingTalk successfully" msgstr "绑定 钉钉 成功" -#: authentication/views/dingtalk.py:213 authentication/views/dingtalk.py:248 +#: authentication/views/dingtalk.py:214 authentication/views/dingtalk.py:249 msgid "Failed to get user from DingTalk" msgstr "从钉钉获取用户失败" -#: authentication/views/dingtalk.py:255 +#: authentication/views/dingtalk.py:256 msgid "Please login with a password and then bind the DingTalk" msgstr "请使用密码登录,然后绑定钉钉" @@ -3600,8 +3609,8 @@ msgstr "定时触发" msgid "Ready" msgstr "准备" -#: common/const/choices.py:16 terminal/const.py:77 tickets/const.py:29 -#: tickets/const.py:39 +#: common/const/choices.py:16 terminal/const.py:77 tickets/const.py:30 +#: tickets/const.py:40 msgid "Pending" msgstr "待定的" @@ -3655,7 +3664,7 @@ msgstr "编码数据为 text" msgid "Encrypt field using Secret Key" msgstr "加密的字段" -#: common/db/fields.py:573 +#: common/db/fields.py:580 msgid "" "Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or " "{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', " @@ -3665,15 +3674,15 @@ msgstr "" "{'type': 'attrs', 'attrs': [{'name': 'ip', 'match': 'exact', 'value': " "'1.1.1.1'}}" -#: common/db/fields.py:580 +#: common/db/fields.py:587 msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\"" msgstr "无效类型,应为 all、ids 或 attrs" -#: common/db/fields.py:583 +#: common/db/fields.py:590 msgid "Invalid ids for ids, should be a list" msgstr "无效的ID,应为列表" -#: common/db/fields.py:585 common/db/fields.py:590 +#: common/db/fields.py:592 common/db/fields.py:597 #: common/serializers/fields.py:133 tickets/serializers/ticket/common.py:58 #: xpack/plugins/cloud/serializers/account_attrs.py:56 #: xpack/plugins/cloud/serializers/account_attrs.py:79 @@ -3681,11 +3690,11 @@ msgstr "无效的ID,应为列表" msgid "This field is required." msgstr "该字段是必填项。" -#: common/db/fields.py:588 common/db/fields.py:593 +#: common/db/fields.py:595 common/db/fields.py:600 msgid "Invalid attrs, should be a list of dict" msgstr "无效的属性,应为dict列表" -#: common/db/fields.py:595 +#: common/db/fields.py:602 msgid "Invalid attrs, should be has name and value" msgstr "无效属性,应具有名称和值" @@ -3867,6 +3876,8 @@ msgstr "无效选项: {}" msgid "Labels" msgstr "标签" +# msgid "Labels" +# msgstr "标签管理" #: common/tasks.py:31 common/utils/verify_code.py:16 msgid "Send email" msgstr "发件邮件" @@ -3904,16 +3915,16 @@ msgstr "不能包含特殊字符" msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" -#: jumpserver/conf.py:453 +#: jumpserver/conf.py:454 #, python-brace-format msgid "The verification code is: {code}" msgstr "验证码为: {code}" -#: jumpserver/conf.py:458 +#: jumpserver/conf.py:459 msgid "Create account successfully" msgstr "创建账号成功" -#: jumpserver/conf.py:460 +#: jumpserver/conf.py:461 msgid "Your account has been created successfully" msgstr "你的账号已创建成功" @@ -4012,15 +4023,15 @@ msgstr "Ansible 已禁用" msgid "Skip hosts below:" msgstr "跳过以下主机: " -#: ops/api/celery.py:65 ops/api/celery.py:80 +#: ops/api/celery.py:66 ops/api/celery.py:81 msgid "Waiting task start" msgstr "等待任务开始" -#: ops/api/celery.py:203 +#: ops/api/celery.py:246 msgid "Task {} not found" msgstr "任务 {} 不存在" -#: ops/api/celery.py:208 +#: ops/api/celery.py:251 msgid "Task {} args or kwargs error" msgstr "任务 {} 执行参数错误" @@ -4102,7 +4113,7 @@ msgstr "VCS" msgid "Adhoc" msgstr "命令" -#: ops/const.py:39 ops/models/job.py:143 +#: ops/const.py:39 ops/models/job.py:144 msgid "Playbook" msgstr "Playbook" @@ -4192,11 +4203,11 @@ msgstr "需要周期或定期设置" msgid "Pattern" msgstr "模式" -#: ops/models/adhoc.py:23 ops/models/job.py:140 +#: ops/models/adhoc.py:23 ops/models/job.py:141 msgid "Module" msgstr "模块" -#: ops/models/adhoc.py:24 ops/models/celery.py:60 ops/models/job.py:138 +#: ops/models/adhoc.py:24 ops/models/celery.py:81 ops/models/job.py:139 #: terminal/models/component/task.py:14 msgid "Args" msgstr "参数" @@ -4215,12 +4226,12 @@ msgstr "最后执行" msgid "Date last run" msgstr "最后运行日期" -#: ops/models/base.py:51 ops/models/job.py:231 +#: ops/models/base.py:51 ops/models/job.py:232 #: xpack/plugins/cloud/models.py:202 msgid "Result" msgstr "结果" -#: ops/models/base.py:52 ops/models/job.py:232 +#: ops/models/base.py:52 ops/models/job.py:233 msgid "Summary" msgstr "汇总" @@ -4228,68 +4239,68 @@ msgstr "汇总" msgid "Date last publish" msgstr "发布日期" -#: ops/models/celery.py:49 +#: ops/models/celery.py:70 msgid "Celery Task" msgstr "Celery 任务" -#: ops/models/celery.py:52 +#: ops/models/celery.py:73 msgid "Can view task monitor" msgstr "可以查看任务监控" -#: ops/models/celery.py:61 terminal/models/component/task.py:15 +#: ops/models/celery.py:82 terminal/models/component/task.py:15 msgid "Kwargs" msgstr "其它参数" -#: ops/models/celery.py:63 terminal/models/session/sharing.py:128 -#: tickets/const.py:25 +#: ops/models/celery.py:84 terminal/models/session/sharing.py:128 +#: tickets/const.py:26 msgid "Finished" msgstr "结束" -#: ops/models/celery.py:64 +#: ops/models/celery.py:85 msgid "Date published" msgstr "发布日期" -#: ops/models/celery.py:89 +#: ops/models/celery.py:110 msgid "Celery Task Execution" msgstr "Celery 任务执行" -#: ops/models/job.py:141 +#: ops/models/job.py:142 msgid "Chdir" msgstr "运行目录" -#: ops/models/job.py:142 +#: ops/models/job.py:143 msgid "Timeout (Seconds)" msgstr "超时时间 (秒)" -#: ops/models/job.py:147 +#: ops/models/job.py:148 msgid "Use Parameter Define" msgstr "使用参数定义" -#: ops/models/job.py:148 +#: ops/models/job.py:149 msgid "Parameters define" msgstr "参数定义" -#: ops/models/job.py:149 +#: ops/models/job.py:150 msgid "Runas" msgstr "运行用户" -#: ops/models/job.py:151 +#: ops/models/job.py:152 msgid "Runas policy" msgstr "用户策略" -#: ops/models/job.py:215 +#: ops/models/job.py:216 msgid "Job" msgstr "作业" -#: ops/models/job.py:238 +#: ops/models/job.py:239 msgid "Material" msgstr "Material" -#: ops/models/job.py:240 +#: ops/models/job.py:241 msgid "Material Type" msgstr "Material 类型" -#: ops/models/job.py:557 +#: ops/models/job.py:558 msgid "Job Execution" msgstr "作业执行" @@ -4358,6 +4369,7 @@ msgid "Is finished" msgstr "是否完成" #: ops/serializers/job.py:69 +#: settings/templates/ldap/_msg_import_ldap_user.html:7 msgid "Time cost" msgstr "花费时间" @@ -4647,7 +4659,7 @@ msgstr "内部角色,不能删除" msgid "The role has been bound to users, can't be destroy" msgstr "角色已绑定用户,不能删除" -#: rbac/api/role.py:100 +#: rbac/api/role.py:102 msgid "Internal role, can't be update" msgstr "内部角色,不能更新" @@ -4958,6 +4970,12 @@ msgstr "其它设置" msgid "Chat prompt" msgstr "聊天提示" +#: settings/notifications.py:23 +#, fuzzy +#| msgid "Notification of account backup route task results" +msgid "Notification of Synchronized LDAP User Task Results" +msgstr "账号备份任务结果通知" + #: settings/serializers/auth/base.py:10 msgid "Authentication" msgstr "认证" @@ -5117,7 +5135,7 @@ msgstr "连接超时时间 (秒)" msgid "Search paged size (piece)" msgstr "搜索分页数量 (条)" -#: settings/serializers/auth/ldap.py:81 +#: settings/serializers/auth/ldap.py:84 msgid "Enable LDAP auth" msgstr "启用 LDAP 认证" @@ -6040,112 +6058,133 @@ msgstr "启用 Razor 服务" msgid "Enable SSH Client" msgstr "启用 SSH Client" -#: settings/tasks/ldap.py:24 +#: settings/tasks/ldap.py:28 msgid "Periodic import ldap user" msgstr "周期导入 LDAP 用户" -#: settings/tasks/ldap.py:45 +#: settings/tasks/ldap.py:66 msgid "Registration periodic import ldap user task" msgstr "注册周期导入 LDAP 用户 任务" -#: settings/utils/ldap.py:491 +#: settings/templates/ldap/_msg_import_ldap_user.html:2 +msgid "Sync task Finish" +msgstr "同步任务完成" + +#: settings/templates/ldap/_msg_import_ldap_user.html:6 +#: terminal/models/session/session.py:45 +msgid "Date end" +msgstr "结束日期" + +#: settings/templates/ldap/_msg_import_ldap_user.html:9 +msgid "Synced Organization" +msgstr "已同步组织" + +#: settings/templates/ldap/_msg_import_ldap_user.html:15 +msgid "Synced User" +msgstr "已同步用户" + +#: settings/templates/ldap/_msg_import_ldap_user.html:22 +msgid "No user synchronization required" +msgstr "没有用户需要同步" + +#: settings/utils/ldap.py:494 msgid "ldap:// or ldaps:// protocol is used." msgstr "使用 ldap:// 或 ldaps:// 协议" -#: settings/utils/ldap.py:502 +#: settings/utils/ldap.py:505 msgid "Host or port is disconnected: {}" msgstr "主机或端口不可连接: {}" -#: settings/utils/ldap.py:504 +#: settings/utils/ldap.py:507 msgid "The port is not the port of the LDAP service: {}" msgstr "端口不是LDAP服务端口: {}" -#: settings/utils/ldap.py:506 +#: settings/utils/ldap.py:509 msgid "Please add certificate: {}" msgstr "请添加证书" -#: settings/utils/ldap.py:510 settings/utils/ldap.py:537 -#: settings/utils/ldap.py:567 settings/utils/ldap.py:595 +#: settings/utils/ldap.py:513 settings/utils/ldap.py:540 +#: settings/utils/ldap.py:570 settings/utils/ldap.py:598 msgid "Unknown error: {}" msgstr "未知错误: {}" -#: settings/utils/ldap.py:524 +#: settings/utils/ldap.py:527 msgid "Bind DN or Password incorrect" msgstr "绑定DN或密码错误" -#: settings/utils/ldap.py:531 +#: settings/utils/ldap.py:534 msgid "Please enter Bind DN: {}" msgstr "请输入绑定DN: {}" -#: settings/utils/ldap.py:533 +#: settings/utils/ldap.py:536 msgid "Please enter Password: {}" msgstr "请输入密码: {}" -#: settings/utils/ldap.py:535 +#: settings/utils/ldap.py:538 msgid "Please enter correct Bind DN and Password: {}" msgstr "请输入正确的绑定DN和密码: {}" -#: settings/utils/ldap.py:553 +#: settings/utils/ldap.py:556 msgid "Invalid User OU or User search filter: {}" msgstr "不合法的用户OU或用户过滤器: {}" -#: settings/utils/ldap.py:584 +#: settings/utils/ldap.py:587 msgid "LDAP User attr map not include: {}" msgstr "LDAP属性映射没有包含: {}" -#: settings/utils/ldap.py:591 +#: settings/utils/ldap.py:594 msgid "LDAP User attr map is not dict" msgstr "LDAP属性映射不合法" -#: settings/utils/ldap.py:610 +#: settings/utils/ldap.py:613 msgid "LDAP authentication is not enabled" msgstr "LDAP认证没有启用" -#: settings/utils/ldap.py:628 +#: settings/utils/ldap.py:631 msgid "Error (Invalid LDAP server): {}" msgstr "错误 (不合法的LDAP服务器地址): {}" -#: settings/utils/ldap.py:630 +#: settings/utils/ldap.py:633 msgid "Error (Invalid Bind DN): {}" msgstr "错误 (不合法的绑定DN): {}" -#: settings/utils/ldap.py:632 +#: settings/utils/ldap.py:635 msgid "Error (Invalid LDAP User attr map): {}" msgstr "错误 (不合法的LDAP属性映射): {}" -#: settings/utils/ldap.py:634 +#: settings/utils/ldap.py:637 msgid "Error (Invalid User OU or User search filter): {}" msgstr "错误 (不合法的用户OU或用户过滤器): {}" -#: settings/utils/ldap.py:636 +#: settings/utils/ldap.py:639 msgid "Error (Not enabled LDAP authentication): {}" msgstr "错误 (没有启用LDAP认证): {}" -#: settings/utils/ldap.py:638 +#: settings/utils/ldap.py:641 msgid "Error (Unknown): {}" msgstr "错误 (未知): {}" -#: settings/utils/ldap.py:641 +#: settings/utils/ldap.py:644 msgid "Succeed: Match {} s user" msgstr "成功匹配 {} 个用户" -#: settings/utils/ldap.py:652 +#: settings/utils/ldap.py:655 msgid "Please test the connection first" msgstr "请先测试连接" -#: settings/utils/ldap.py:674 +#: settings/utils/ldap.py:677 msgid "Authentication failed (configuration incorrect): {}" msgstr "认证失败 (配置错误): {}" -#: settings/utils/ldap.py:678 +#: settings/utils/ldap.py:681 msgid "Authentication failed (username or password incorrect): {}" msgstr "认证失败 (用户名或密码不正确): {}" -#: settings/utils/ldap.py:680 +#: settings/utils/ldap.py:683 msgid "Authentication failed (Unknown): {}" msgstr "认证失败: (未知): {}" -#: settings/utils/ldap.py:683 +#: settings/utils/ldap.py:686 msgid "Authentication success: {}" msgstr "认证成功: {}" @@ -6287,12 +6326,12 @@ msgid "Send verification code" msgstr "发送验证码" #: templates/_mfa_login_field.html:106 -#: users/templates/users/forgot_password.html:130 +#: users/templates/users/forgot_password.html:174 msgid "Wait: " msgstr "等待:" #: templates/_mfa_login_field.html:116 -#: users/templates/users/forgot_password.html:146 +#: users/templates/users/forgot_password.html:190 msgid "The verification code has been sent" msgstr "验证码已发送" @@ -6651,14 +6690,14 @@ msgid "SQLServer port" msgstr "SQLServer 端口" #: terminal/models/component/endpoint.py:30 -#: terminal/models/component/endpoint.py:103 +#: terminal/models/component/endpoint.py:117 #: terminal/serializers/endpoint.py:73 terminal/serializers/storage.py:41 #: terminal/serializers/storage.py:53 terminal/serializers/storage.py:83 #: terminal/serializers/storage.py:93 terminal/serializers/storage.py:101 msgid "Endpoint" msgstr "端点" -#: terminal/models/component/endpoint.py:109 +#: terminal/models/component/endpoint.py:123 msgid "Endpoint rule" msgstr "端点规则" @@ -6748,10 +6787,6 @@ msgstr "登录来源" msgid "Replay" msgstr "回放" -#: terminal/models/session/session.py:45 -msgid "Date end" -msgstr "结束日期" - #: terminal/models/session/session.py:47 terminal/serializers/session.py:65 msgid "Command amount" msgstr "命令数量" @@ -7266,54 +7301,66 @@ msgstr "申请人" msgid "App Tickets" msgstr "工单管理" -#: tickets/const.py:9 +#: tickets/const.py:10 msgid "Apply for asset" msgstr "申请资产" -#: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 +#: tickets/const.py:17 tickets/const.py:25 tickets/const.py:44 msgid "Open" msgstr "打开" -#: tickets/const.py:18 tickets/const.py:31 +#: tickets/const.py:19 tickets/const.py:32 msgid "Reopen" msgstr "重新打开" -#: tickets/const.py:19 tickets/const.py:32 +#: tickets/const.py:20 tickets/const.py:33 msgid "Approved" msgstr "已同意" -#: tickets/const.py:20 tickets/const.py:33 +#: tickets/const.py:21 tickets/const.py:34 msgid "Rejected" msgstr "已拒绝" -#: tickets/const.py:30 tickets/const.py:38 +#: tickets/const.py:31 tickets/const.py:39 msgid "Closed" msgstr "关闭的" -#: tickets/const.py:50 +#: tickets/const.py:51 msgid "One level" msgstr "1 级" -#: tickets/const.py:51 +#: tickets/const.py:52 msgid "Two level" msgstr "2 级" -#: tickets/const.py:55 +#: tickets/const.py:56 msgid "Org admin" msgstr "组织管理员" -#: tickets/const.py:56 +#: tickets/const.py:57 msgid "Custom user" msgstr "自定义用户" -#: tickets/const.py:57 +#: tickets/const.py:58 msgid "Super admin" msgstr "超级管理员" -#: tickets/const.py:58 +#: tickets/const.py:59 msgid "Super admin and org admin" msgstr "组织管理员或超级管理员" +#: tickets/const.py:63 +msgid "All assets" +msgstr "所有资产" + +#: tickets/const.py:64 +msgid "Permed assets" +msgstr "授权的资产" + +#: tickets/const.py:65 +msgid "Permed valid assets" +msgstr "有效授权的资产" + #: tickets/errors.py:9 msgid "Ticket already closed" msgstr "工单已经关闭" @@ -7825,7 +7872,7 @@ msgstr "用户密码历史" msgid "Reset password" msgstr "重置密码" -#: users/notifications.py:85 users/views/profile/reset.py:230 +#: users/notifications.py:85 users/views/profile/reset.py:231 msgid "Reset password success" msgstr "重置密码成功" @@ -8010,6 +8057,10 @@ msgid "" msgstr "" "管理员已开启'仅允许已存在用户登录',当前用户不在用户列表中,请联系管理员。" +#: users/signal_handlers.py:166 +msgid "Clean up expired user sessions" +msgstr "清除过期的用户会话" + #: users/tasks.py:25 msgid "Check password expired" msgstr "校验密码已过期" @@ -8086,28 +8137,28 @@ msgstr "你的 SSH 密钥已经被管理员重置" msgid "click here to set your password" msgstr "点击这里设置密码" -#: users/templates/users/forgot_password.html:32 +#: users/templates/users/forgot_password.html:46 msgid "Input your email account, that will send a email to your" msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中" -#: users/templates/users/forgot_password.html:35 +#: users/templates/users/forgot_password.html:49 msgid "" "Enter your mobile number and a verification code will be sent to your phone" msgstr "输入您的手机号码,验证码将发送到您的手机" -#: users/templates/users/forgot_password.html:57 +#: users/templates/users/forgot_password.html:71 msgid "Email account" msgstr "邮箱账号" -#: users/templates/users/forgot_password.html:61 +#: users/templates/users/forgot_password.html:92 msgid "Mobile number" msgstr "手机号码" -#: users/templates/users/forgot_password.html:69 +#: users/templates/users/forgot_password.html:100 msgid "Send" msgstr "发送" -#: users/templates/users/forgot_password.html:73 +#: users/templates/users/forgot_password.html:104 #: users/templates/users/forgot_password_previewing.html:30 msgid "Submit" msgstr "提交" @@ -8197,7 +8248,7 @@ msgstr "Android手机下载" msgid "iPhone downloads" msgstr "iPhone手机下载" -#: users/templates/users/user_otp_enable_install_app.html:26 +#: users/templates/users/user_otp_enable_install_app.html:27 msgid "" "After installation, click the next step to enter the binding page (if " "installed, go to the next step directly)." @@ -8222,31 +8273,31 @@ msgstr "账号保护已开启,请根据提示完成以下操作" msgid "Open MFA Authenticator and enter the 6-bit dynamic code" msgstr "请打开 MFA 验证器,输入 6 位动态码" -#: users/views/profile/otp.py:85 +#: users/views/profile/otp.py:106 msgid "Already bound" msgstr "已经绑定" -#: users/views/profile/otp.py:86 +#: users/views/profile/otp.py:107 msgid "MFA already bound, disable first, then bound" msgstr "MFA(OTP) 已经绑定,请先禁用,再绑定" -#: users/views/profile/otp.py:113 +#: users/views/profile/otp.py:134 msgid "OTP enable success" msgstr "MFA(OTP) 启用成功" -#: users/views/profile/otp.py:114 +#: users/views/profile/otp.py:135 msgid "OTP enable success, return login page" msgstr "MFA(OTP) 启用成功,返回到登录页面" -#: users/views/profile/otp.py:156 +#: users/views/profile/otp.py:177 msgid "Disable OTP" msgstr "禁用虚拟 MFA(OTP)" -#: users/views/profile/otp.py:162 +#: users/views/profile/otp.py:183 msgid "OTP disable success" msgstr "MFA(OTP) 禁用成功" -#: users/views/profile/otp.py:163 +#: users/views/profile/otp.py:184 msgid "OTP disable success, return login page" msgstr "MFA(OTP) 禁用成功,返回登录页面" @@ -8254,29 +8305,29 @@ msgstr "MFA(OTP) 禁用成功,返回登录页面" msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/profile/reset.py:65 +#: users/views/profile/reset.py:66 msgid "" "Non-local users can log in only from third-party platforms and cannot change " "their passwords: {}" msgstr "非本地用户仅允许从第三方平台登录,不支持修改密码: {}" -#: users/views/profile/reset.py:185 users/views/profile/reset.py:196 +#: users/views/profile/reset.py:186 users/views/profile/reset.py:197 msgid "Token invalid or expired" msgstr "令牌错误或失效" -#: users/views/profile/reset.py:201 +#: users/views/profile/reset.py:202 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" -#: users/views/profile/reset.py:208 +#: users/views/profile/reset.py:209 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/profile/reset.py:214 +#: users/views/profile/reset.py:215 msgid "* The new password cannot be the last {} passwords" msgstr "* 新密码不能是最近 {} 次的密码" -#: users/views/profile/reset.py:231 +#: users/views/profile/reset.py:232 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" @@ -8985,36 +9036,6 @@ msgstr "企业专业版" msgid "Ultimate edition" msgstr "企业旗舰版" -#~ msgid "Select users" -#~ msgstr "选择用户" - -#~ msgid "For security, only list several users" -#~ msgstr "为了安全,仅列出几个用户" - -#~ msgid "Period" -#~ msgstr "周期" - -#~ msgid "Run interval" -#~ msgstr "运行间隔" - -#~ msgid "Regularly perform" -#~ msgstr "定期执行" - -#~ msgid "Cycle perform" -#~ msgstr "周期执行" - -#~ msgid "Applications" -#~ msgstr "应用管理" - -#~ msgid "SMTP port" -#~ msgstr "SMTP 端口" - -#~ msgid "SMTP account" -#~ msgstr "SMTP 账号" - -#~ msgid "SMTP password" -#~ msgstr "SMTP 密码" - #~ msgid "Password can not contains `{{` or `}}`" #~ msgstr "密码不能包含 `{{` 或 `}}` 字符" diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index d6fb241db..9f869ace6 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -282,6 +282,7 @@ class Config(dict): 'AUTH_LDAP_SYNC_INTERVAL': None, 'AUTH_LDAP_SYNC_CRONTAB': None, 'AUTH_LDAP_SYNC_ORG_IDS': ['00000000-0000-0000-0000-000000000002'], + 'AUTH_LDAP_SYNC_RECEIVERS': [], 'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False, 'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1, @@ -546,7 +547,6 @@ class Config(dict): 'REFERER_CHECK_ENABLED': False, 'SESSION_ENGINE': 'cache', 'SESSION_SAVE_EVERY_REQUEST': True, - 'SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE': False, 'SERVER_REPLAY_STORAGE': {}, 'SECURITY_DATA_CRYPTO_ALGO': None, 'GMSSL_ENABLED': False, @@ -605,7 +605,9 @@ class Config(dict): 'GPT_MODEL': 'gpt-3.5-turbo', 'VIRTUAL_APP_ENABLED': False, - 'FILE_UPLOAD_SIZE_LIMIT_MB': 200 + 'FILE_UPLOAD_SIZE_LIMIT_MB': 200, + + 'TICKET_APPLY_ASSET_SCOPE': 'all' } old_config_map = { diff --git a/apps/jumpserver/middleware.py b/apps/jumpserver/middleware.py index 9a49189c8..ad6bb496d 100644 --- a/apps/jumpserver/middleware.py +++ b/apps/jumpserver/middleware.py @@ -66,11 +66,6 @@ class RequestMiddleware: def __call__(self, request): set_current_request(request) response = self.get_response(request) - is_request_api = request.path.startswith('/api') - if not settings.SESSION_EXPIRE_AT_BROWSER_CLOSE and \ - not is_request_api: - age = request.session.get_expiry_age() - request.session.set_expiry(age) return response diff --git a/apps/jumpserver/rewriting/storage/permissions.py b/apps/jumpserver/rewriting/storage/permissions.py index 511545865..7d91c246f 100644 --- a/apps/jumpserver/rewriting/storage/permissions.py +++ b/apps/jumpserver/rewriting/storage/permissions.py @@ -3,6 +3,7 @@ path_perms_map = { 'xpack': '*', 'settings': '*', + 'img': '*', 'replay': 'default', 'applets': 'terminal.view_applet', 'virtual_apps': 'terminal.view_virtualapp', diff --git a/apps/jumpserver/rewriting/storage/servers.py b/apps/jumpserver/rewriting/storage/servers.py new file mode 100644 index 000000000..5fc4b0514 --- /dev/null +++ b/apps/jumpserver/rewriting/storage/servers.py @@ -0,0 +1,14 @@ +from private_storage.servers import NginxXAccelRedirectServer, DjangoServer + + +class StaticFileServer(object): + + @staticmethod + def serve(private_file): + full_path = private_file.full_path + # todo: gzip 文件录像 nginx 处理后,浏览器无法正常解析内容 + # 造成在线播放失败,暂时仅使用 nginx 处理 mp4 录像文件 + if full_path.endswith('.mp4'): + return NginxXAccelRedirectServer.serve(private_file) + else: + return DjangoServer.serve(private_file) diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 66ae6289c..5e44a22a4 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -50,6 +50,7 @@ AUTH_LDAP_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_SYNC_IS_PERIODIC AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB AUTH_LDAP_SYNC_ORG_IDS = CONFIG.AUTH_LDAP_SYNC_ORG_IDS +AUTH_LDAP_SYNC_RECEIVERS = CONFIG.AUTH_LDAP_SYNC_RECEIVERS AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS # ============================================================================== diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 03ac5bb33..b0f07d834 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -234,11 +234,9 @@ CSRF_COOKIE_NAME = '{}csrftoken'.format(SESSION_COOKIE_NAME_PREFIX) SESSION_COOKIE_NAME = '{}sessionid'.format(SESSION_COOKIE_NAME_PREFIX) SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE -SESSION_EXPIRE_AT_BROWSER_CLOSE = True -# 自定义的配置,SESSION_EXPIRE_AT_BROWSER_CLOSE 始终为 True, 下面这个来控制是否强制关闭后过期 cookie -SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE = CONFIG.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE SESSION_SAVE_EVERY_REQUEST = CONFIG.SESSION_SAVE_EVERY_REQUEST -SESSION_ENGINE = "django.contrib.sessions.backends.{}".format(CONFIG.SESSION_ENGINE) +SESSION_EXPIRE_AT_BROWSER_CLOSE = CONFIG.SESSION_EXPIRE_AT_BROWSER_CLOSE +SESSION_ENGINE = "common.sessions.{}".format(CONFIG.SESSION_ENGINE) MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' # Database @@ -319,9 +317,7 @@ MEDIA_ROOT = os.path.join(PROJECT_DIR, 'data', 'media').replace('\\', '/') + '/' PRIVATE_STORAGE_ROOT = MEDIA_ROOT PRIVATE_STORAGE_AUTH_FUNCTION = 'jumpserver.rewriting.storage.permissions.allow_access' PRIVATE_STORAGE_INTERNAL_URL = '/private-media/' -PRIVATE_STORAGE_SERVER = 'nginx' -if DEBUG_DEV: - PRIVATE_STORAGE_SERVER = 'django' +PRIVATE_STORAGE_SERVER = 'jumpserver.rewriting.storage.servers.StaticFileServer' # Use django-bootstrap-form to format template, input max width arg # BOOTSTRAP_COLUMN_COUNT = 11 diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index 311e54aa8..ed5cc61a9 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -214,6 +214,9 @@ PERM_TREE_REGEN_INTERVAL = CONFIG.PERM_TREE_REGEN_INTERVAL MAGNUS_ORACLE_PORTS = CONFIG.MAGNUS_ORACLE_PORTS LIMIT_SUPER_PRIV = CONFIG.LIMIT_SUPER_PRIV +# Asset account may be too many +ASSET_SIZE = 'small' + # Chat AI CHAT_AI_ENABLED = CONFIG.CHAT_AI_ENABLED GPT_API_KEY = CONFIG.GPT_API_KEY @@ -224,3 +227,5 @@ GPT_MODEL = CONFIG.GPT_MODEL VIRTUAL_APP_ENABLED = CONFIG.VIRTUAL_APP_ENABLED FILE_UPLOAD_SIZE_LIMIT_MB = CONFIG.FILE_UPLOAD_SIZE_LIMIT_MB + +TICKET_APPLY_ASSET_SCOPE = CONFIG.TICKET_APPLY_ASSET_SCOPE diff --git a/apps/labels/mixins.py b/apps/labels/mixins.py index 9feb6f0f0..33e73b60b 100644 --- a/apps/labels/mixins.py +++ b/apps/labels/mixins.py @@ -1,14 +1,15 @@ from django.contrib.contenttypes.fields import GenericRelation from django.db import models -from django.db.models import OneToOneField +from django.db.models import OneToOneField, Count +from common.utils import lazyproperty from .models import LabeledResource __all__ = ['LabeledMixin'] class LabeledMixin(models.Model): - _labels = GenericRelation(LabeledResource, object_id_field='res_id', content_type_field='res_type') + labels = GenericRelation(LabeledResource, object_id_field='res_id', content_type_field='res_type') class Meta: abstract = True @@ -21,7 +22,7 @@ class LabeledMixin(models.Model): model = pk_field.related_model return model - @property + @lazyproperty def real(self): pk_field = self._meta.pk if isinstance(pk_field, OneToOneField): @@ -29,9 +30,43 @@ class LabeledMixin(models.Model): return self @property - def labels(self): - return self.real._labels + def res_labels(self): + return self.real.labels - @labels.setter - def labels(self, value): - self.real._labels.set(value, bulk=False) + @res_labels.setter + def res_labels(self, value): + self.real.labels.set(value, bulk=False) + + @classmethod + def filter_resources_by_labels(cls, resources, label_ids): + return cls._get_filter_res_by_labels_m2m_all(resources, label_ids) + + @classmethod + def _get_filter_res_by_labels_m2m_in(cls, resources, label_ids): + return resources.filter(label_id__in=label_ids) + + @classmethod + def _get_filter_res_by_labels_m2m_all(cls, resources, label_ids): + if len(label_ids) == 1: + return cls._get_filter_res_by_labels_m2m_in(resources, label_ids) + + resources = resources.filter(label_id__in=label_ids) \ + .values('res_id') \ + .order_by('res_id') \ + .annotate(count=Count('res_id', distinct=True)) \ + .values('res_id', 'count') \ + .filter(count=len(label_ids)) + return resources + + @classmethod + def get_labels_filter_attr_q(cls, value, match): + resources = LabeledResource.objects.all() + if not value: + return None + + if match != 'm2m_all': + resources = cls._get_filter_res_by_labels_m2m_in(resources, value) + else: + resources = cls._get_filter_res_by_labels_m2m_all(resources, value) + res_ids = set(resources.values_list('res_id', flat=True)) + return models.Q(id__in=res_ids) diff --git a/apps/labels/serializers.py b/apps/labels/serializers.py index 803843ad8..62abc1bc8 100644 --- a/apps/labels/serializers.py +++ b/apps/labels/serializers.py @@ -34,7 +34,7 @@ class LabelSerializer(BulkOrgResourceModelSerializer): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.annotate(res_count=Count('labeled_resources')) + queryset = queryset.annotate(res_count=Count('labeled_resources', distinct=True)) return queryset diff --git a/apps/notifications/ws.py b/apps/notifications/ws.py index 019b0b409..b172c4ed4 100644 --- a/apps/notifications/ws.py +++ b/apps/notifications/ws.py @@ -1,28 +1,32 @@ import json +import time +from threading import Thread from channels.generic.websocket import JsonWebsocketConsumer -from django.core.cache import cache +from django.conf import settings from common.db.utils import safe_db_connection +from common.sessions.cache import user_session_manager from common.utils import get_logger from .signal_handlers import new_site_msg_chan from .site_msg import SiteMessageUtil logger = get_logger(__name__) -WS_SESSION_KEY = 'ws_session_key' class SiteMsgWebsocket(JsonWebsocketConsumer): sub = None refresh_every_seconds = 10 + @property + def session(self): + return self.scope['session'] + def connect(self): user = self.scope["user"] if user.is_authenticated: self.accept() - session = self.scope['session'] - redis_client = cache.client.get_client() - redis_client.sadd(WS_SESSION_KEY, session.session_key) + user_session_manager.add_or_increment(self.session.session_key) self.sub = self.watch_recv_new_site_msg() else: self.close() @@ -66,6 +70,32 @@ class SiteMsgWebsocket(JsonWebsocketConsumer): if not self.sub: return self.sub.unsubscribe() - session = self.scope['session'] - redis_client = cache.client.get_client() - redis_client.srem(WS_SESSION_KEY, session.session_key) + + user_session_manager.decrement_or_remove(self.session.session_key) + if self.should_delete_session(): + thread = Thread(target=self.delay_delete_session) + thread.start() + + def should_delete_session(self): + return (self.session.modified or settings.SESSION_SAVE_EVERY_REQUEST) and \ + not self.session.is_empty() and \ + self.session.get_expire_at_browser_close() and \ + not user_session_manager.check_active(self.session.session_key) + + def delay_delete_session(self): + timeout = 3 + check_interval = 0.5 + + start_time = time.time() + while time.time() - start_time < timeout: + time.sleep(check_interval) + if user_session_manager.check_active(self.session.session_key): + return + + self.delete_session() + + def delete_session(self): + try: + self.session.delete() + except Exception as e: + logger.info(f'delete session error: {e}') diff --git a/apps/ops/ansible/modules_utils/custom_common.py b/apps/ops/ansible/modules_utils/custom_common.py index 0cf1420d2..7015ff2f9 100644 --- a/apps/ops/ansible/modules_utils/custom_common.py +++ b/apps/ops/ansible/modules_utils/custom_common.py @@ -4,6 +4,21 @@ import time import paramiko from sshtunnel import SSHTunnelForwarder +from packaging import version + +if version.parse(paramiko.__version__) > version.parse("2.8.1"): + _preferred_pubkeys = ( + "ssh-ed25519", + "ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp384", + "ecdsa-sha2-nistp521", + "ssh-rsa", + "rsa-sha2-256", + "rsa-sha2-512", + "ssh-dss", + ) + paramiko.transport.Transport._preferred_pubkeys = _preferred_pubkeys + def common_argument_spec(): options = dict( diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py index 797d00c5e..dd66a886e 100644 --- a/apps/ops/api/celery.py +++ b/apps/ops/api/celery.py @@ -2,6 +2,7 @@ # import os import re +from collections import defaultdict from celery.result import AsyncResult from django.shortcuts import get_object_or_404 @@ -166,16 +167,58 @@ class CeleryTaskViewSet( i.next_exec_time = now + next_run_at return queryset + def generate_summary_state(self, execution_qs): + model = self.get_queryset().model + executions = execution_qs.order_by('-date_published').values('name', 'state') + summary_state_dict = defaultdict( + lambda: { + 'states': [], 'state': 'green', + 'summary': {'total': 0, 'success': 0} + } + ) + for execution in executions: + name = execution['name'] + state = execution['state'] + + summary = summary_state_dict[name]['summary'] + + summary['total'] += 1 + summary['success'] += 1 if state == 'SUCCESS' else 0 + + states = summary_state_dict[name].get('states') + if states is not None and len(states) >= 5: + color = model.compute_state_color(states) + summary_state_dict[name]['state'] = color + summary_state_dict[name].pop('states', None) + elif isinstance(states, list): + states.append(state) + + return summary_state_dict + + def loading_summary_state(self, queryset): + if isinstance(queryset, list): + names = [i.name for i in queryset] + execution_qs = CeleryTaskExecution.objects.filter(name__in=names) + else: + execution_qs = CeleryTaskExecution.objects.all() + summary_state_dict = self.generate_summary_state(execution_qs) + for i in queryset: + i.summary = summary_state_dict.get(i.name, {}).get('summary', {}) + i.state = summary_state_dict.get(i.name, {}).get('state', 'green') + return queryset + def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: page = self.generate_execute_time(page) + page = self.loading_summary_state(page) serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) queryset = self.generate_execute_time(queryset) + queryset = self.loading_summary_state(queryset) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index 61768f5d9..eb908d04c 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -246,6 +246,6 @@ class UsernameHintsAPI(APIView): .filter(username__icontains=query) \ .filter(asset__in=assets) \ .values('username') \ - .annotate(total=Count('username')) \ + .annotate(total=Count('username', distinct=True)) \ .order_by('total', '-username')[:10] return Response(data=top_accounts) diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index a48538273..ef1fab463 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -15,6 +15,9 @@ class CeleryTask(models.Model): name = models.CharField(max_length=1024, verbose_name=_('Name')) date_last_publish = models.DateTimeField(null=True, verbose_name=_("Date last publish")) + __summary = None + __state = None + @property def meta(self): task = app.tasks.get(self.name, None) @@ -25,25 +28,43 @@ class CeleryTask(models.Model): @property def summary(self): + if self.__summary is not None: + return self.__summary executions = CeleryTaskExecution.objects.filter(name=self.name) total = executions.count() success = executions.filter(state='SUCCESS').count() return {'total': total, 'success': success} + @summary.setter + def summary(self, value): + self.__summary = value + + @staticmethod + def compute_state_color(states: list, default_count=5): + color = 'green' + states = states[:default_count] + if not states: + return color + if states[0] == 'FAILURE': + color = 'red' + elif 'FAILURE' in states: + color = 'yellow' + return color + @property def state(self): - last_five_executions = CeleryTaskExecution.objects \ - .filter(name=self.name) \ - .order_by('-date_published')[:5] + if self.__state is not None: + return self.__state + last_five_executions = CeleryTaskExecution.objects.filter( + name=self.name + ).order_by('-date_published').values('state')[:5] + states = [i['state'] for i in last_five_executions] + color = self.compute_state_color(states) + return color - if len(last_five_executions) > 0: - if last_five_executions[0].state == 'FAILURE': - return "red" - - for execution in last_five_executions: - if execution.state == 'FAILURE': - return "yellow" - return "green" + @state.setter + def state(self, value): + self.__state = value class Meta: verbose_name = _("Celery Task") diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 9fcc079ca..04c7fa519 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -67,6 +67,7 @@ class JMSPermedInventory(JMSInventory): 'postgresql': ['postgresql'], 'sqlserver': ['sqlserver'], 'ssh': ['shell', 'python', 'win_shell', 'raw'], + 'winrm': ['win_shell', 'shell'], } if self.module not in protocol_supported_modules_mapping.get(protocol.name, []): diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 4dc3c796e..aec38527a 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -87,7 +87,8 @@ class OrgResourceStatisticsRefreshUtil: if not cache_field_name: return org = getattr(instance, 'org', None) - cls.refresh_org_fields(((org, cache_field_name),)) + cache_field_name = tuple(cache_field_name) + cls.refresh_org_fields.delay(org_fields=((org, cache_field_name),)) @receiver(post_save) diff --git a/apps/perms/api/user_permission/assets.py b/apps/perms/api/user_permission/assets.py index bc51dfe77..5456af1e3 100644 --- a/apps/perms/api/user_permission/assets.py +++ b/apps/perms/api/user_permission/assets.py @@ -1,5 +1,6 @@ import abc +from django.conf import settings from rest_framework.generics import ListAPIView, RetrieveAPIView from assets.api.asset.asset import AssetFilterSet @@ -7,8 +8,7 @@ from assets.models import Asset, Node from common.utils import get_logger, lazyproperty, is_uuid from orgs.utils import tmp_to_root_org from perms import serializers -from perms.pagination import AllPermedAssetPagination -from perms.pagination import NodePermedAssetPagination +from perms.pagination import NodePermedAssetPagination, AllPermedAssetPagination from perms.utils import UserPermAssetUtil, PermAssetDetailUtil from .mixin import ( SelfOrPKUserMixin @@ -39,7 +39,7 @@ class UserPermedAssetRetrieveApi(SelfOrPKUserMixin, RetrieveAPIView): class BaseUserPermedAssetsApi(SelfOrPKUserMixin, ListAPIView): - ordering = ('name',) + ordering = [] search_fields = ('name', 'address', 'comment') ordering_fields = ("name", "address") filterset_class = AssetFilterSet @@ -48,6 +48,8 @@ class BaseUserPermedAssetsApi(SelfOrPKUserMixin, ListAPIView): def get_queryset(self): if getattr(self, 'swagger_fake_view', False): return Asset.objects.none() + if settings.ASSET_SIZE == 'small': + self.ordering = ['name'] assets = self.get_assets() assets = self.serializer_class.setup_eager_loading(assets) return assets diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index 17cdd7573..114c5475e 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -14,6 +14,7 @@ from assets.api import SerializeToTreeNodeMixin from assets.models import Asset from assets.utils import KubernetesTree from authentication.models import ConnectionToken +from common.exceptions import JMSException from common.utils import get_object_or_none, lazyproperty from common.utils.common import timeit from perms.hands import Node @@ -181,6 +182,8 @@ class UserPermedNodeChildrenWithAssetsAsCategoryTreeApi(BaseUserNodeWithAssetAsT return self.query_asset_util.get_all_assets() def _get_tree_nodes_async(self): + if self.request.query_params.get('lv') == '0': + return [], [] if not self.tp or not all(self.tp): nodes = UserPermAssetUtil.get_type_nodes_tree_or_cached(self.user) return nodes, [] @@ -262,5 +265,8 @@ class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView): if not any([namespace, pod]) and not key: asset_node = k8s_tree_instance.as_asset_tree_node() tree.append(asset_node) - tree.extend(k8s_tree_instance.async_tree_node(namespace, pod)) - return Response(data=tree) + try: + tree.extend(k8s_tree_instance.async_tree_node(namespace, pod)) + return Response(data=tree) + except Exception as e: + raise JMSException(e) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 519b0adb8..d8c7026e5 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -130,7 +130,7 @@ class AssetPermission(LabeledMixin, JMSOrgBaseModel): qs1_ids = User.objects.filter(id__in=user_ids).distinct().values_list('id', flat=True) qs2_ids = User.objects.filter(groups__id__in=group_ids).distinct().values_list('id', flat=True) qs_ids = list(qs1_ids) + list(qs2_ids) - qs = User.objects.filter(id__in=qs_ids) + qs = User.objects.filter(id__in=qs_ids, is_service_account=False) return qs def get_all_assets(self, flat=False): diff --git a/apps/perms/notifications.py b/apps/perms/notifications.py index 5c82c2589..f168a0517 100644 --- a/apps/perms/notifications.py +++ b/apps/perms/notifications.py @@ -9,7 +9,7 @@ class PermedAssetsWillExpireUserMsg(UserMessage): def __init__(self, user, assets, day_count=0): super().__init__(user) self.assets = assets - self.day_count = _('today') if day_count == 0 else day_count + _('day') + self.day_count = _('today') if day_count == 0 else str(day_count) + _('day') def get_html_msg(self) -> dict: subject = _("You permed assets is about to expire") @@ -41,7 +41,7 @@ class AssetPermsWillExpireForOrgAdminMsg(UserMessage): super().__init__(user) self.perms = perms self.org = org - self.day_count = _('today') if day_count == 0 else day_count + _('day') + self.day_count = _('today') if day_count == 0 else str(day_count) + _('day') def get_items_with_url(self): items_with_url = [] diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py index 012c6f7e1..aa816dd05 100644 --- a/apps/perms/serializers/permission.py +++ b/apps/perms/serializers/permission.py @@ -197,9 +197,9 @@ class AssetPermissionListSerializer(AssetPermissionSerializer): """Perform necessary eager loading of data.""" queryset = queryset \ .prefetch_related('labels', 'labels__label') \ - .annotate(users_amount=Count("users"), - user_groups_amount=Count("user_groups"), - assets_amount=Count("assets"), - nodes_amount=Count("nodes"), + .annotate(users_amount=Count("users", distinct=True), + user_groups_amount=Count("user_groups", distinct=True), + assets_amount=Count("assets", distinct=True), + nodes_amount=Count("nodes", distinct=True), ) return queryset diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 0bd9bdc02..e3441a04d 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -8,9 +8,9 @@ from rest_framework import serializers from accounts.models import Account from assets.const import Category, AllTypes from assets.models import Node, Asset, Platform -from assets.serializers.asset.common import AssetLabelSerializer, AssetProtocolsPermsSerializer -from common.serializers.fields import ObjectRelatedField, LabeledChoiceField +from assets.serializers.asset.common import AssetProtocolsPermsSerializer from common.serializers import ResourceLabelsMixin +from common.serializers.fields import ObjectRelatedField, LabeledChoiceField from orgs.mixins.serializers import OrgResourceModelSerializerMixin from perms.serializers.permission import ActionChoicesField diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 036dabef2..859f579be 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -13,7 +13,7 @@ class AssetPermissionUtil(object): """ 资产授权相关的方法工具 """ @timeit - def get_permissions_for_user(self, user, with_group=True, flat=False): + def get_permissions_for_user(self, user, with_group=True, flat=False, with_expired=False): """ 获取用户的授权规则 """ perm_ids = set() # user @@ -25,7 +25,7 @@ class AssetPermissionUtil(object): groups = user.groups.all() group_perm_ids = self.get_permissions_for_user_groups(groups, flat=True) perm_ids.update(group_perm_ids) - perms = self.get_permissions(ids=perm_ids) + perms = self.get_permissions(ids=perm_ids, with_expired=with_expired) if flat: return perms.values_list('id', flat=True) return perms @@ -102,6 +102,8 @@ class AssetPermissionUtil(object): return model.objects.filter(id__in=ids) @staticmethod - def get_permissions(ids): - perms = AssetPermission.objects.filter(id__in=ids).valid().order_by('-date_expired') - return perms + def get_permissions(ids, with_expired=False): + perms = AssetPermission.objects.filter(id__in=ids) + if not with_expired: + perms = perms.valid() + return perms.order_by('-date_expired') diff --git a/apps/perms/utils/user_perm.py b/apps/perms/utils/user_perm.py index 0ad02fb25..1fc4e86bf 100644 --- a/apps/perms/utils/user_perm.py +++ b/apps/perms/utils/user_perm.py @@ -7,10 +7,10 @@ from django.db.models import Q from rest_framework.utils.encoders import JSONEncoder from assets.const import AllTypes -from assets.models import FavoriteAsset, Asset +from assets.models import FavoriteAsset, Asset, Node from common.utils.common import timeit, get_logger from orgs.utils import current_org, tmp_to_root_org -from perms.models import PermNode, UserAssetGrantedTreeNodeRelation +from perms.models import PermNode, UserAssetGrantedTreeNodeRelation, AssetPermission from .permission import AssetPermissionUtil __all__ = ['AssetPermissionPermAssetUtil', 'UserPermAssetUtil', 'UserPermNodeUtil'] @@ -21,36 +21,37 @@ logger = get_logger(__name__) class AssetPermissionPermAssetUtil: def __init__(self, perm_ids): - self.perm_ids = perm_ids + self.perm_ids = set(perm_ids) def get_all_assets(self): - """ 获取所有授权的资产 """ node_assets = self.get_perm_nodes_assets() direct_assets = self.get_direct_assets() # 比原来的查到所有 asset id 再搜索块很多,因为当资产量大的时候,搜索会很慢 - return (node_assets | direct_assets).distinct() + return (node_assets | direct_assets).order_by().distinct() + + def get_perm_nodes(self): + """ 获取所有授权节点 """ + nodes_ids = AssetPermission.objects \ + .filter(id__in=self.perm_ids) \ + .values_list('nodes', flat=True) + nodes_ids = set(nodes_ids) + nodes = Node.objects.filter(id__in=nodes_ids).only('id', 'key') + return nodes @timeit - def get_perm_nodes_assets(self, flat=False): + def get_perm_nodes_assets(self): """ 获取所有授权节点下的资产 """ - from assets.models import Node - nodes = Node.objects \ - .prefetch_related('granted_by_permissions') \ - .filter(granted_by_permissions__in=self.perm_ids) \ - .only('id', 'key') - assets = PermNode.get_nodes_all_assets(*nodes) - if flat: - return set(assets.values_list('id', flat=True)) + nodes = self.get_perm_nodes() + assets = PermNode.get_nodes_all_assets(*nodes, distinct=False) return assets @timeit - def get_direct_assets(self, flat=False): + def get_direct_assets(self): """ 获取直接授权的资产 """ - assets = Asset.objects.order_by() \ - .filter(granted_by_permissions__id__in=self.perm_ids) \ - .distinct() - if flat: - return set(assets.values_list('id', flat=True)) + asset_ids = AssetPermission.assets.through.objects \ + .filter(assetpermission_id__in=self.perm_ids) \ + .values_list('asset_id', flat=True) + assets = Asset.objects.filter(id__in=asset_ids) return assets @@ -152,6 +153,7 @@ class UserPermAssetUtil(AssetPermissionPermAssetUtil): assets = assets.filter(nodes__id=node.id).order_by().distinct() return assets + @timeit def _get_indirect_perm_node_all_assets(self, node): """ 获取间接授权节点下的所有资产 此算法依据 `UserAssetGrantedTreeNodeRelation` 的数据查询 diff --git a/apps/perms/utils/user_perm_tree.py b/apps/perms/utils/user_perm_tree.py index 13577a9b1..17248adc0 100644 --- a/apps/perms/utils/user_perm_tree.py +++ b/apps/perms/utils/user_perm_tree.py @@ -72,7 +72,7 @@ class UserPermTreeRefreshUtil(_UserPermTreeCacheMixin): @timeit def refresh_if_need(self, force=False): - built_just_now = cache.get(self.cache_key_time) + built_just_now = False if settings.ASSET_SIZE == 'small' else cache.get(self.cache_key_time) if built_just_now: logger.info('Refresh user perm tree just now, pass: {}'.format(built_just_now)) return @@ -80,12 +80,18 @@ class UserPermTreeRefreshUtil(_UserPermTreeCacheMixin): if not to_refresh_orgs: logger.info('Not have to refresh orgs') return + logger.info("Delay refresh user orgs: {} {}".format(self.user, [o.name for o in to_refresh_orgs])) - refresh_user_orgs_perm_tree(user_orgs=((self.user, tuple(to_refresh_orgs)),)) - refresh_user_favorite_assets(users=(self.user,)) + sync = True if settings.ASSET_SIZE == 'small' else False + refresh_user_orgs_perm_tree.apply(sync=sync, user_orgs=((self.user, tuple(to_refresh_orgs)),)) + refresh_user_favorite_assets.apply(sync=sync, users=(self.user,)) @timeit def refresh_tree_manual(self): + """ + 用来手动 debug + :return: + """ built_just_now = cache.get(self.cache_key_time) if built_just_now: logger.info('Refresh just now, pass: {}'.format(built_just_now)) @@ -105,8 +111,9 @@ class UserPermTreeRefreshUtil(_UserPermTreeCacheMixin): return self._clean_user_perm_tree_for_legacy_org() - ttl = settings.PERM_TREE_REGEN_INTERVAL - cache.set(self.cache_key_time, int(time.time()), ttl) + if settings.ASSET_SIZE != 'small': + ttl = settings.PERM_TREE_REGEN_INTERVAL + cache.set(self.cache_key_time, int(time.time()), ttl) lock = UserGrantedTreeRebuildLock(self.user.id) got = lock.acquire(blocking=False) @@ -193,7 +200,13 @@ class UserPermTreeExpireUtil(_UserPermTreeCacheMixin): cache_key = self.get_cache_key(uid) p.srem(cache_key, *org_ids) p.execute() - logger.info('Expire perm tree for users: [{}], orgs: [{}]'.format(user_ids, org_ids)) + users_display = ','.join([str(i) for i in user_ids[:3]]) + if len(user_ids) > 3: + users_display += '...' + orgs_display = ','.join([str(i) for i in org_ids[:3]]) + if len(org_ids) > 3: + orgs_display += '...' + logger.info('Expire perm tree for users: [{}], orgs: [{}]'.format(users_display, orgs_display)) def expire_perm_tree_for_all_user(self): keys = self.client.keys(self.cache_key_all_user) diff --git a/apps/rbac/api/role.py b/apps/rbac/api/role.py index b9133c5c5..022a2ac05 100644 --- a/apps/rbac/api/role.py +++ b/apps/rbac/api/role.py @@ -80,9 +80,11 @@ class RoleViewSet(JMSModelViewSet): queryset = Role.objects.filter(id__in=ids).order_by(*self.ordering) org_id = current_org.id q = Q(role__scope=Role.Scope.system) | Q(role__scope=Role.Scope.org, org_id=org_id) - role_bindings = RoleBinding.objects.filter(q).values_list('role_id').annotate(user_count=Count('user_id')) + role_bindings = RoleBinding.objects.filter(q).values_list('role_id').annotate( + user_count=Count('user_id', distinct=True) + ) role_user_amount_mapper = {role_id: user_count for role_id, user_count in role_bindings} - queryset = queryset.annotate(permissions_amount=Count('permissions')) + queryset = queryset.annotate(permissions_amount=Count('permissions', distinct=True)) queryset = list(queryset) for role in queryset: role.users_amount = role_user_amount_mapper.get(role.id, 0) diff --git a/apps/settings/api/ldap.py b/apps/settings/api/ldap.py index 9c619e24d..f13a2e9af 100644 --- a/apps/settings/api/ldap.py +++ b/apps/settings/api/ldap.py @@ -137,7 +137,7 @@ class LDAPUserImportAPI(APIView): return Response({'msg': _('Get ldap users is None')}, status=400) orgs = self.get_orgs() - errors = LDAPImportUtil().perform_import(users, orgs) + new_users, errors = LDAPImportUtil().perform_import(users, orgs) if errors: return Response({'errors': errors}, status=400) diff --git a/apps/settings/api/public.py b/apps/settings/api/public.py index 81abfeb99..9808ea1c6 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -3,6 +3,7 @@ from rest_framework import generics from rest_framework.permissions import AllowAny from authentication.permissions import IsValidUserOrConnectionToken +from common.const.choices import COUNTRY_CALLING_CODES from common.utils import get_logger, lazyproperty from common.utils.timezone import local_now from .. import serializers @@ -24,7 +25,8 @@ class OpenPublicSettingApi(generics.RetrieveAPIView): def get_object(self): return { "XPACK_ENABLED": settings.XPACK_ENABLED, - "INTERFACE": self.interface_setting + "INTERFACE": self.interface_setting, + "COUNTRY_CALLING_CODES": COUNTRY_CALLING_CODES } diff --git a/apps/settings/notifications.py b/apps/settings/notifications.py new file mode 100644 index 000000000..5a80c966e --- /dev/null +++ b/apps/settings/notifications.py @@ -0,0 +1,36 @@ +from django.template.loader import render_to_string +from django.utils.translation import gettext as _ + +from common.utils import get_logger +from common.utils.timezone import local_now_display +from notifications.notifications import UserMessage + +logger = get_logger(__file__) + + +class LDAPImportMessage(UserMessage): + def __init__(self, user, extra_kwargs): + super().__init__(user) + self.orgs = extra_kwargs.pop('orgs', []) + self.end_time = extra_kwargs.pop('end_time', '') + self.start_time = extra_kwargs.pop('start_time', '') + self.time_start_display = extra_kwargs.pop('time_start_display', '') + self.new_users = extra_kwargs.pop('new_users', []) + self.errors = extra_kwargs.pop('errors', []) + self.cost_time = extra_kwargs.pop('cost_time', '') + + def get_html_msg(self) -> dict: + subject = _('Notification of Synchronized LDAP User Task Results') + context = { + 'orgs': self.orgs, + 'start_time': self.time_start_display, + 'end_time': local_now_display(), + 'cost_time': self.cost_time, + 'users': self.new_users, + 'errors': self.errors + } + message = render_to_string('ldap/_msg_import_ldap_user.html', context) + return { + 'subject': subject, + 'message': message + } diff --git a/apps/settings/serializers/auth/ldap.py b/apps/settings/serializers/auth/ldap.py index e4739251c..7a696db91 100644 --- a/apps/settings/serializers/auth/ldap.py +++ b/apps/settings/serializers/auth/ldap.py @@ -77,6 +77,9 @@ class LDAPSettingSerializer(serializers.Serializer): required=False, label=_('Connect timeout (s)'), ) AUTH_LDAP_SEARCH_PAGED_SIZE = serializers.IntegerField(required=False, label=_('Search paged size (piece)')) + AUTH_LDAP_SYNC_RECEIVERS = serializers.ListField( + required=False, label=_('Recipient'), max_length=36 + ) AUTH_LDAP = serializers.BooleanField(required=False, label=_('Enable LDAP auth')) diff --git a/apps/settings/serializers/auth/oauth2.py b/apps/settings/serializers/auth/oauth2.py index 56ddd6a66..b5a0dbb62 100644 --- a/apps/settings/serializers/auth/oauth2.py +++ b/apps/settings/serializers/auth/oauth2.py @@ -43,7 +43,7 @@ class OAuth2SettingSerializer(serializers.Serializer): ) AUTH_OAUTH2_ACCESS_TOKEN_METHOD = serializers.ChoiceField( default='GET', label=_('Client authentication method'), - choices=(('GET', 'GET'), ('POST', 'POST')) + choices=(('GET', 'GET'), ('POST', 'POST-DATA'), ('POST_JSON', 'POST-JSON')) ) AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT = serializers.CharField( required=True, max_length=1024, label=_('Provider userinfo endpoint') diff --git a/apps/settings/serializers/public.py b/apps/settings/serializers/public.py index 6e5a74c8e..278764c65 100644 --- a/apps/settings/serializers/public.py +++ b/apps/settings/serializers/public.py @@ -11,6 +11,7 @@ __all__ = [ class PublicSettingSerializer(serializers.Serializer): XPACK_ENABLED = serializers.BooleanField() INTERFACE = serializers.DictField() + COUNTRY_CALLING_CODES = serializers.ListField() class PrivateSettingSerializer(PublicSettingSerializer): diff --git a/apps/settings/tasks/ldap.py b/apps/settings/tasks/ldap.py index 0530680e8..e662eeaeb 100644 --- a/apps/settings/tasks/ldap.py +++ b/apps/settings/tasks/ldap.py @@ -1,15 +1,19 @@ # coding: utf-8 # +import time from celery import shared_task from django.conf import settings from django.utils.translation import gettext_lazy as _ from common.utils import get_logger +from common.utils.timezone import local_now_display from ops.celery.decorator import after_app_ready_start from ops.celery.utils import ( create_or_update_celery_periodic_tasks, disable_celery_periodic_task ) from orgs.models import Organization +from settings.notifications import LDAPImportMessage +from users.models import User from ..utils import LDAPSyncUtil, LDAPServerUtil, LDAPImportUtil __all__ = ['sync_ldap_user', 'import_ldap_user_periodic', 'import_ldap_user'] @@ -23,6 +27,8 @@ def sync_ldap_user(): @shared_task(verbose_name=_('Periodic import ldap user')) def import_ldap_user(): + start_time = time.time() + time_start_display = local_now_display() logger.info("Start import ldap user task") util_server = LDAPServerUtil() util_import = LDAPImportUtil() @@ -35,11 +41,26 @@ def import_ldap_user(): org_ids = [Organization.DEFAULT_ID] default_org = Organization.default() orgs = list(set([Organization.get_instance(org_id, default=default_org) for org_id in org_ids])) - errors = util_import.perform_import(users, orgs) + new_users, errors = util_import.perform_import(users, orgs) if errors: logger.error("Imported LDAP users errors: {}".format(errors)) else: logger.info('Imported {} users successfully'.format(len(users))) + if settings.AUTH_LDAP_SYNC_RECEIVERS: + user_ids = settings.AUTH_LDAP_SYNC_RECEIVERS + recipient_list = User.objects.filter(id__in=list(user_ids)) + end_time = time.time() + extra_kwargs = { + 'orgs': orgs, + 'end_time': end_time, + 'start_time': start_time, + 'time_start_display': time_start_display, + 'new_users': new_users, + 'errors': errors, + 'cost_time': end_time - start_time, + } + for user in recipient_list: + LDAPImportMessage(user, extra_kwargs).publish() @shared_task(verbose_name=_('Registration periodic import ldap user task')) diff --git a/apps/settings/templates/ldap/_msg_import_ldap_user.html b/apps/settings/templates/ldap/_msg_import_ldap_user.html new file mode 100644 index 000000000..feb00cd34 --- /dev/null +++ b/apps/settings/templates/ldap/_msg_import_ldap_user.html @@ -0,0 +1,34 @@ +{% load i18n %} +

{% trans "Sync task Finish" %}

+{% trans 'Time' %}: +
    +
  • {% trans 'Date start' %}: {{ start_time }}
  • +
  • {% trans 'Date end' %}: {{ end_time }}
  • +
  • {% trans 'Time cost' %}: {{ cost_time| floatformat:0 }}s
  • +
+{% trans "Synced Organization" %}: +
    + {% for org in orgs %} +
  • {{ org }}
  • + {% endfor %} +
+{% trans "Synced User" %}: +
    + {% if users %} + {% for user in users %} +
  • {{ user }}
  • + {% endfor %} + {% else %} +
  • {% trans 'No user synchronization required' %}
  • + {% endif %} +
+{% if errors %} + {% trans 'Error' %}: +
    + {% for error in errors %} +
  • {{ error }}
  • + {% endfor %} +
+{% endif %} + + diff --git a/apps/settings/utils/ldap.py b/apps/settings/utils/ldap.py index b4f21affa..6333f0350 100644 --- a/apps/settings/utils/ldap.py +++ b/apps/settings/utils/ldap.py @@ -400,11 +400,14 @@ class LDAPImportUtil(object): logger.info('Start perform import ldap users, count: {}'.format(len(users))) errors = [] objs = [] + new_users = [] group_users_mapper = defaultdict(set) for user in users: groups = user.pop('groups', []) try: obj, created = self.update_or_create(user) + if created: + new_users.append(obj) objs.append(obj) except Exception as e: errors.append({user['username']: str(e)}) @@ -421,7 +424,7 @@ class LDAPImportUtil(object): for org in orgs: self.bind_org(org, objs, group_users_mapper) logger.info('End perform import ldap users') - return errors + return new_users, errors def exit_user_group(self, user_groups_mapper): # 通过对比查询本次导入用户需要移除的用户组 diff --git a/apps/terminal/api/component/endpoint.py b/apps/terminal/api/component/endpoint.py index b40aba9aa..9b573fc58 100644 --- a/apps/terminal/api/component/endpoint.py +++ b/apps/terminal/api/component/endpoint.py @@ -42,7 +42,7 @@ class SmartEndpointViewMixin: return endpoint def match_endpoint_by_label(self): - return Endpoint.match_by_instance_label(self.target_instance, self.target_protocol) + return Endpoint.match_by_instance_label(self.target_instance, self.target_protocol, self.request) def match_endpoint_by_target_ip(self): target_ip = self.request.GET.get('target_ip', '') # 支持target_ip参数,用来方便测试 diff --git a/apps/terminal/models/component/endpoint.py b/apps/terminal/models/component/endpoint.py index d9d4cfab8..cdb9b0135 100644 --- a/apps/terminal/models/component/endpoint.py +++ b/apps/terminal/models/component/endpoint.py @@ -75,7 +75,20 @@ class Endpoint(JMSBaseModel): return endpoint @classmethod - def match_by_instance_label(cls, instance, protocol): + def handle_endpoint_host(cls, endpoint, request=None): + if not endpoint.host and request: + # 动态添加 current request host + host_port = request.get_host() + # IPv6 + if host_port.startswith('['): + host = host_port.split(']:')[0].rstrip(']') + ']' + else: + host = host_port.split(':')[0] + endpoint.host = host + return endpoint + + @classmethod + def match_by_instance_label(cls, instance, protocol, request=None): from assets.models import Asset from terminal.models import Session if isinstance(instance, Session): @@ -88,6 +101,7 @@ class Endpoint(JMSBaseModel): endpoints = cls.objects.filter(name__in=list(values)).order_by('-date_updated') for endpoint in endpoints: if endpoint.is_valid_for(instance, protocol): + endpoint = cls.handle_endpoint_host(endpoint, request) return endpoint @@ -130,13 +144,5 @@ class EndpointRule(JMSBaseModel): endpoint = endpoint_rule.endpoint else: endpoint = Endpoint.get_or_create_default(request) - if not endpoint.host and request: - # 动态添加 current request host - host_port = request.get_host() - # IPv6 - if host_port.startswith('['): - host = host_port.split(']:')[0].rstrip(']') + ']' - else: - host = host_port.split(':')[0] - endpoint.host = host + endpoint = Endpoint.handle_endpoint_host(endpoint, request) return endpoint diff --git a/apps/tickets/api/__init__.py b/apps/tickets/api/__init__.py index 645133d8e..1c66b843b 100644 --- a/apps/tickets/api/__init__.py +++ b/apps/tickets/api/__init__.py @@ -5,3 +5,4 @@ from .ticket import * from .comment import * from .relation import * from .super_ticket import * +from .perms import * diff --git a/apps/tickets/api/perms.py b/apps/tickets/api/perms.py new file mode 100644 index 000000000..fb7d7a138 --- /dev/null +++ b/apps/tickets/api/perms.py @@ -0,0 +1,66 @@ +from django.conf import settings + +from assets.models import Asset, Node +from assets.serializers.asset.common import MiniAssetSerializer +from assets.serializers.node import NodeSerializer +from common.api import SuggestionMixin +from orgs.mixins.api import OrgReadonlyModelViewSet +from perms.utils import AssetPermissionPermAssetUtil +from perms.utils.permission import AssetPermissionUtil +from tickets.const import TicketApplyAssetScope + +__all__ = ['ApplyAssetsViewSet', 'ApplyNodesViewSet'] + + +class ApplyAssetsViewSet(OrgReadonlyModelViewSet, SuggestionMixin): + model = Asset + serializer_class = MiniAssetSerializer + rbac_perms = ( + ("match", "assets.match_asset"), + ) + + search_fields = ("name", "address", "comment") + + def get_queryset(self): + if TicketApplyAssetScope.is_permed(): + queryset = self.get_assets(with_expired=True) + elif TicketApplyAssetScope.is_permed_valid(): + queryset = self.get_assets() + else: + queryset = super().get_queryset() + return queryset + + def get_assets(self, with_expired=False): + perms = AssetPermissionUtil().get_permissions_for_user( + self.request.user, flat=True, with_expired=with_expired + ) + util = AssetPermissionPermAssetUtil(perms) + assets = util.get_all_assets() + return assets + + +class ApplyNodesViewSet(OrgReadonlyModelViewSet, SuggestionMixin): + model = Node + serializer_class = NodeSerializer + rbac_perms = ( + ("match", "assets.match_node"), + ) + + search_fields = ('full_value',) + + def get_queryset(self): + if TicketApplyAssetScope.is_permed(): + queryset = self.get_nodes(with_expired=True) + elif TicketApplyAssetScope.is_permed_valid(): + queryset = self.get_nodes() + else: + queryset = super().get_queryset() + return queryset + + def get_nodes(self, with_expired=False): + perms = AssetPermissionUtil().get_permissions_for_user( + self.request.user, flat=True, with_expired=with_expired + ) + util = AssetPermissionPermAssetUtil(perms) + nodes = util.get_perm_nodes() + return nodes diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index da60aefe8..ebac1c65a 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.exceptions import MethodNotAllowed +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from audits.handler import create_or_update_operate_log @@ -41,7 +42,6 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): ordering = ('-date_created',) rbac_perms = { 'open': 'tickets.view_ticket', - 'bulk': 'tickets.change_ticket', } def retrieve(self, request, *args, **kwargs): @@ -122,7 +122,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): self._record_operate_log(instance, TicketAction.close) return Response('ok') - @action(detail=False, methods=[PUT], permission_classes=[RBACPermission, ]) + @action(detail=False, methods=[PUT], permission_classes=[IsAuthenticated, ]) def bulk(self, request, *args, **kwargs): self.ticket_not_allowed() diff --git a/apps/tickets/const.py b/apps/tickets/const.py index a2a5ec981..09c1b39e4 100644 --- a/apps/tickets/const.py +++ b/apps/tickets/const.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.db.models import TextChoices, IntegerChoices from django.utils.translation import gettext_lazy as _ @@ -56,3 +57,21 @@ class TicketApprovalStrategy(TextChoices): custom_user = 'custom_user', _("Custom user") super_admin = 'super_admin', _("Super admin") super_org_admin = 'super_org_admin', _("Super admin and org admin") + + +class TicketApplyAssetScope(TextChoices): + all = 'all', _("All assets") + permed = 'permed', _("Permed assets") + permed_valid = 'permed_valid', _('Permed valid assets') + + @classmethod + def get_scope(cls): + return settings.TICKET_APPLY_ASSET_SCOPE.lower() + + @classmethod + def is_permed(cls): + return cls.get_scope() == cls.permed + + @classmethod + def is_permed_valid(cls): + return cls.get_scope() == cls.permed_valid diff --git a/apps/tickets/models/ticket/general.py b/apps/tickets/models/ticket/general.py index f7c78a4a1..656a8322a 100644 --- a/apps/tickets/models/ticket/general.py +++ b/apps/tickets/models/ticket/general.py @@ -57,7 +57,7 @@ class TicketStep(JMSBaseModel): assignees.update(state=state) self.status = StepStatus.closed self.state = state - self.save(update_fields=['state', 'status']) + self.save(update_fields=['state', 'status', 'date_updated']) def set_active(self): self.status = StepStatus.active diff --git a/apps/tickets/urls/api_urls.py b/apps/tickets/urls/api_urls.py index 9cc72d815..88b203d84 100644 --- a/apps/tickets/urls/api_urls.py +++ b/apps/tickets/urls/api_urls.py @@ -16,6 +16,8 @@ router.register('apply-login-tickets', api.ApplyLoginTicketViewSet, 'apply-login router.register('apply-command-tickets', api.ApplyCommandTicketViewSet, 'apply-command-ticket') router.register('apply-login-asset-tickets', api.ApplyLoginAssetTicketViewSet, 'apply-login-asset-ticket') router.register('ticket-session-relation', api.TicketSessionRelationViewSet, 'ticket-session-relation') +router.register('apply-assets', api.ApplyAssetsViewSet, 'ticket-session-relation') +router.register('apply-nodes', api.ApplyNodesViewSet, 'ticket-session-relation') urlpatterns = [ path('tickets//session/', api.TicketSessionApi.as_view(), name='ticket-session'), diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 290c0c1f1..f31ec6690 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -729,7 +729,7 @@ class JSONFilterMixin: bindings = RoleBinding.objects.filter(**kwargs, role__in=value) if match == 'm2m_all': - user_id = bindings.values('user_id').annotate(count=Count('user_id')) \ + user_id = bindings.values('user_id').annotate(count=Count('user_id', distinct=True)) \ .filter(count=len(value)).values_list('user_id', flat=True) else: user_id = bindings.values_list('user_id', flat=True) diff --git a/apps/users/serializers/group.py b/apps/users/serializers/group.py index 0c6470d42..54e47eb5b 100644 --- a/apps/users/serializers/group.py +++ b/apps/users/serializers/group.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from django.db.models import Count +from django.db.models import Count, Q from django.utils.translation import gettext_lazy as _ from rest_framework import serializers @@ -46,7 +46,7 @@ class UserGroupSerializer(ResourceLabelsMixin, BulkOrgResourceModelSerializer): def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ queryset = queryset.prefetch_related('labels', 'labels__label') \ - .annotate(users_amount=Count('users')) + .annotate(users_amount=Count('users', distinct=True, filter=Q(users__is_service_account=False))) return queryset diff --git a/apps/users/signal_handlers.py b/apps/users/signal_handlers.py index 6e53bb87b..8e1f92254 100644 --- a/apps/users/signal_handlers.py +++ b/apps/users/signal_handlers.py @@ -163,9 +163,9 @@ def on_openid_create_or_update_user(sender, request, user, created, name, userna user.save() -@shared_task(verbose_name=_('Clean audits session task log')) +@shared_task(verbose_name=_('Clean up expired user sessions')) @register_as_period_task(crontab=CRONTAB_AT_PM_TWO) -def clean_audits_log_period(): +def clean_expired_user_session_period(): UserSession.clear_expired_sessions() diff --git a/apps/users/tasks.py b/apps/users/tasks.py index 638e3bdec..7fd707ac7 100644 --- a/apps/users/tasks.py +++ b/apps/users/tasks.py @@ -86,7 +86,7 @@ def check_user_expired_periodic(): @tmp_to_root_org() def check_unused_users(): uncommon_users_ttl = settings.SECURITY_UNCOMMON_USERS_TTL - if not uncommon_users_ttl or not uncommon_users_ttl.isdigit(): + if not uncommon_users_ttl: return uncommon_users_ttl = int(uncommon_users_ttl) diff --git a/apps/users/templates/users/forgot_password.html b/apps/users/templates/users/forgot_password.html index 64137ea8a..cdb32dca2 100644 --- a/apps/users/templates/users/forgot_password.html +++ b/apps/users/templates/users/forgot_password.html @@ -7,6 +7,7 @@ .margin-bottom { margin-bottom: 15px; } + .input-style { width: 100%; display: inline-block; @@ -22,6 +23,19 @@ height: 100%; vertical-align: top; } + + .scrollable-menu { + height: auto; + max-height: 18rem; + overflow-x: hidden; + } + + .input-group { + .input-group-btn .btn-secondary { + color: #464a4c; + background-color: #eceeef; + } + } {% endblock %} {% block html_title %}{% trans 'Forgot password' %}{% endblock %} @@ -57,9 +71,26 @@ placeholder="{% trans 'Email account' %}" value="{{ email }}">
- - {{ form.sms.help_text }} +
+
+ + +
+ +
diff --git a/apps/users/views/profile/otp.py b/apps/users/views/profile/otp.py index a42a7a513..aca1251fc 100644 --- a/apps/users/views/profile/otp.py +++ b/apps/users/views/profile/otp.py @@ -1,10 +1,14 @@ # ~*~ coding: utf-8 ~*~ +import os +from django.conf import settings from django.contrib.auth import logout as auth_logout from django.http.response import HttpResponseRedirect from django.shortcuts import redirect +from django.templatetags.static import static from django.urls import reverse from django.utils.translation import gettext as _ +from django.utils._os import safe_join from django.views.generic.base import TemplateView from django.views.generic.edit import FormView @@ -45,9 +49,26 @@ class UserOtpEnableStartView(AuthMixin, TemplateView): class UserOtpEnableInstallAppView(TemplateView): template_name = 'users/user_otp_enable_install_app.html' + @staticmethod + def replace_authenticator_png(platform): + media_url = settings.MEDIA_URL + base_path = f'img/authenticator_{platform}.png' + authenticator_media_path = safe_join(settings.MEDIA_ROOT, base_path) + if os.path.exists(authenticator_media_path): + authenticator_url = f'{media_url}{base_path}' + else: + authenticator_url = static(base_path) + return authenticator_url + def get_context_data(self, **kwargs): user = get_user_or_pre_auth_user(self.request) - context = {'user': user} + authenticator_android_url = self.replace_authenticator_png('android') + authenticator_iphone_url = self.replace_authenticator_png('iphone') + context = { + 'user': user, + 'authenticator_android_url': authenticator_android_url, + 'authenticator_iphone_url': authenticator_iphone_url + } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/apps/users/views/profile/reset.py b/apps/users/views/profile/reset.py index b2eed3cdd..554a2ca88 100644 --- a/apps/users/views/profile/reset.py +++ b/apps/users/views/profile/reset.py @@ -13,6 +13,7 @@ from django.views.generic import FormView, RedirectView from authentication.errors import IntervalTooShort from authentication.utils import check_user_property_is_correct +from common.const.choices import COUNTRY_CALLING_CODES from common.utils import FlashMessageUtil, get_object_or_none, random_string from common.utils.verify_code import SendAndVerifyCodeUtil from users.notifications import ResetPasswordSuccessMsg @@ -108,7 +109,7 @@ class UserForgotPasswordView(FormView): for k, v in cleaned_data.items(): if v: context[k] = v - + context['countries'] = COUNTRY_CALLING_CODES context['form_type'] = 'email' context['XPACK_ENABLED'] = settings.XPACK_ENABLED validate_backends = self.get_validate_backends_context(has_phone) diff --git a/config_example.yml b/config_example.yml index 03cc0aa03..232ac32d8 100644 --- a/config_example.yml +++ b/config_example.yml @@ -85,7 +85,7 @@ REDIS_PORT: 6379 # SECURITY_WATERMARK_ENABLED: False # 浏览器关闭页面后,会话过期 -# SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE: False +# SESSION_EXPIRE_AT_BROWSER_CLOSE: False # 每次api请求,session续期 # SESSION_SAVE_EVERY_REQUEST: True diff --git a/poetry.lock b/poetry.lock index 579edd025..e46f32e6e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -730,17 +730,16 @@ reference = "tsinghua" [[package]] name = "azure-core" -version = "1.29.6" +version = "1.29.7" description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "azure-core-1.29.6.tar.gz", hash = "sha256:13b485252ecd9384ae624894fe51cfa6220966207264c360beada239f88b738a"}, - {file = "azure_core-1.29.6-py3-none-any.whl", hash = "sha256:604a005bce6a49ba661bb7b2be84a9b169047e52fcfcd0a4e4770affab4178f7"}, + {file = "azure-core-1.29.7.tar.gz", hash = "sha256:2944faf1a7ff1558b1f457cabf60f279869cabaeef86b353bed8eb032c7d8c5e"}, + {file = "azure_core-1.29.7-py3-none-any.whl", hash = "sha256:95a7b41b4af102e5fcdfac9500fcc82ff86e936c7145a099b7848b9ac0501250"}, ] [package.dependencies] -anyio = ">=3.0,<5.0" requests = ">=2.21.0" six = ">=1.11.0" typing-extensions = ">=4.6.0" @@ -961,19 +960,22 @@ reference = "tsinghua" [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] @@ -1062,6 +1064,22 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +optional = false +python-versions = "*" +files = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "cachetools" version = "5.3.2" @@ -1811,6 +1829,22 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "distro" version = "1.9.0" @@ -1895,7 +1929,7 @@ reference = "tsinghua" [[package]] name = "django-cas-ng" version = "4.3.0" -description = "Django CAS 1.0/2.0/3.0 client authentication library, support Django 2.2, 3.0, 3.1, 3.2, 4.0 and Python 3.7+" +description = "" optional = false python-versions = ">=3.7" files = [ @@ -2228,22 +2262,23 @@ reference = "tsinghua" [[package]] name = "dnspython" -version = "2.4.2" +version = "2.5.0" description = "DNS toolkit" optional = false -python-versions = ">=3.8,<4.0" +python-versions = ">=3.8" files = [ - {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, - {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, + {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"}, + {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"}, ] [package.extras] -dnssec = ["cryptography (>=2.6,<42.0)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=5.0.3)", "mypy (>=1.0.1)", "pylint (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "sphinx (>=7.0.0)", "twine (>=4.0.0)", "wheel (>=0.41.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.25.1)"] doq = ["aioquic (>=0.9.20)"] -idna = ["idna (>=2.1,<4.0)"] -trio = ["trio (>=0.14,<0.23)"] -wmi = ["wmi (>=1.5.1,<2.0.0)"] +idna = ["idna (>=2.1)"] +trio = ["trio (>=0.14)"] +wmi = ["wmi (>=1.5.1)"] [package.source] type = "legacy" @@ -2528,6 +2563,41 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "exchangelib" +version = "5.1.0" +description = "Client for Microsoft Exchange Web Services (EWS)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "exchangelib-5.1.0-py2.py3-none-any.whl", hash = "sha256:28f492df84c3d74b1183d535d34daa8c7d7a80fe69cf6b35041770824781f4b0"}, + {file = "exchangelib-5.1.0.tar.gz", hash = "sha256:f835c14d380839dc74f53664f091cafc978a158855877a510c0c5554b85cdbac"}, +] + +[package.dependencies] +cached-property = "*" +defusedxml = ">=0.6.0" +dnspython = ">=2.2.0" +isodate = "*" +lxml = ">3.0" +oauthlib = "*" +pygments = "*" +requests = ">=2.7" +requests-ntlm = ">=0.2.0" +requests-oauthlib = "*" +tzdata = "*" +tzlocal = "*" + +[package.extras] +complete = ["requests-gssapi", "requests-negotiate-sspi"] +kerberos = ["requests-gssapi"] +sspi = ["requests-negotiate-sspi"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "executing" version = "2.0.1" @@ -2789,13 +2859,13 @@ reference = "tsinghua" [[package]] name = "google-auth" -version = "2.25.2" +version = "2.27.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.25.2.tar.gz", hash = "sha256:42f707937feb4f5e5a39e6c4f343a17300a459aaf03141457ba505812841cc40"}, - {file = "google_auth-2.25.2-py2.py3-none-any.whl", hash = "sha256:473a8dfd0135f75bb79d878436e568f2695dce456764bf3a02b6f8c540b1d256"}, + {file = "google-auth-2.27.0.tar.gz", hash = "sha256:e863a56ccc2d8efa83df7a80272601e43487fa9a728a376205c86c26aaefa821"}, + {file = "google_auth-2.27.0-py2.py3-none-any.whl", hash = "sha256:8e4bad367015430ff253fe49d500fdc3396c1a434db5740828c728e45bcce245"}, ] [package.dependencies] @@ -3515,12 +3585,12 @@ reference = "tsinghua" [[package]] name = "jms-storage" -version = "0.0.53" +version = "0.0.55" description = "Jumpserver storage python sdk tools" optional = false python-versions = "*" files = [ - {file = "jms-storage-0.0.53.tar.gz", hash = "sha256:d197816b3edc6a370892d5a246265ea7ae7267159b1a4ad88a14ae56e92013be"}, + {file = "jms-storage-0.0.55.tar.gz", hash = "sha256:cab3ac01b9733dd30101b59dd21de104543355c010ca0024cfaed3069e392cfa"}, ] [package.dependencies] @@ -3718,106 +3788,96 @@ reference = "tsinghua" [[package]] name = "lxml" -version = "5.0.0" +version = "5.1.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +python-versions = ">=3.6" files = [ - {file = "lxml-5.0.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73bfab795d354aaf2f4eb7a5b0db513031734fd371047342d5803834ce19ec18"}, - {file = "lxml-5.0.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cb564bbe55ff0897d9cf1225041a44576d7ae87f06fd60163544c91de2623d3f"}, - {file = "lxml-5.0.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a5501438dd521bb7e0dde5008c40c7bfcfaafaf86eccb3f9bd27509abb793da"}, - {file = "lxml-5.0.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7ba26a7dc929a1b3487d51bbcb0099afed2fc06e891b82845c8f37a2d7d7fbbd"}, - {file = "lxml-5.0.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:9b59c429e1a2246da86ae237ffc3565efcdc71c281cd38ca8b44d5fb6a3b993a"}, - {file = "lxml-5.0.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:3ffa066db40b0347e48334bd4465de768e295a3525b9a59831228b5f4f93162d"}, - {file = "lxml-5.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8ce8b468ab50f9e944719d1134709ec11fe0d2840891a6cae369e22141b1094c"}, - {file = "lxml-5.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:583c0e15ae06adc81035346ae2abb2e748f0b5197e7740d8af31222db41bbf7b"}, - {file = "lxml-5.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:904d36165848b59c4e04ae5b969072e602bd987485076fca8ec42c6cd7a7aedc"}, - {file = "lxml-5.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ac21aace6712472e77ea9dfc38329f53830c4259ece54c786107105ebb069053"}, - {file = "lxml-5.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f92d73faa0b1a76d1932429d684b7ce95829e93c3eef3715ec9b98ab192c9d31"}, - {file = "lxml-5.0.0-cp310-cp310-win32.whl", hash = "sha256:03290e2f714f2e7431c8430c08b48167f657da7bc689c6248e828ff3c66d5b1b"}, - {file = "lxml-5.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:3e6cbb68bf70081f036bfc018649cf4b46c4e7eaf7860a277cae92dee2a57f69"}, - {file = "lxml-5.0.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5382612ba2424cea5d2c89e2c29077023d8de88f8d60d5ceff5f76334516df9e"}, - {file = "lxml-5.0.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:07a900735bad9af7be3085480bf384f68ed5580ba465b39a098e6a882c060d6b"}, - {file = "lxml-5.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:980ba47c8db4b9d870014c7040edb230825b79017a6a27aa54cdb6fcc02d8cc0"}, - {file = "lxml-5.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6507c58431dbd95b50654b3313c5ad54f90e54e5f2cdacf733de61eae478eec5"}, - {file = "lxml-5.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4a45a278518e4308865c1e9dbb2c42ce84fb154efb03adeb16fdae3c1687c7c9"}, - {file = "lxml-5.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:59cea9ba1c675fbd6867ca1078fc717a113e7f5b7644943b74137b7cc55abebf"}, - {file = "lxml-5.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd39ef87fd1f7bb5c4aa53454936e6135cbfe03fe3744e8218be193f9e4fef16"}, - {file = "lxml-5.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e6bb39d91bf932e7520cb5718ae3c2f498052aca53294d5d59fdd9068fe1a7f2"}, - {file = "lxml-5.0.0-cp311-cp311-win32.whl", hash = "sha256:21af2c3862db6f4f486cddf73ec1157b40d5828876c47cd880edcbad8240ea1b"}, - {file = "lxml-5.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:c1249aa4eaced30b59ecf8b8cae0b1ccede04583c74ca7d10b6f8bbead908b2c"}, - {file = "lxml-5.0.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f30e697b6215e759d0824768b2c5b0618d2dc19abe6c67eeed2b0460f52470d1"}, - {file = "lxml-5.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d1bb64646480c36a4aa1b6a44a5b6e33d0fcbeab9f53f1b39072cd3bb2c6243a"}, - {file = "lxml-5.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4e69c36c8618707a90ed3fb6f48a6cc9254ffcdbf7b259e439a5ae5fbf9c5206"}, - {file = "lxml-5.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9ca498f8554a09fbc3a2f8fc4b23261e07bc27bef99b3df98e2570688033f6fc"}, - {file = "lxml-5.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0326e9b8176ea77269fb39e7af4010906e73e9496a9f8eaf06d253b1b1231ceb"}, - {file = "lxml-5.0.0-cp312-cp312-win32.whl", hash = "sha256:5fb988e15378d6e905ca8f60813950a0c56da9469d0e8e5d8fe785b282684ec5"}, - {file = "lxml-5.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:bb58e8f4b2cfe012cd312239b8d5139995fe8f5945c7c26d5fbbbb1ddb9acd47"}, - {file = "lxml-5.0.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81509dffd8aba3bdb43e90cbd218c9c068a1f4047d97bc9546b3ac9e3a4ae81d"}, - {file = "lxml-5.0.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e675a4b95208e74c34ac0751cc4bab9170e7728b61601fb0f4746892c2bb7e0b"}, - {file = "lxml-5.0.0-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:405e3760f83a8ba3bdb6e622ec79595cdc20db916ce37377bbcb95b5711fa4ca"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f15844a1b93dcaa09c2b22e22a73384f3ae4502347c3881cfdd674e14ac04e21"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88f559f8beb6b90e41a7faae4aca4c8173a4819874a9bf8e74c8d7c1d51f3162"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e8c63f5c7d87e7044880b01851ac4e863c3349e6f6b6ab456fe218d9346e816d"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:0d277d4717756fe8816f0beeff229cb72f9dd02a43b70e1d3f07c8efadfb9fe1"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8954da15403db1acfc0544b3c3f963a6ef4e428283ab6555e3e298bbbff1cf6"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:aebd8fd378e074b22e79cad329dcccd243c40ff1cafaa512d19276c5bb9554e1"}, - {file = "lxml-5.0.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b6d4e148edee59c2ad38af15810dbcb8b5d7b13e5de3509d8cf3edfe74c0adca"}, - {file = "lxml-5.0.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:70ab4e02f7aa5fb4131c8b222a111ce7676f3767e36084fba3a4e7338dc82dcd"}, - {file = "lxml-5.0.0-cp36-cp36m-win32.whl", hash = "sha256:de1a8b54170024cf1c0c2718c82412bca42cd82e390556e3d8031af9541b416f"}, - {file = "lxml-5.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5b39f63edbe7e018c2ac1cf0259ee0dd2355274e8a3003d404699b040782e55e"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:77b73952534967a4497d9e4f26fbeebfba19950cbc66b7cc3a706214429d8106"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8cc0a951e5616ac626f7036309c41fb9774adcd4aa7db0886463da1ce5b65edb"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4b9d5b01900a760eb3acf6cef50aead4ef2fa79e7ddb927084244e41dfe37b65"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:173bcead3af5d87c7bca9a030675073ddaad8e0a9f0b04be07cd9390453e7226"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:44fa9afd632210f1eeda51cf284ed8dbab0c7ec8b008dd39ba02818e0e114e69"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fef10f27d6318d2d7c88680e113511ddecf09ee4f9559b3623b73ee89fa8f6cc"}, - {file = "lxml-5.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3663542aee845129a981889c19b366beab0b1dadcf5ca164696aabfe1aa51667"}, - {file = "lxml-5.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7188495c1bf71bfda87d78ed50601e72d252119ce11710d6e71ff36e35fea5a0"}, - {file = "lxml-5.0.0-cp37-cp37m-win32.whl", hash = "sha256:6a2de85deabf939b0af89e2e1ea46bfb1239545e2da6f8ac96522755a388025f"}, - {file = "lxml-5.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ea56825c1e23c9c8ea385a191dac75f9160477057285b88c88736d9305e6118f"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:3f908afd0477cace17f941d1b9cfa10b769fe1464770abe4cfb3d9f35378d0f8"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52a9ab31853d3808e7cf0183b3a5f7e8ffd622ea4aee1deb5252dbeaefd5b40d"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c7fe19abb3d3c55a9e65d289b12ad73b3a31a3f0bda3c539a890329ae9973bd6"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:1ef0793e1e2dd221fce7c142177008725680f7b9e4a184ab108d90d5d3ab69b7"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:581a78f299a9f5448b2c3aea904bfcd17c59bf83016d221d7f93f83633bb2ab2"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:affdd833f82334fdb10fc9a1c7b35cdb5a86d0b672b4e14dd542e1fe7bcea894"}, - {file = "lxml-5.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6bba06d8982be0f0f6432d289a8d104417a0ab9ed04114446c4ceb6d4a40c65d"}, - {file = "lxml-5.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:80209b31dd3908bc5b014f540fd192c97ea52ab179713a730456c5baf7ce80c1"}, - {file = "lxml-5.0.0-cp38-cp38-win32.whl", hash = "sha256:dac2733fe4e159b0aae0439db6813b7b1d23ff96d0b34c0107b87faf79208c4e"}, - {file = "lxml-5.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:ee60f33456ff34b2dd1d048a740a2572798356208e4c494301c931de3a0ab3a2"}, - {file = "lxml-5.0.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:5eff173f0ff408bfa578cbdafd35a7e0ca94d1a9ffe09a8a48e0572d0904d486"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:78d6d8e5b54ed89dc0f0901eaaa579c384ad8d59fa43cc7fb06e9bb89115f8f4"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:71a7cee869578bc17b18050532bb2f0bc682a7b97dda77041741a1bd2febe6c7"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7df433d08d4587dc3932f7fcfc3194519a6824824104854e76441fd3bc000d29"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:793be9b4945c2dfd69828fb5948d7d9569b78e0599e4a2e88d92affeb0ff3aa3"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7cfb6af73602c8d288581df8a225989d7e9d5aab0a174be0e19fcfa800b6797"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bfdc4668ac56687a89ca3eca44231144a2e9d02ba3b877558db74ba20e2bd9fa"}, - {file = "lxml-5.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2992591e2294bb07faf7f5f6d5cb60710c046404f4bfce09fb488b85d2a8f58f"}, - {file = "lxml-5.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4786b0af7511ea614fd86407a52a7bc161aa5772d311d97df2591ed2351de768"}, - {file = "lxml-5.0.0-cp39-cp39-win32.whl", hash = "sha256:016de3b29a262655fc3d2075dc1b2611f84f4c3d97a71d579c883d45e201eee4"}, - {file = "lxml-5.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:52c0acc2f29b0a204efc11a5ed911a74f50a25eb7d7d5069c2b1fd3b3346ce11"}, - {file = "lxml-5.0.0-pp310-pypy310_pp73-macosx_11_0_x86_64.whl", hash = "sha256:96095bfc0c02072fc89afa67626013a253596ea5118b8a7f4daaae049dafa096"}, - {file = "lxml-5.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:992029258ed719f130d5a9c443d142c32843046f1263f2c492862b2a853be570"}, - {file = "lxml-5.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:db40e85cffd22f7d65dcce30e85af565a66401a6ed22fc0c56ed342cfa4ffc43"}, - {file = "lxml-5.0.0-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:cfa8a4cdc3765574b7fd0c7cfa5fbd1e2108014c9dfd299c679e5152bea9a55e"}, - {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:049fef98d02513c34f5babd07569fc1cf1ed14c0f2fbff18fe72597f977ef3c2"}, - {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a85136d0ee18a41c91cc3e2844c683be0e72e6dda4cb58da9e15fcaef3726af7"}, - {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:766868f729f3ab84125350f1a0ea2594d8b1628a608a574542a5aff7355b9941"}, - {file = "lxml-5.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:99cad5c912f359e59e921689c04e54662cdd80835d80eeaa931e22612f515df7"}, - {file = "lxml-5.0.0-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:c90c593aa8dd57d5dab0ef6d7d64af894008971d98e6a41b320fdd75258fbc6e"}, - {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8134d5441d1ed6a682e3de3d7a98717a328dce619ee9c4c8b3b91f0cb0eb3e28"}, - {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f298ac9149037d6a3d5c74991bded39ac46292520b9c7c182cb102486cc87677"}, - {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:894c5f71186b410679aaab5774543fcb9cbabe8893f0b31d11cf28a0740e80be"}, - {file = "lxml-5.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9cd3d6c2c67d4fdcd795e4945e2ba5434909c96640b4cc09453bd0dc7e8e1bac"}, - {file = "lxml-5.0.0.zip", hash = "sha256:2219cbf790e701acf9a21a31ead75f983e73daf0eceb9da6990212e4d20ebefe"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, + {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, + {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, + {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, + {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, + {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, + {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, + {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, + {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, + {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, + {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, + {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, + {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, + {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, + {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, + {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, + {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, + {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, + {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, + {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, + {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, + {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, + {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, + {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, + {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, + {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.7)"] +source = ["Cython (>=3.0.8)"] [package.source] type = "legacy" @@ -3851,16 +3911,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -3919,59 +3969,59 @@ reference = "tsinghua" [[package]] name = "maxminddb" -version = "2.5.1" +version = "2.5.2" description = "Reader for the MaxMind DB format" optional = false python-versions = ">=3.8" files = [ - {file = "maxminddb-2.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:62e93a8e99937bf4307eeece3ca37e1161325ebf9363c4ce195410fb5daf64a0"}, - {file = "maxminddb-2.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea2e27a507b53dfbf2ba2ba85c98682a1ad2dac3f9941a7bffa5cb86150d0c47"}, - {file = "maxminddb-2.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a01b0341bd6bee431bb8c07c7ac0ed221250c7390b125c025b7d57578e78e8a3"}, - {file = "maxminddb-2.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:607344b1079ea647629bf962dcea7580ec864faaad3f5aae650e2e8652121d89"}, - {file = "maxminddb-2.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c2901daebd7c8a702302315e7a58cdc38e626406ad4a05b4d48634897d5f5a3"}, - {file = "maxminddb-2.5.1-cp310-cp310-win32.whl", hash = "sha256:7805ae8c9de433c38939ada2e376706a9f6740239f61fd445927b88f5b42c267"}, - {file = "maxminddb-2.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:f1e5bd58b71f322dc6c16a95a129433b1bc229d4b714f870a61c2367425396ee"}, - {file = "maxminddb-2.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0bbbd58b300aaddf985f763720bdebba9f7a73168ff9f57168117f630ad1c06"}, - {file = "maxminddb-2.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a6751e2e89d62d53217870bcc2a8c887dc56ae370ba1b74e52e880761916e54"}, - {file = "maxminddb-2.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ecb1be961f1969be047d07743093f0dcf2f6d4ec3a06a4555587f380a96f6e7"}, - {file = "maxminddb-2.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1e091c2b44673c218ee2df23adbc0b6d04fd5c646cfcb6c6fe26fb849434812a"}, - {file = "maxminddb-2.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09b295c401c104ae0e30f66c1a3f3c2aa4ba2cbe12a787576499356a5a4d6c1"}, - {file = "maxminddb-2.5.1-cp311-cp311-win32.whl", hash = "sha256:3d52c693baf07bba897d109b0ecb067f21fd0cc0fb266d67db456e85b80d699e"}, - {file = "maxminddb-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:4c67621e842c415ce336ab019a9f087305dfcf24c095b68b8e9d27848f6f6d91"}, - {file = "maxminddb-2.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17ea454f61631b9815d420d48d00663f8718fc7de30be53ffcec0f73989475eb"}, - {file = "maxminddb-2.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef4d508c899ce0f37de731340759c68bfd1102a39a873675c71fae2c8d71ad97"}, - {file = "maxminddb-2.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e5ca423b1e310f0327536f5ed1a2c6e08d83289a7f909e021590b0b477cae2"}, - {file = "maxminddb-2.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0a21abd85e10e5e0f60244b49c3db17e7e48befd4972e62a62833d91e2acbb49"}, - {file = "maxminddb-2.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:85a302d79577efe5bc308647394ffdc535dd5f062644c41103604ccf24931a05"}, - {file = "maxminddb-2.5.1-cp312-cp312-win32.whl", hash = "sha256:dd28c434fb44f825dde6a75df2c338d44645791b03480af66a4d993f93801e10"}, - {file = "maxminddb-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:b477852cf1741d9187b021e23723e64b063794bbf946a9b5b84cc222f3caf58a"}, - {file = "maxminddb-2.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a1e1a19f9740f586362f47862d0095b54d50b9d465babcaa8a563746132fe5be"}, - {file = "maxminddb-2.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d654895b546a47e85f2e071b98e377a60bb03cd643b9423017fa66fcd5adedce"}, - {file = "maxminddb-2.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0702da59b9670a72761b65cb1a52bc3032d8f6799bdab641cb8350ad5740580b"}, - {file = "maxminddb-2.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2e20a70c1545d6626dcd4ce2d7ecf3d566d978ea64cb37e7952f93baff66b812"}, - {file = "maxminddb-2.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0cbd272db3202e948c9088e48dec62add071a47971d84ceb11d2cb2880f83e5a"}, - {file = "maxminddb-2.5.1-cp38-cp38-win32.whl", hash = "sha256:fbd01fc7d7b5b2befe914e8cdb5ed3a1c5476e57b765197cceff8d897f33d012"}, - {file = "maxminddb-2.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:fe0af3ba9e1a78ed5f2ad32fc18d18b78ef233e7d0c627e1a77a525a7eb0c241"}, - {file = "maxminddb-2.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d772be68cce812f7c4b15ae8c68e624c8b88ff83071e3903ca5b5f55e343c25"}, - {file = "maxminddb-2.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910e7b3ad87d5352ed3f496bd42bffbf9f896245278b0d8e76afa1382e42a7ae"}, - {file = "maxminddb-2.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:892c11a8694394e97d3ac0f8d5974ea588c732d14e721f22095c58b4f584c144"}, - {file = "maxminddb-2.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3ce1f42bdfce7b86cb5a56cba730fed611fb879d867e6024f0d520257bef6891"}, - {file = "maxminddb-2.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6667948e7501a513caef90edda2d367865097239d4c2381eb3998e9905af7209"}, - {file = "maxminddb-2.5.1-cp39-cp39-win32.whl", hash = "sha256:500d321bdefe4dcd351e4390a79b7786aab49b0536bedfa0788e5ffb0e91e421"}, - {file = "maxminddb-2.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:93f7055779caf7753810f1e2c6444af6d727393fd116ffa0767fbd54fb8c9bbf"}, - {file = "maxminddb-2.5.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8cee4315da7cdd3f2a18f1ab1418953a7a9eda65e63095b01f03c7d3645d633e"}, - {file = "maxminddb-2.5.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97eac5af102cede4b5f57cecb25e8f949fa4e4a8d812bed575539951c60ecaf"}, - {file = "maxminddb-2.5.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:526744b12075051fa20979090c111cc3a42a3b55e2714818270c7b84a41a8cfe"}, - {file = "maxminddb-2.5.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fad45cd2f2e3c5fbebacb8d172a60fb22443222e549bf740a0bc7eeb849e5ce7"}, - {file = "maxminddb-2.5.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b98ed5c34955c48e72d35daed713ba4a6833a8a6d1204e79d2c85e644049792"}, - {file = "maxminddb-2.5.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:639aee8abd63a95baa12b94b6f3a842d51877d631879c7d08c98c68dc44a84c3"}, - {file = "maxminddb-2.5.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a7a73ab4bbc16b81983531c99fa102a0c7dae459db958c17fea48c981f5e764"}, - {file = "maxminddb-2.5.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:aae262da1940a67c3ba765c49e2308947ce68ff647f87630002c306433a98ca1"}, - {file = "maxminddb-2.5.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b223c53077a736c304b63cf5afceb928975fbd12ddae5afd6b71370bab7b4700"}, - {file = "maxminddb-2.5.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:969d0057ea5472e0b574c5293c4f3ecf49585362351c543e8ea55dc48b60f1eb"}, - {file = "maxminddb-2.5.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4d36cf3d390f02d2bdf53d9efefb92be7bd70e07a5a86cdb79020c48c2d81b7"}, - {file = "maxminddb-2.5.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:188173c07dce0692fd5660a6eb7ea8c126d7b3a4b61496c8a8ee9e8b10186ff5"}, - {file = "maxminddb-2.5.1.tar.gz", hash = "sha256:4807d374e645bd68334e4f487ba85a27189dbc1267a98e644aa686a7927e0559"}, + {file = "maxminddb-2.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f5682963a5817066db50f219c33aaa7eb969888211a289a444c42b5dfa0c0f78"}, + {file = "maxminddb-2.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fe6bb1b5ea132fcd9fd7b16c80247f0ba667018d5f9f98cd645b297e3b02fbf"}, + {file = "maxminddb-2.5.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a3ec4b161e872cc615b7a09ae9770049e9794e7b3832e3d78905a65c5049d"}, + {file = "maxminddb-2.5.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29d63e7711e5f95c7c190010e57dca9e262aee8ac300aaf75c3f7ede0b5a5863"}, + {file = "maxminddb-2.5.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:08a540ec3661f6ca40499c86028e96dca5780e9d471b485dc797859b0b22dd22"}, + {file = "maxminddb-2.5.2-cp310-cp310-win32.whl", hash = "sha256:17fdb691c389a0e956410d5baef9ad082a0aa67dd6aa231d193499e71a104c19"}, + {file = "maxminddb-2.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:d71b48d3dff9150a44e949b28fa5e7251a7a6895a3a77e200ce08410f096f12f"}, + {file = "maxminddb-2.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1409a045eb04cebb297221eab1020c4f05434d02c0961410f6996ef474482998"}, + {file = "maxminddb-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d839c480e4b93bb37bb1cc2777d77e6b2127c006e60b56f748f10571d8b0e471"}, + {file = "maxminddb-2.5.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bca70905515fe50684974a9afaa7db4a4e9fbfdebcb0c2cde9db8e048e0d8145"}, + {file = "maxminddb-2.5.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:67f97cd0c6aac39a51294b04a1e922532125285c24b18a58e2a9c92c7691fa9f"}, + {file = "maxminddb-2.5.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a3fab6bea6cc59444e6bad2a4fbf91228f6f51dcb29d09ed091930a475bd8cb"}, + {file = "maxminddb-2.5.2-cp311-cp311-win32.whl", hash = "sha256:a99e3125528ea31e807f80e8c5b65118dc5cc122d0a435f1691a3cc1df55840c"}, + {file = "maxminddb-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:b6adf63695fa5e3d2549f7c2c9d82c6d252edd5c6ba67074637d2cb944143673"}, + {file = "maxminddb-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ed504ca9f3c42e8e71bdbe21f5b818139a1448ac15d7bb6ce12cf41e3b7e2067"}, + {file = "maxminddb-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a5053231228d7cbf57d98a741b3cbee9efa9e689348dbb56c414e5a4c7f6f1c"}, + {file = "maxminddb-2.5.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7e8688342bab592647313cd2054779bcd35ad85933424ceae9f07e3a9779986"}, + {file = "maxminddb-2.5.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:335ee3140b41d4e751c14f8fae297aa064c7d3f184c9fbb2790336123187c440"}, + {file = "maxminddb-2.5.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b0203fa2731da45e5461f6e8a0768e85bba8e02137a1598b3fcadf7cbfe8e6f2"}, + {file = "maxminddb-2.5.2-cp312-cp312-win32.whl", hash = "sha256:8b89129de70e1629f200df9dfda4e4f477c26b05c29e0836604a00209c9466d5"}, + {file = "maxminddb-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:099f4e27feec4bb9658034a3eb853e746721fc15709030bee4f2f889f4a34185"}, + {file = "maxminddb-2.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:19d8d1e9bbc5281fb4c8112d541d2bd350fd8b5ddfbb43a6951e46df7cd27b9d"}, + {file = "maxminddb-2.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94183a78628cad257183a88ce12a3bb9ffbfe0544bd0c1aafc1f9dc55629dd1b"}, + {file = "maxminddb-2.5.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17de49660372dcccaa23958eccdd1c2464f92f594d027045ad76788db14a5da4"}, + {file = "maxminddb-2.5.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae05c4f87b1dd9a21d430c52451eef5f3bd5af609d093408db91fe0dc4d8d7d1"}, + {file = "maxminddb-2.5.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cb718908b9dffa10e02361094158ae68ded5a82c750de89737437999a81bafe"}, + {file = "maxminddb-2.5.2-cp38-cp38-win32.whl", hash = "sha256:e0faa0c4c458eb0eb2f267daa7b106baef72c3c7ebcbece00b9e974fc8321412"}, + {file = "maxminddb-2.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:bac5a29fdc5df9222f7baecbcc4a88b309a66a7d147b34160940c0850ee4b9c5"}, + {file = "maxminddb-2.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c204f53ef7c1d77e9fb0dba415dbb56419f2b08ccaca66cd772e29b3a793c3e7"}, + {file = "maxminddb-2.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae98508a200db6f7ae5985a53039aba8eef7ed71d34b0a0e9c9145c3e6139fc3"}, + {file = "maxminddb-2.5.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e9198d25e252b27d4e9526d5fcd4b78341c23153363a94f1246de5afcd39f6d"}, + {file = "maxminddb-2.5.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b85b008f8e2cf3abfabdc24041549c51c97ea9a8bc46eeeadac8cec7acf9fbf0"}, + {file = "maxminddb-2.5.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6f50210506e9818162ef6706d3127efb0575dfe2cc98a7236ca2011f1cc3effe"}, + {file = "maxminddb-2.5.2-cp39-cp39-win32.whl", hash = "sha256:2bba43d370a57785f5ef61c10d0b4bf8de58d431da3c4c2ed78bb2ff3d07edbf"}, + {file = "maxminddb-2.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:2e01b09480b97d2ebe6765618fb12a0f52caa17368d6cf1f42481d6740428de7"}, + {file = "maxminddb-2.5.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dd47d13376eaee2e8d1a1fb55d3d6ccdcc995bc931699967f7d5670ec6a454a3"}, + {file = "maxminddb-2.5.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abd626efaba4f0bc867462337f846796da0bb97b82125dbdbc63067947e353b0"}, + {file = "maxminddb-2.5.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ddbe547d83a2e28e81d9f59fd9708d3044ffb2398ee0f8df2e2a2e9cdea6646"}, + {file = "maxminddb-2.5.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:22184fa2514c15f5b39e4e2522f4f73d00afcf5eb7102c473f9376f3c3a03b81"}, + {file = "maxminddb-2.5.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5cb6702fbcc5b209ac3cffacd9cf0a5155feabbeb6fdcf497038be7cb6e52da6"}, + {file = "maxminddb-2.5.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0c3ebfc0af00445089629faffa4c5a1fcc42a1ca5d7dffc42bba314fde20c6d"}, + {file = "maxminddb-2.5.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:461dcf0a4f67aa1c9faea6d52c4060d39559bf68e99a514cf8c1e01af383f90b"}, + {file = "maxminddb-2.5.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e012e889639aab411f5483990188da51c968377f665dcb90584971dbf314d50a"}, + {file = "maxminddb-2.5.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:20596e452d03071db37a72c8ef9236126c04ed342864f68db0adf0d1bc9f642e"}, + {file = "maxminddb-2.5.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ec51b66774b102824c9a3dd4916356283f6a61db1868d4ebcb98bf26486718e"}, + {file = "maxminddb-2.5.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fda0dd512f345cc92492f96c61a0df47efc2e2064c15e8053ab2114b362d64d"}, + {file = "maxminddb-2.5.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:862fcfe226ebda29a537cdce678dc8dc71ca6540ad2483099f80c6a1ee4cdbdd"}, + {file = "maxminddb-2.5.2.tar.gz", hash = "sha256:b3c33e4fc7821ee6c9f40837116e16ab6175863d4a64eee024c5bec686690a87"}, ] [package.dependencies] @@ -4274,13 +4324,13 @@ reference = "tsinghua" [[package]] name = "netaddr" -version = "0.10.0" +version = "0.10.1" description = "A network address manipulation library for Python" optional = false python-versions = "*" files = [ - {file = "netaddr-0.10.0-py2.py3-none-any.whl", hash = "sha256:8752f96c8fc24162edbf5b73d3e464b5d88e62869917582daa37b2695b65afb4"}, - {file = "netaddr-0.10.0.tar.gz", hash = "sha256:4c30c54adf4ea4318b3c055ea3d8c7f6554a50aa2cd8aea4605a23caa0b0229e"}, + {file = "netaddr-0.10.1-py2.py3-none-any.whl", hash = "sha256:9822305b42ea1020d54fee322d43cee5622b044c07a1f0130b459bb467efcf88"}, + {file = "netaddr-0.10.1.tar.gz", hash = "sha256:f4da4222ca8c3f43c8e18a8263e5426c750a3a837fdfeccf74c68d0408eaa3bf"}, ] [package.source] @@ -4370,13 +4420,13 @@ reference = "tsinghua" [[package]] name = "openai" -version = "1.6.1" +version = "1.9.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.6.1-py3-none-any.whl", hash = "sha256:bc9f774838d67ac29fb24cdeb2d58faf57de8b311085dcd1348f7aa02a96c7ee"}, - {file = "openai-1.6.1.tar.gz", hash = "sha256:d553ca9dbf9486b08e75b09e8671e4f638462aaadccfced632bf490fc3d75fa2"}, + {file = "openai-1.9.0-py3-none-any.whl", hash = "sha256:5774a0582ed82f6de92200ed5024e03e272b93e04e9d31caeda5fb80f63df50d"}, + {file = "openai-1.9.0.tar.gz", hash = "sha256:3e9947a544556c051fa138a4def5bd8b468364ec52803c6628532ab949ddce55"}, ] [package.dependencies] @@ -4499,13 +4549,13 @@ reference = "tsinghua" [[package]] name = "oslo-config" -version = "9.2.0" +version = "9.3.0" description = "Oslo Configuration API" optional = false python-versions = ">=3.8" files = [ - {file = "oslo.config-9.2.0-py3-none-any.whl", hash = "sha256:b98e50b19161fc76f25905ff74043e239258a3ebe799a5f9070d285e3c039dee"}, - {file = "oslo.config-9.2.0.tar.gz", hash = "sha256:ffeb01ca65a603d5525905f1a88a3319be09ce2c6ac376c4312aaec283095878"}, + {file = "oslo.config-9.3.0-py3-none-any.whl", hash = "sha256:5642e75ab8070aee96563670b1c1ee3b6f3cac3c0302fe7fc78973cd4b4e3d29"}, + {file = "oslo.config-9.3.0.tar.gz", hash = "sha256:a4b1e526135d67c0e9b14d3ed299c6ec8a3887f92afcb26f4f3ea918504a3554"}, ] [package.dependencies] @@ -4547,13 +4597,13 @@ reference = "tsinghua" [[package]] name = "oslo-serialization" -version = "5.2.0" +version = "5.3.0" description = "Oslo Serialization library" optional = false python-versions = ">=3.8" files = [ - {file = "oslo.serialization-5.2.0-py3-none-any.whl", hash = "sha256:c7ec759192a787c7e1a5e765920bb594752c75e6e0cd5a9a82c385a9088125e5"}, - {file = "oslo.serialization-5.2.0.tar.gz", hash = "sha256:9cf030d61a6cce1f47a62d4050f5e83e1bd1a1018ac671bb193aee07d15bdbc2"}, + {file = "oslo.serialization-5.3.0-py3-none-any.whl", hash = "sha256:0da7248d0e515b875ef9883e3631ff51f9a8d11e8576247f0ded890f3276c0bf"}, + {file = "oslo.serialization-5.3.0.tar.gz", hash = "sha256:228898f4f33b7deabc74289b32bbd302a659c39cf6dda9048510f930fc4f76ed"}, ] [package.dependencies] @@ -4570,13 +4620,13 @@ reference = "tsinghua" [[package]] name = "oslo-utils" -version = "6.3.0" +version = "7.0.0" description = "Oslo Utility library" optional = false python-versions = ">=3.8" files = [ - {file = "oslo.utils-6.3.0-py3-none-any.whl", hash = "sha256:6bac2e56650f502caae6c0e8ba6e5eda3d7a16743d115f8836cad54538dd667f"}, - {file = "oslo.utils-6.3.0.tar.gz", hash = "sha256:758d945b2bad5bea81abed80ad33ffea1d1d793348ac5eb5b3866ba745b11d55"}, + {file = "oslo.utils-7.0.0-py3-none-any.whl", hash = "sha256:dbb724041a2ea0c342d524c4d7c7f07c8bc5016f4762d38c6a41b2ef805b3a8e"}, + {file = "oslo.utils-7.0.0.tar.gz", hash = "sha256:5263c00980cfab74f6635ef61d0fc91e6bd4a8dd0e78a77897ed6e447c8c6731"}, ] [package.dependencies] @@ -4966,22 +5016,22 @@ reference = "tsinghua" [[package]] name = "protobuf" -version = "4.25.1" +version = "4.25.2" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.1-cp310-abi3-win32.whl", hash = "sha256:193f50a6ab78a970c9b4f148e7c750cfde64f59815e86f686c22e26b4fe01ce7"}, - {file = "protobuf-4.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:3497c1af9f2526962f09329fd61a36566305e6c72da2590ae0d7d1322818843b"}, - {file = "protobuf-4.25.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:0bf384e75b92c42830c0a679b0cd4d6e2b36ae0cf3dbb1e1dfdda48a244f4bcd"}, - {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:0f881b589ff449bf0b931a711926e9ddaad3b35089cc039ce1af50b21a4ae8cb"}, - {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:ca37bf6a6d0046272c152eea90d2e4ef34593aaa32e8873fc14c16440f22d4b7"}, - {file = "protobuf-4.25.1-cp38-cp38-win32.whl", hash = "sha256:abc0525ae2689a8000837729eef7883b9391cd6aa7950249dcf5a4ede230d5dd"}, - {file = "protobuf-4.25.1-cp38-cp38-win_amd64.whl", hash = "sha256:1484f9e692091450e7edf418c939e15bfc8fc68856e36ce399aed6889dae8bb0"}, - {file = "protobuf-4.25.1-cp39-cp39-win32.whl", hash = "sha256:8bdbeaddaac52d15c6dce38c71b03038ef7772b977847eb6d374fc86636fa510"}, - {file = "protobuf-4.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:becc576b7e6b553d22cbdf418686ee4daa443d7217999125c045ad56322dda10"}, - {file = "protobuf-4.25.1-py3-none-any.whl", hash = "sha256:a19731d5e83ae4737bb2a089605e636077ac001d18781b3cf489b9546c7c80d6"}, - {file = "protobuf-4.25.1.tar.gz", hash = "sha256:57d65074b4f5baa4ab5da1605c02be90ac20c8b40fb137d6a8df9f416b0d0ce2"}, + {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, + {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, + {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, + {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, + {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, + {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, + {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, + {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, + {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, ] [package.source] @@ -5775,11 +5825,9 @@ files = [ {file = "pymssql-2.2.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:049f2e3de919e8e02504780a21ebbf235e21ca8ed5c7538c5b6e705aa6c43d8c"}, {file = "pymssql-2.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd86d8e3e346e34f3f03d12e333747b53a1daa74374a727f4714d5b82ee0dd5"}, {file = "pymssql-2.2.8-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:508226a0df7cb6faeda9f8e84e85743690ca427d7b27af9a73d75fcf0c1eef6e"}, - {file = "pymssql-2.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:47859887adeaf184766b5e0bc845dd23611f3808f9521552063bb36eabc10092"}, {file = "pymssql-2.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d873e553374d5b1c57fe1c43bb75e3bcc2920678db1ef26f6bfed396c7d21b30"}, {file = "pymssql-2.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf31b8b76634c826a91f9999e15b7bfb0c051a0f53b319fd56481a67e5b903bb"}, {file = "pymssql-2.2.8-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:821945c2214fe666fd456c61e09a29a00e7719c9e136c801bffb3a254e9c579b"}, - {file = "pymssql-2.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:cc85b609b4e60eac25fa38bbac1ff854fd2c2a276e0ca4a3614c6f97efb644bb"}, {file = "pymssql-2.2.8-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:ebe7f64d5278d807f14bea08951e02512bfbc6219fd4d4f15bb45ded885cf3d4"}, {file = "pymssql-2.2.8-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:253af3d39fc0235627966817262d5c4c94ad09dcbea59664748063470048c29c"}, {file = "pymssql-2.2.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c9d109df536dc5f7dd851a88d285a4c9cb12a9314b621625f4f5ab1197eb312"}, @@ -5795,13 +5843,11 @@ files = [ {file = "pymssql-2.2.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3906993300650844ec140aa58772c0f5f3e9e9d5709c061334fd1551acdcf066"}, {file = "pymssql-2.2.8-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7309c7352e4a87c9995c3183ebfe0ff4135e955bb759109637673c61c9f0ca8d"}, {file = "pymssql-2.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9b8d603cc1ec7ae585c5a409a1d45e8da067970c79dd550d45c238ae0aa0f79f"}, - {file = "pymssql-2.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:293cb4d0339e221d877d6b19a1905082b658f0100a1e2ccc9dda10de58938901"}, {file = "pymssql-2.2.8-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:895041edd002a2e91d8a4faf0906b6fbfef29d9164bc6beb398421f5927fa40e"}, {file = "pymssql-2.2.8-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6b2d9c6d38a416c6f2db36ff1cd8e69f9a5387a46f9f4f612623192e0c9404b1"}, {file = "pymssql-2.2.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63d6f25cf40fe6a03c49be2d4d337858362b8ab944d6684c268e4990807cf0c"}, {file = "pymssql-2.2.8-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c83ad3ad20951f3a94894b354fa5fa9666dcd5ebb4a635dad507c7d1dd545833"}, {file = "pymssql-2.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3933f7f082be74698eea835df51798dab9bc727d94d3d280bffc75ab9265f890"}, - {file = "pymssql-2.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:de313375b90b0f554058992f35c4a4beb3f6ec2f5912d8cd6afb649f95b03a9f"}, {file = "pymssql-2.2.8.tar.gz", hash = "sha256:9baefbfbd07d0142756e2dfcaa804154361ac5806ab9381350aad4e780c3033e"}, ] @@ -6290,7 +6336,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -6298,15 +6343,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -6323,7 +6361,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -6331,7 +6368,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -6595,13 +6631,13 @@ reference = "tsinghua" [[package]] name = "service-identity" -version = "23.1.0" +version = "24.1.0" description = "Service identity verification for pyOpenSSL & cryptography." optional = false python-versions = ">=3.8" files = [ - {file = "service_identity-23.1.0-py3-none-any.whl", hash = "sha256:87415a691d52fcad954a500cb81f424d0273f8e7e3ee7d766128f4575080f383"}, - {file = "service_identity-23.1.0.tar.gz", hash = "sha256:ecb33cd96307755041e978ab14f8b14e13b40f1fbd525a4dc78f46d2b986431d"}, + {file = "service_identity-24.1.0-py3-none-any.whl", hash = "sha256:a28caf8130c8a5c1c7a6f5293faaf239bbfb7751e4862436920ee6f2616f568a"}, + {file = "service_identity-24.1.0.tar.gz", hash = "sha256:6829c9d62fb832c2e1c435629b0a8c476e1929881f28bee4d20bc24161009221"}, ] [package.dependencies] @@ -6611,7 +6647,7 @@ pyasn1 = "*" pyasn1-modules = "*" [package.extras] -dev = ["pyopenssl", "service-identity[docs,idna,mypy,tests]"] +dev = ["pyopenssl", "service-identity[idna,mypy,tests]"] docs = ["furo", "myst-parser", "pyopenssl", "sphinx", "sphinx-notfound-page"] idna = ["idna"] mypy = ["idna", "mypy", "types-pyopenssl"] @@ -7051,13 +7087,13 @@ reference = "tsinghua" [[package]] name = "traitlets" -version = "5.14.0" +version = "5.14.1" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" files = [ - {file = "traitlets-5.14.0-py3-none-any.whl", hash = "sha256:f14949d23829023013c47df20b4a76ccd1a85effb786dc060f34de7948361b33"}, - {file = "traitlets-5.14.0.tar.gz", hash = "sha256:fcdaa8ac49c04dfa0ed3ee3384ef6dfdb5d6f3741502be247279407679296772"}, + {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"}, + {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"}, ] [package.extras] @@ -7218,6 +7254,28 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "tzlocal" +version = "5.2" +description = "tzinfo object for the local timezone" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, + {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, +] + +[package.dependencies] +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "ua-parser" version = "0.18.0" @@ -7371,13 +7429,13 @@ reference = "tsinghua" [[package]] name = "wcwidth" -version = "0.2.12" +version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"}, - {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"}, + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] [package.source] @@ -7827,4 +7885,4 @@ reference = "tsinghua" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "5474eb8b2e55c714fc812ea637bf83fe40c5acaadebccc10072e30101ab9ba13" +content-hash = "3f228326a11742303de60a3b1bb8f748b6efc5a3dae1aa200e8a5cc6c9df9c0a" diff --git a/pyproject.toml b/pyproject.toml index 68e86ca27..b8c7fc0c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ pynacl = "1.5.0" python-dateutil = "2.8.2" pyyaml = "6.0.1" requests = "2.31.0" -jms-storage = "0.0.53" +jms-storage = "0.0.55" simplejson = "3.19.1" six = "1.16.0" sshtunnel = "0.4.0" @@ -146,6 +146,8 @@ django-cors-headers = "^4.3.0" mistune = "2.0.3" openai = "^1.3.7" xlsxwriter = "^3.1.9" +exchangelib = "^5.1.0" +xmlsec = "^1.3.13" polib = "^1.2.0" tqdm = "^4.66.1" diff --git a/utils/start_celery_beat.py b/utils/start_celery_beat.py index 19d578e70..2f089a4af 100644 --- a/utils/start_celery_beat.py +++ b/utils/start_celery_beat.py @@ -1,13 +1,12 @@ #!/usr/bin/env python # import os -import sys import signal import subprocess +import sys import redis_lock - -from redis import Redis, Sentinel +from redis import Redis, Sentinel, ConnectionPool BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) APPS_DIR = os.path.join(BASE_DIR, 'apps') @@ -50,9 +49,15 @@ if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS: ) redis_client = sentinel_client.master_for(REDIS_SENTINEL_SERVICE_NAME) else: - connection_params['host'] = settings.REDIS_HOST - connection_params['port'] = settings.REDIS_PORT - redis_client = Redis(**connection_params) + REDIS_PROTOCOL = 'rediss' if settings.REDIS_USE_SSL else 'redis' + REDIS_LOCATION_NO_DB = '%(protocol)s://:%(password)s@%(host)s:%(port)s' % { + 'protocol': REDIS_PROTOCOL, + 'password': settings.REDIS_PASSWORD, + 'host': settings.REDIS_HOST, + 'port': settings.REDIS_PORT, + } + pool = ConnectionPool.from_url(REDIS_LOCATION_NO_DB, **connection_params) + redis_client = Redis(connection_pool=pool) scheduler = "ops.celery.beat.schedulers:DatabaseScheduler" processes = []