From c34b7e69eb0f3a8e8238832f9b7e36823e6cb3c5 Mon Sep 17 00:00:00 2001
From: jiangweidong
Date: Mon, 30 Jan 2023 17:33:10 +0800
Subject: [PATCH 01/92] =?UTF-8?q?fix:=20mongodb=E6=97=A0=E6=8C=87=E5=AE=9A?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E6=97=B6=EF=BC=8C=E4=B8=80=E4=BA=9B?=
=?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E4=BB=BB=E5=8A=A1=E4=BC=9A=E5=A4=B1?=
=?UTF-8?q?=E8=B4=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/assets/serializers/asset/database.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/apps/assets/serializers/asset/database.py b/apps/assets/serializers/asset/database.py
index c4ad0171d..9ef2232a7 100644
--- a/apps/assets/serializers/asset/database.py
+++ b/apps/assets/serializers/asset/database.py
@@ -1,3 +1,6 @@
+from rest_framework.serializers import ValidationError
+from django.utils.translation import ugettext_lazy as _
+
from assets.models import Database
from .common import AssetSerializer
from ..gateway import GatewayWithAccountSecretSerializer
@@ -14,6 +17,13 @@ class DatabaseSerializer(AssetSerializer):
]
fields = AssetSerializer.Meta.fields + extra_fields
+ def validate(self, attrs):
+ platform = attrs.get('platform')
+ if platform and getattr(platform, 'name') == 'MongoDB' \
+ and not attrs.get('db_name'):
+ raise ValidationError({'db_name': _('This field is required.')})
+ return attrs
+
class DatabaseWithGatewaySerializer(DatabaseSerializer):
gateway = GatewayWithAccountSecretSerializer()
From 87ca9cb11d004fd60f11a4393a27de3f0d43d2d4 Mon Sep 17 00:00:00 2001
From: jiangweidong
Date: Mon, 30 Jan 2023 20:26:07 +0800
Subject: [PATCH 02/92] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8type?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/assets/serializers/asset/database.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/assets/serializers/asset/database.py b/apps/assets/serializers/asset/database.py
index 9ef2232a7..4c00c486c 100644
--- a/apps/assets/serializers/asset/database.py
+++ b/apps/assets/serializers/asset/database.py
@@ -19,7 +19,7 @@ class DatabaseSerializer(AssetSerializer):
def validate(self, attrs):
platform = attrs.get('platform')
- if platform and getattr(platform, 'name') == 'MongoDB' \
+ if platform and getattr(platform, 'type') == 'mongodb' \
and not attrs.get('db_name'):
raise ValidationError({'db_name': _('This field is required.')})
return attrs
From 382201188c8490da96b847725129f3554248a538 Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 10:22:25 +0800
Subject: [PATCH 03/92] =?UTF-8?q?perf:=20admin=20user=20=E4=B8=8D=E8=83=BD?=
=?UTF-8?q?=E5=88=A0=E9=99=A4=EF=BC=8Cxpack=20=20=E5=BC=95=E7=94=A8?=
=?UTF-8?q?=E7=9D=80=EF=BC=8C=E4=B8=8D=E7=A1=AE=E5=AE=9A=E9=A1=BA=E5=BA=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../migrations/0104_auto_20220817_1544.py | 3 --
apps/assets/models/_user.py | 53 ++++++++++++++-----
2 files changed, 40 insertions(+), 16 deletions(-)
diff --git a/apps/assets/migrations/0104_auto_20220817_1544.py b/apps/assets/migrations/0104_auto_20220817_1544.py
index 864eba8f1..972c34e09 100644
--- a/apps/assets/migrations/0104_auto_20220817_1544.py
+++ b/apps/assets/migrations/0104_auto_20220817_1544.py
@@ -52,9 +52,6 @@ class Migration(migrations.Migration):
migrations.DeleteModel(
name='Cluster',
),
- migrations.DeleteModel(
- name='AdminUser',
- ),
migrations.DeleteModel(
name='HistoricalAuthBook',
),
diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py
index d7809a2b3..019147456 100644
--- a/apps/assets/models/_user.py
+++ b/apps/assets/models/_user.py
@@ -17,7 +17,23 @@ __all__ = ['SystemUser']
logger = logging.getLogger(__name__)
-class SystemUser(OrgModelMixin):
+class OldBaseUser(models.Model):
+ id = models.UUIDField(default=uuid.uuid4, primary_key=True)
+ name = models.CharField(max_length=128, verbose_name=_('Name'))
+ username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
+ password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
+ private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
+ public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
+ comment = models.TextField(blank=True, verbose_name=_('Comment'))
+ date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
+ date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
+ created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
+
+ class Meta:
+ abstract = True
+
+
+class SystemUser(OrgModelMixin, OldBaseUser):
LOGIN_AUTO = 'auto'
LOGIN_MANUAL = 'manual'
LOGIN_MODE_CHOICES = (
@@ -29,19 +45,7 @@ class SystemUser(OrgModelMixin):
common = 'common', _('Common user')
admin = 'admin', _('Admin user')
- id = models.UUIDField(default=uuid.uuid4, primary_key=True)
- name = models.CharField(max_length=128, verbose_name=_('Name'))
- username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
- password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
- private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
- public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
token = models.TextField(default='', verbose_name=_('Token'))
-
- comment = models.TextField(blank=True, verbose_name=_('Comment'))
- date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
- date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
- created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
-
username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user"))
type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type'))
priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)])
@@ -66,3 +70,26 @@ class SystemUser(OrgModelMixin):
permissions = [
('match_systemuser', _('Can match system user')),
]
+
+
+# Deprecated: 准备废弃
+class AdminUser(OrgModelMixin, OldBaseUser):
+ """
+ A privileged user that ansible can use it to push system user and so on
+ """
+ BECOME_METHOD_CHOICES = (
+ ('sudo', 'sudo'),
+ ('su', 'su'),
+ )
+ become = models.BooleanField(default=True)
+ become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
+ become_user = models.CharField(default='root', max_length=64)
+ _become_pass = models.CharField(default='', blank=True, max_length=128)
+
+ def __str__(self):
+ return self.name
+
+ class Meta:
+ ordering = ['name']
+ unique_together = [('name', 'org_id')]
+ verbose_name = _("Admin user")
\ No newline at end of file
From 0c1048ed89f1f7d46bbc48a3f9d39e6867ff2e5b Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 10:42:55 +0800
Subject: [PATCH 04/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20migrations?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../migrations/0093_auto_20220403_1627.py | 18 ++++++++++++------
apps/assets/migrations/0107_automation.py | 18 ++----------------
2 files changed, 14 insertions(+), 22 deletions(-)
diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py
index ee536497e..71d88f773 100644
--- a/apps/assets/migrations/0093_auto_20220403_1627.py
+++ b/apps/assets/migrations/0093_auto_20220403_1627.py
@@ -71,12 +71,18 @@ class Migration(migrations.Migration):
),
migrations.AlterModelOptions(
name='asset',
- options={'ordering': ['name'],
- 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'),
- ('test_assetconnectivity', 'Can test asset connectivity'),
- ('push_assetsystemuser', 'Can push system user to asset'),
- ('match_asset', 'Can match asset'), ('add_assettonode', 'Add asset to node'),
- ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'},
+ options={
+ 'ordering': ['name'],
+ 'permissions': [
+ ('refresh_assethardwareinfo', 'Can refresh asset hardware info'),
+ ('test_assetconnectivity', 'Can test asset connectivity'),
+ ('push_assetaccount', 'Can push account to asset'),
+ ('test_account', 'Can verify account'), ('match_asset', 'Can match asset'),
+ ('add_assettonode', 'Add asset to node'),
+ ('move_assettonode', 'Move asset to node')
+ ],
+ 'verbose_name': 'Asset'
+ },
),
migrations.RenameField(
model_name='asset',
diff --git a/apps/assets/migrations/0107_automation.py b/apps/assets/migrations/0107_automation.py
index 38bb777e4..56c2cf4eb 100644
--- a/apps/assets/migrations/0107_automation.py
+++ b/apps/assets/migrations/0107_automation.py
@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
],
options={
'verbose_name': 'Automation task',
- 'unique_together': {('org_id', 'name')},
+ 'unique_together': {('org_id', 'name', 'type')},
},
),
migrations.CreateModel(
@@ -93,18 +93,4 @@ class Migration(migrations.Migration):
name='automation',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation task'),
),
- migrations.AlterUniqueTogether(
- name='baseautomation',
- unique_together={('org_id', 'name', 'type')},
- ),
- migrations.AlterModelOptions(
- name='asset',
- options={'ordering': ['name'],
- 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'),
- ('test_assetconnectivity', 'Can test asset connectivity'),
- ('push_assetaccount', 'Can push account to asset'),
- ('test_account', 'Can verify account'), ('match_asset', 'Can match asset'),
- ('add_assettonode', 'Add asset to node'),
- ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'},
- ),
-]
+ ]
From 3702ba92ead51c187722732a69a4294522676c64 Mon Sep 17 00:00:00 2001
From: fit2bot <68588906+fit2bot@users.noreply.github.com>
Date: Tue, 31 Jan 2023 11:00:21 +0800
Subject: [PATCH 05/92] perf: celery task delete (#9372)
Co-authored-by: feng <1304903146@qq.com>
---
apps/ops/api/celery.py | 22 ++++++++++------------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py
index f55c296f9..ba6de12b4 100644
--- a/apps/ops/api/celery.py
+++ b/apps/ops/api/celery.py
@@ -1,31 +1,26 @@
# -*- coding: utf-8 -*-
#
-
import os
import re
+from celery.result import AsyncResult
+from rest_framework import generics, viewsets, mixins
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _
-from rest_framework import viewsets
-from celery.result import AsyncResult
-from rest_framework import generics
from django_celery_beat.models import PeriodicTask
-
from common.permissions import IsValidUser
-from common.api import LogTailApi
+from common.api import LogTailApi, CommonApiMixin
from ..models import CeleryTaskExecution, CeleryTask
-from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer
from ..celery.utils import get_celery_task_log_path
from ..ansible.utils import get_ansible_task_log_path
-from common.api import CommonApiMixin
+from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer
+from ..serializers.celery import CeleryTaskSerializer, CeleryTaskExecutionSerializer
__all__ = [
'CeleryTaskExecutionLogApi', 'CeleryResultApi', 'CeleryPeriodTaskViewSet',
'AnsibleTaskLogApi', 'CeleryTaskViewSet', 'CeleryTaskExecutionViewSet'
]
-from ..serializers.celery import CeleryTaskSerializer, CeleryTaskExecutionSerializer
-
class CeleryTaskExecutionLogApi(LogTailApi):
permission_classes = (IsValidUser,)
@@ -103,9 +98,12 @@ class CelerySummaryAPIView(generics.RetrieveAPIView):
pass
-class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet):
+class CeleryTaskViewSet(
+ CommonApiMixin, mixins.RetrieveModelMixin,
+ mixins.ListModelMixin, mixins.DestroyModelMixin,
+ viewsets.GenericViewSet
+):
serializer_class = CeleryTaskSerializer
- http_method_names = ('get', 'head', 'options',)
def get_queryset(self):
return CeleryTask.objects.exclude(name__startswith='celery')
From d5cc2e77b224aaa0d843b41c2b9ee975cc16854f Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 10:22:25 +0800
Subject: [PATCH 06/92] =?UTF-8?q?perf:=20admin=20user=20=E4=B8=8D=E8=83=BD?=
=?UTF-8?q?=E5=88=A0=E9=99=A4=EF=BC=8Cxpack=20=20=E5=BC=95=E7=94=A8?=
=?UTF-8?q?=E7=9D=80=EF=BC=8C=E4=B8=8D=E7=A1=AE=E5=AE=9A=E9=A1=BA=E5=BA=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../migrations/0104_auto_20220817_1544.py | 3 --
apps/assets/models/_user.py | 53 ++++++++++++++-----
2 files changed, 40 insertions(+), 16 deletions(-)
diff --git a/apps/assets/migrations/0104_auto_20220817_1544.py b/apps/assets/migrations/0104_auto_20220817_1544.py
index 864eba8f1..972c34e09 100644
--- a/apps/assets/migrations/0104_auto_20220817_1544.py
+++ b/apps/assets/migrations/0104_auto_20220817_1544.py
@@ -52,9 +52,6 @@ class Migration(migrations.Migration):
migrations.DeleteModel(
name='Cluster',
),
- migrations.DeleteModel(
- name='AdminUser',
- ),
migrations.DeleteModel(
name='HistoricalAuthBook',
),
diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py
index d7809a2b3..019147456 100644
--- a/apps/assets/models/_user.py
+++ b/apps/assets/models/_user.py
@@ -17,7 +17,23 @@ __all__ = ['SystemUser']
logger = logging.getLogger(__name__)
-class SystemUser(OrgModelMixin):
+class OldBaseUser(models.Model):
+ id = models.UUIDField(default=uuid.uuid4, primary_key=True)
+ name = models.CharField(max_length=128, verbose_name=_('Name'))
+ username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
+ password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
+ private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
+ public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
+ comment = models.TextField(blank=True, verbose_name=_('Comment'))
+ date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
+ date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
+ created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
+
+ class Meta:
+ abstract = True
+
+
+class SystemUser(OrgModelMixin, OldBaseUser):
LOGIN_AUTO = 'auto'
LOGIN_MANUAL = 'manual'
LOGIN_MODE_CHOICES = (
@@ -29,19 +45,7 @@ class SystemUser(OrgModelMixin):
common = 'common', _('Common user')
admin = 'admin', _('Admin user')
- id = models.UUIDField(default=uuid.uuid4, primary_key=True)
- name = models.CharField(max_length=128, verbose_name=_('Name'))
- username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
- password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
- private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
- public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
token = models.TextField(default='', verbose_name=_('Token'))
-
- comment = models.TextField(blank=True, verbose_name=_('Comment'))
- date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
- date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
- created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
-
username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user"))
type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type'))
priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)])
@@ -66,3 +70,26 @@ class SystemUser(OrgModelMixin):
permissions = [
('match_systemuser', _('Can match system user')),
]
+
+
+# Deprecated: 准备废弃
+class AdminUser(OrgModelMixin, OldBaseUser):
+ """
+ A privileged user that ansible can use it to push system user and so on
+ """
+ BECOME_METHOD_CHOICES = (
+ ('sudo', 'sudo'),
+ ('su', 'su'),
+ )
+ become = models.BooleanField(default=True)
+ become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
+ become_user = models.CharField(default='root', max_length=64)
+ _become_pass = models.CharField(default='', blank=True, max_length=128)
+
+ def __str__(self):
+ return self.name
+
+ class Meta:
+ ordering = ['name']
+ unique_together = [('name', 'org_id')]
+ verbose_name = _("Admin user")
\ No newline at end of file
From b34af62ec1505ffc140adda1bc99461cd8ad3b45 Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 10:42:55 +0800
Subject: [PATCH 07/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20migrations?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../migrations/0093_auto_20220403_1627.py | 18 ++++++++++++------
apps/assets/migrations/0107_automation.py | 18 ++----------------
2 files changed, 14 insertions(+), 22 deletions(-)
diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py
index ee536497e..71d88f773 100644
--- a/apps/assets/migrations/0093_auto_20220403_1627.py
+++ b/apps/assets/migrations/0093_auto_20220403_1627.py
@@ -71,12 +71,18 @@ class Migration(migrations.Migration):
),
migrations.AlterModelOptions(
name='asset',
- options={'ordering': ['name'],
- 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'),
- ('test_assetconnectivity', 'Can test asset connectivity'),
- ('push_assetsystemuser', 'Can push system user to asset'),
- ('match_asset', 'Can match asset'), ('add_assettonode', 'Add asset to node'),
- ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'},
+ options={
+ 'ordering': ['name'],
+ 'permissions': [
+ ('refresh_assethardwareinfo', 'Can refresh asset hardware info'),
+ ('test_assetconnectivity', 'Can test asset connectivity'),
+ ('push_assetaccount', 'Can push account to asset'),
+ ('test_account', 'Can verify account'), ('match_asset', 'Can match asset'),
+ ('add_assettonode', 'Add asset to node'),
+ ('move_assettonode', 'Move asset to node')
+ ],
+ 'verbose_name': 'Asset'
+ },
),
migrations.RenameField(
model_name='asset',
diff --git a/apps/assets/migrations/0107_automation.py b/apps/assets/migrations/0107_automation.py
index 38bb777e4..56c2cf4eb 100644
--- a/apps/assets/migrations/0107_automation.py
+++ b/apps/assets/migrations/0107_automation.py
@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
],
options={
'verbose_name': 'Automation task',
- 'unique_together': {('org_id', 'name')},
+ 'unique_together': {('org_id', 'name', 'type')},
},
),
migrations.CreateModel(
@@ -93,18 +93,4 @@ class Migration(migrations.Migration):
name='automation',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation task'),
),
- migrations.AlterUniqueTogether(
- name='baseautomation',
- unique_together={('org_id', 'name', 'type')},
- ),
- migrations.AlterModelOptions(
- name='asset',
- options={'ordering': ['name'],
- 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'),
- ('test_assetconnectivity', 'Can test asset connectivity'),
- ('push_assetaccount', 'Can push account to asset'),
- ('test_account', 'Can verify account'), ('match_asset', 'Can match asset'),
- ('add_assettonode', 'Add asset to node'),
- ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'},
- ),
-]
+ ]
From 9d59fb736b9dafced335053eb1bf17b3d54f6f04 Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 13:03:45 +0800
Subject: [PATCH 08/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20database=20?=
=?UTF-8?q?=E7=9A=84=20cert=20model=20field?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/assets/migrations/0093_auto_20220403_1627.py | 7 ++++---
apps/assets/models/asset/common.py | 2 ++
apps/assets/models/asset/database.py | 15 ++++-----------
3 files changed, 10 insertions(+), 14 deletions(-)
diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py
index 71d88f773..45b8e92ed 100644
--- a/apps/assets/migrations/0093_auto_20220403_1627.py
+++ b/apps/assets/migrations/0093_auto_20220403_1627.py
@@ -2,6 +2,7 @@
import django.db
from django.db import migrations, models
+import common.db.fields
def migrate_to_host(apps, schema_editor):
@@ -120,9 +121,9 @@ class Migration(migrations.Migration):
primary_key=True, serialize=False, to='assets.asset')),
('db_name', models.CharField(blank=True, max_length=1024, verbose_name='Database')),
('allow_invalid_cert', models.BooleanField(default=False, verbose_name='Allow invalid cert')),
- ('ca_cert', models.TextField(blank=True, verbose_name='CA cert')),
- ('client_cert', models.TextField(blank=True, verbose_name='Client cert')),
- ('client_key', models.TextField(blank=True, verbose_name='Client key'),),
+ ('ca_cert', common.db.fields.EncryptTextField(blank=True, verbose_name='CA cert')),
+ ('client_cert', common.db.fields.EncryptTextField(blank=True, verbose_name='Client cert')),
+ ('client_key', common.db.fields.EncryptTextField(blank=True, verbose_name='Client key'),),
('use_ssl', models.BooleanField(default=False, verbose_name='Use SSL'),),
],
options={
diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py
index c7d8238e0..3914f000c 100644
--- a/apps/assets/models/asset/common.py
+++ b/apps/assets/models/asset/common.py
@@ -11,6 +11,7 @@ from django.utils.translation import ugettext_lazy as _
from assets import const
from common.utils import lazyproperty
+from common.db.fields import EncryptMixin
from orgs.mixins.models import OrgManager, JMSOrgBaseModel
from ..base import AbsConnectivity
from ..platform import Platform
@@ -139,6 +140,7 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
if not instance:
return []
specific_fields = self.get_specific_fields(instance)
+ specific_fields = [i for i in specific_fields if not isinstance(i, EncryptMixin)]
info = [
{
'label': i.verbose_name,
diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py
index 2c033de9e..12da55b30 100644
--- a/apps/assets/models/asset/database.py
+++ b/apps/assets/models/asset/database.py
@@ -1,28 +1,21 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
+from common.db.fields import EncryptTextField
from .common import Asset
class Database(Asset):
db_name = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True)
use_ssl = models.BooleanField(default=False, verbose_name=_("Use SSL"))
- ca_cert = models.TextField(verbose_name=_("CA cert"), blank=True)
- client_cert = models.TextField(verbose_name=_("Client cert"), blank=True)
- client_key = models.TextField(verbose_name=_("Client key"), blank=True)
+ ca_cert = EncryptTextField(verbose_name=_("CA cert"), blank=True)
+ client_cert = EncryptTextField(verbose_name=_("Client cert"), blank=True)
+ client_key = EncryptTextField(verbose_name=_("Client key"), blank=True)
allow_invalid_cert = models.BooleanField(default=False, verbose_name=_('Allow invalid cert'))
def __str__(self):
return '{}({}://{}/{})'.format(self.name, self.type, self.address, self.db_name)
- @property
- def specific(self):
- return {
- 'db_name': self.db_name,
- 'use_ssl': self.use_ssl,
- 'allow_invalid_cert': self.allow_invalid_cert,
- }
-
@property
def ip(self):
return self.address
From 9161b1ab1c7781c26005adaba4c0fb9e3daca42f Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 13:41:09 +0800
Subject: [PATCH 09/92] =?UTF-8?q?perf:=20=E8=B5=84=E4=BA=A7=E7=9A=84=20spe?=
=?UTF-8?q?cific=20=E4=B8=8D=E8=BF=94=E5=9B=9E=E5=8A=A0=E5=AF=86=E4=BF=A1?=
=?UTF-8?q?=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/assets/models/asset/common.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py
index 3914f000c..60f2f3c14 100644
--- a/apps/assets/models/asset/common.py
+++ b/apps/assets/models/asset/common.py
@@ -140,7 +140,6 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
if not instance:
return []
specific_fields = self.get_specific_fields(instance)
- specific_fields = [i for i in specific_fields if not isinstance(i, EncryptMixin)]
info = [
{
'label': i.verbose_name,
@@ -169,6 +168,7 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
@staticmethod
def get_specific_fields(instance):
specific_fields = [i for i in instance._meta.local_fields if i.name != 'asset_ptr']
+ specific_fields = [i for i in specific_fields if not isinstance(i, EncryptMixin)]
return specific_fields
def get_target_ip(self):
From 229e89af0335b60b6927459fae9f394cbc1a0f52 Mon Sep 17 00:00:00 2001
From: Bai
Date: Tue, 31 Jan 2023 15:00:02 +0800
Subject: [PATCH 10/92] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=BB=88?=
=?UTF-8?q?=E7=AB=AF=E5=88=97=E8=A1=A8=E6=A0=B9=E6=8D=AE=E8=B4=9F=E8=BD=BD?=
=?UTF-8?q?=E7=8A=B6=E6=80=81=E8=BF=9B=E8=A1=8C=E8=BF=87=E6=BB=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/terminal/api/component/terminal.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/terminal/api/component/terminal.py b/apps/terminal/api/component/terminal.py
index e2e1ad22f..d877be1df 100644
--- a/apps/terminal/api/component/terminal.py
+++ b/apps/terminal/api/component/terminal.py
@@ -25,7 +25,7 @@ class TerminalViewSet(JMSBulkModelViewSet):
queryset = Terminal.objects.filter(is_deleted=False)
serializer_class = serializers.TerminalSerializer
filterset_fields = ['name', 'remote_addr', 'type']
- custom_filter_fields = ['status']
+ custom_filter_fields = ['load']
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
@@ -40,7 +40,7 @@ class TerminalViewSet(JMSBulkModelViewSet):
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
- s = self.request.query_params.get('status')
+ s = self.request.query_params.get('load')
if not s:
return queryset
filtered_queryset_id = [str(q.id) for q in queryset if q.load == s]
From 6b4dbe655484e134cdb5ddd849e25cec01c356ee Mon Sep 17 00:00:00 2001
From: Bai
Date: Tue, 31 Jan 2023 15:43:06 +0800
Subject: [PATCH 11/92] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=B4=A6?=
=?UTF-8?q?=E5=8F=B7=E5=88=97=E8=A1=A8=E5=AF=BC=E5=87=BA500=E7=9A=84?=
=?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/audits/handler.py | 11 +++++++----
apps/common/views/mixins.py | 7 +++----
2 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/apps/audits/handler.py b/apps/audits/handler.py
index 7aff4c427..9d521824b 100644
--- a/apps/audits/handler.py
+++ b/apps/audits/handler.py
@@ -183,7 +183,7 @@ class OperatorLogHandler(metaclass=Singleton):
return data
def create_or_update_operate_log(
- self, action, resource_type, resource=None,
+ self, action, resource_type, resource=None, resource_display=None,
force=False, log_id=None, before=None, after=None,
object_name=None
):
@@ -192,7 +192,9 @@ class OperatorLogHandler(metaclass=Singleton):
return
remote_addr = get_request_ip(current_request)
- resource_display = self.get_resource_display(resource)
+ if resource_display is None:
+ resource_display = self.get_resource_display(resource)
+ resource_id = resource.id if resource is not None else ''
before, after = self.data_processing(before, after)
if not force and not any([before, after]):
# 前后都没变化,没必要生成日志,除非手动强制保存
@@ -200,9 +202,10 @@ class OperatorLogHandler(metaclass=Singleton):
data = {
'id': log_id, "user": str(user), 'action': action,
- 'resource_type': str(resource_type), 'resource': resource_display,
+ 'resource_type': str(resource_type),
+ 'resource_id': resource_id, 'resource': resource_display,
'remote_addr': remote_addr, 'before': before, 'after': after,
- 'org_id': get_current_org_id(), 'resource_id': str(resource.id)
+ 'org_id': get_current_org_id(),
}
data = self._activity_handle(data, object_name, resource=resource)
with transaction.atomic():
diff --git a/apps/common/views/mixins.py b/apps/common/views/mixins.py
index 39b146b3f..b0c38dee2 100644
--- a/apps/common/views/mixins.py
+++ b/apps/common/views/mixins.py
@@ -66,11 +66,11 @@ class RecordViewLogMixin:
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
- resource = self.get_resource_display(request)
+ resource_display = self.get_resource_display(request)
resource_type = self.model._meta.verbose_name
create_or_update_operate_log(
self.ACTION, resource_type, force=True,
- resource=resource
+ resource_display=resource_display
)
return response
@@ -78,7 +78,6 @@ class RecordViewLogMixin:
response = super().retrieve(request, *args, **kwargs)
resource_type = self.model._meta.verbose_name
create_or_update_operate_log(
- self.ACTION, resource_type, force=True,
- resource=self.get_object()
+ self.ACTION, resource_type, force=True, resource=self.get_object()
)
return response
From a7b744db1074e87782c4c1f29c00fe3531b41ef5 Mon Sep 17 00:00:00 2001
From: Bai
Date: Tue, 31 Jan 2023 16:05:28 +0800
Subject: [PATCH 12/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=B4=A6?=
=?UTF-8?q?=E5=8F=B7=E5=88=97=E8=A1=A8=20secret=20=E5=AD=97=E6=AE=B5?=
=?UTF-8?q?=E5=90=8D=E7=A7=B0=20=E5=AF=86=E9=92=A5/=E5=AF=86=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/accounts/serializers/account/base.py | 2 +-
apps/locale/ja/LC_MESSAGES/django.mo | 4 +-
apps/locale/ja/LC_MESSAGES/django.po | 481 ++++++++++++----------
apps/locale/zh/LC_MESSAGES/django.mo | 4 +-
apps/locale/zh/LC_MESSAGES/django.po | 478 +++++++++++----------
5 files changed, 518 insertions(+), 451 deletions(-)
diff --git a/apps/accounts/serializers/account/base.py b/apps/accounts/serializers/account/base.py
index d92c4cc9c..529704ef1 100644
--- a/apps/accounts/serializers/account/base.py
+++ b/apps/accounts/serializers/account/base.py
@@ -16,7 +16,7 @@ class AuthValidateMixin(serializers.Serializer):
choices=SecretType.choices, required=True, label=_('Secret type')
)
secret = EncryptedField(
- label=_('Secret'), required=False, max_length=40960, allow_blank=True,
+ label=_('Secret/Password'), required=False, max_length=40960, allow_blank=True,
allow_null=True, write_only=True,
)
passphrase = serializers.CharField(
diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo
index c4d0c146f..c36e4dc8f 100644
--- a/apps/locale/ja/LC_MESSAGES/django.mo
+++ b/apps/locale/ja/LC_MESSAGES/django.mo
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:eb850ffd130e7cad2ea8c186f94a059c6a882dd1526f7a4c4a16d2fea2a1815b
-size 119290
+oid sha256:b3c3f8e65468adb0105f2cbcbb8aa3ed50066c9db439a9921932c6e2adcacec3
+size 119640
diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po
index 063cce72e..ac8957e43 100644
--- a/apps/locale/ja/LC_MESSAGES/django.po
+++ b/apps/locale/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: 2023-01-16 14:24+0800\n"
+"POT-Creation-Date: 2023-01-31 16:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -24,7 +24,7 @@ msgstr "パラメータ 'action' は [{}] でなければなりません。"
#: accounts/const/account.py:6
#: accounts/serializers/automations/change_secret.py:33
-#: assets/models/_user.py:35 audits/signal_handlers.py:51
+#: assets/models/_user.py:24 audits/signal_handlers.py:51
#: authentication/confirm/password.py:9 authentication/forms.py:32
#: authentication/templates/authentication/login.html:288
#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47
@@ -45,7 +45,7 @@ msgstr "SSHキー"
msgid "Access key"
msgstr "アクセスキー"
-#: accounts/const/account.py:9 assets/models/_user.py:38
+#: accounts/const/account.py:9 assets/models/_user.py:48
#: authentication/models/sso_token.py:14
msgid "Token"
msgstr "トークン"
@@ -91,102 +91,107 @@ msgstr "パスワード/キーの確認"
msgid "Gather accounts"
msgstr "アカウントを集める"
-#: accounts/const/automation.py:43
+#: accounts/const/automation.py:26
+#, fuzzy
+msgid "Verify gateway account"
+msgstr "パスワード/キーの確認"
+
+#: accounts/const/automation.py:44
#, fuzzy
#| msgid "Set password"
msgid "Specific password"
msgstr "パスワードの設定"
-#: accounts/const/automation.py:44
+#: accounts/const/automation.py:45
msgid "Random"
msgstr ""
-#: accounts/const/automation.py:48 ops/const.py:13
+#: accounts/const/automation.py:49 ops/const.py:13
msgid "Append SSH KEY"
msgstr "追加"
-#: accounts/const/automation.py:49 ops/const.py:14
+#: accounts/const/automation.py:50 ops/const.py:14
msgid "Empty and append SSH KEY"
msgstr "すべてクリアして追加"
-#: accounts/const/automation.py:50 ops/const.py:15
+#: accounts/const/automation.py:51 ops/const.py:15
msgid "Replace (The key generated by JumpServer) "
msgstr "置換(JumpServerによって生成された鍵)"
-#: accounts/const/automation.py:55
+#: accounts/const/automation.py:56
#, fuzzy
#| msgid "Date created"
msgid "On asset create"
msgstr "作成された日付"
-#: accounts/const/automation.py:58
+#: accounts/const/automation.py:59
#, fuzzy
#| msgid "After change"
msgid "On perm add user"
msgstr "変更後"
-#: accounts/const/automation.py:60
+#: accounts/const/automation.py:61
msgid "On perm add user group"
msgstr ""
-#: accounts/const/automation.py:62
+#: accounts/const/automation.py:63
#, fuzzy
#| msgid "permed assets"
msgid "On perm add asset"
msgstr "パーマ資産"
-#: accounts/const/automation.py:64
+#: accounts/const/automation.py:65
#, fuzzy
#| msgid "After change"
msgid "On perm add node"
msgstr "変更後"
-#: accounts/const/automation.py:66
+#: accounts/const/automation.py:67
#, fuzzy
msgid "On perm add account"
msgstr "アカウントを集める"
-#: accounts/const/automation.py:68
+#: accounts/const/automation.py:69
#, fuzzy
#| msgid "Add asset to node"
msgid "On asset join node"
msgstr "ノードにアセットを追加する"
-#: accounts/const/automation.py:70
+#: accounts/const/automation.py:71
#, fuzzy
#| msgid "User group"
msgid "On user join group"
msgstr "ユーザーグループ"
-#: accounts/const/automation.py:78
+#: accounts/const/automation.py:79
#, fuzzy
#| msgid "After change"
msgid "On perm change"
msgstr "変更後"
-#: accounts/const/automation.py:85
+#: accounts/const/automation.py:86
#, fuzzy
#| msgid "Perm ungroup node"
msgid "Inherit from group or node"
msgstr "グループ化されていないノードを表示"
-#: accounts/const/automation.py:93
+#: accounts/const/automation.py:94
#, fuzzy
#| msgid "Created by"
msgid "Create and push"
msgstr "によって作成された"
-#: accounts/const/automation.py:94
+#: accounts/const/automation.py:95
#, fuzzy
#| msgid "Date created"
msgid "Only create"
msgstr "作成された日付"
-#: accounts/models/account.py:47 accounts/serializers/account/account.py:77
+#: accounts/models/account.py:47 accounts/serializers/account/account.py:88
#: accounts/serializers/automations/change_secret.py:107
#: accounts/serializers/automations/change_secret.py:127 acls/models/base.py:96
-#: acls/serializers/base.py:56 assets/models/asset/common.py:96
-#: assets/models/asset/common.py:281 assets/models/cmd_filter.py:36
+#: acls/serializers/base.py:56 assets/models/asset/common.py:97
+#: assets/models/asset/common.py:286 assets/models/cmd_filter.py:36
#: assets/serializers/domain.py:19 assets/serializers/label.py:27
#: audits/models.py:34 authentication/models/connection_token.py:32
#: perms/models/asset_permission.py:64 perms/serializers/permission.py:27
@@ -197,17 +202,17 @@ msgstr "作成された日付"
msgid "Asset"
msgstr "資産"
-#: accounts/models/account.py:51 accounts/serializers/account/account.py:81
+#: accounts/models/account.py:51 accounts/serializers/account/account.py:92
#: authentication/serializers/connect_token_secret.py:49
msgid "Su from"
msgstr "から切り替え"
#: accounts/models/account.py:53 settings/serializers/auth/cas.py:20
-#: terminal/models/applet/applet.py:25
+#: terminal/models/applet/applet.py:26
msgid "Version"
msgstr "バージョン"
-#: accounts/models/account.py:55 accounts/serializers/account/account.py:78
+#: accounts/models/account.py:55 accounts/serializers/account/account.py:89
#: users/models/user.py:727
msgid "Source"
msgstr "ソース"
@@ -215,7 +220,7 @@ msgstr "ソース"
#: accounts/models/account.py:58
#: accounts/serializers/automations/change_secret.py:108
#: accounts/serializers/automations/change_secret.py:128 acls/models/base.py:98
-#: acls/serializers/base.py:57 assets/serializers/asset/common.py:125
+#: acls/serializers/base.py:57 assets/serializers/asset/common.py:121
#: assets/serializers/gateway.py:30 audits/models.py:35 ops/models/base.py:18
#: terminal/backends/command/models.py:22 terminal/models/session/session.py:33
#: tickets/models/ticket/command_confirm.py:13 xpack/plugins/cloud/models.py:85
@@ -238,7 +243,7 @@ msgstr "資産履歴アカウントを表示できます"
msgid "Can view asset history account secret"
msgstr "資産履歴アカウントパスワードを表示できます"
-#: accounts/models/account.py:104 accounts/serializers/account/account.py:16
+#: accounts/models/account.py:104 accounts/serializers/account/account.py:34
#, fuzzy
msgid "Account template"
msgstr "アカウント名"
@@ -255,7 +260,7 @@ msgstr "資産口座の秘密を変更できます"
#: accounts/models/automations/backup_account.py:25
#: accounts/models/automations/change_secret.py:47
-#: accounts/serializers/account/backup.py:29
+#: accounts/serializers/account/backup.py:32
#: accounts/serializers/automations/change_secret.py:56
msgid "Recipient"
msgstr "受信者"
@@ -266,7 +271,7 @@ msgid "Account backup plan"
msgstr "アカウントバックアップ計画"
#: accounts/models/automations/backup_account.py:77
-#: assets/models/automations/base.py:102 audits/models.py:41
+#: assets/models/automations/base.py:106 audits/models.py:41
#: ops/models/base.py:55 ops/models/celery.py:63 ops/models/job.py:107
#: perms/models/asset_permission.py:72 terminal/models/applet/host.py:108
#: terminal/models/session/session.py:43
@@ -286,13 +291,14 @@ msgid "Account backup snapshot"
msgstr "アカウントのバックアップスナップショット"
#: accounts/models/automations/backup_account.py:88
-#: accounts/serializers/automations/base.py:42
-#: assets/models/automations/base.py:109
+#: accounts/serializers/account/backup.py:39
+#: accounts/serializers/automations/base.py:44
+#: assets/models/automations/base.py:113
#: assets/serializers/automations/base.py:40
msgid "Trigger mode"
msgstr "トリガーモード"
-#: accounts/models/automations/backup_account.py:91 audits/models.py:130
+#: accounts/models/automations/backup_account.py:91 audits/models.py:129
#: terminal/models/session/sharing.py:107 xpack/plugins/cloud/models.py:176
msgid "Reason"
msgstr "理由"
@@ -352,7 +358,7 @@ msgid "Can add push account execution"
msgstr "収集アカウントの作成実行"
#: accounts/models/automations/change_secret.py:17 accounts/models/base.py:36
-#: accounts/serializers/account/account.py:114
+#: accounts/serializers/account/account.py:125
#: accounts/serializers/account/base.py:16
#: accounts/serializers/automations/change_secret.py:46
#: authentication/serializers/connect_token_secret.py:40
@@ -367,7 +373,6 @@ msgstr "鍵ポリシー"
#: accounts/models/automations/change_secret.py:23
#: accounts/models/automations/change_secret.py:72 accounts/models/base.py:38
-#: accounts/serializers/account/base.py:19
#: authentication/models/temp_token.py:10
#: authentication/templates/authentication/_access_key_modal.html:31
#: settings/serializers/auth/radius.py:19
@@ -399,7 +404,7 @@ msgid "Date started"
msgstr "開始日"
#: accounts/models/automations/change_secret.py:74
-#: assets/models/automations/base.py:103 ops/models/base.py:56
+#: assets/models/automations/base.py:107 ops/models/base.py:56
#: ops/models/celery.py:64 ops/models/job.py:108
#: terminal/models/applet/host.py:109
msgid "Date finished"
@@ -429,7 +434,7 @@ msgstr "トリガーモード"
#: accounts/models/automations/push_account.py:14 accounts/models/base.py:34
#: acls/serializers/base.py:18 acls/serializers/base.py:49
-#: assets/models/_user.py:34 audits/models.py:115 authentication/forms.py:25
+#: assets/models/_user.py:23 audits/models.py:114 authentication/forms.py:25
#: authentication/forms.py:27 authentication/models/temp_token.py:9
#: authentication/templates/authentication/_msg_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.html:9
@@ -441,7 +446,7 @@ msgstr "ユーザー名"
#: accounts/models/automations/push_account.py:15 acls/models/base.py:77
#: acls/serializers/base.py:81 assets/models/cmd_filter.py:81
-#: audits/models.py:51 audits/serializers.py:75
+#: audits/models.py:51 audits/serializers.py:82
#: authentication/serializers/connect_token_secret.py:108
#: authentication/templates/authentication/_access_key_modal.html:34
msgid "Action"
@@ -459,18 +464,18 @@ msgstr "パスワード/キーの確認"
#: accounts/models/base.py:33 acls/models/base.py:71
#: acls/models/command_acl.py:21 acls/serializers/base.py:34
-#: applications/models.py:9 assets/models/_user.py:33
-#: assets/models/asset/common.py:94 assets/models/asset/common.py:106
+#: applications/models.py:9 assets/models/_user.py:22
+#: assets/models/asset/common.py:95 assets/models/asset/common.py:107
#: assets/models/cmd_filter.py:21 assets/models/domain.py:18
#: assets/models/group.py:20 assets/models/label.py:18
#: assets/models/platform.py:20 assets/models/platform.py:74
-#: assets/serializers/asset/common.py:143 assets/serializers/platform.py:128
+#: assets/serializers/asset/common.py:139 assets/serializers/platform.py:125
#: authentication/serializers/connect_token_secret.py:102 ops/mixin.py:20
#: ops/models/adhoc.py:22 ops/models/celery.py:15 ops/models/celery.py:57
#: ops/models/job.py:25 ops/models/playbook.py:14 orgs/models.py:69
#: perms/models/asset_permission.py:56 rbac/models/role.py:29
#: settings/models.py:33 settings/serializers/sms.py:6
-#: terminal/models/applet/applet.py:23 terminal/models/component/endpoint.py:12
+#: terminal/models/applet/applet.py:24 terminal/models/component/endpoint.py:12
#: terminal/models/component/endpoint.py:90
#: terminal/models/component/storage.py:26 terminal/models/component/task.py:15
#: terminal/models/component/terminal.py:79 users/forms/profile.py:33
@@ -483,11 +488,11 @@ msgstr "名前"
msgid "Privileged"
msgstr ""
-#: accounts/models/base.py:40 assets/models/asset/common.py:113
+#: accounts/models/base.py:40 assets/models/asset/common.py:114
#: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39
#: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:106
-#: terminal/models/applet/applet.py:28 users/serializers/user.py:158
+#: terminal/models/applet/applet.py:29 users/serializers/user.py:158
msgid "Is active"
msgstr "アクティブです。"
@@ -503,7 +508,7 @@ msgstr ""
"{} -アカウントバックアップの通過タスクが完了しました。詳細は添付ファイルをご"
"覧ください"
-#: accounts/notifications.py:20
+#: accounts/notifications.py:21
msgid ""
"{} - The account backup passage task has been completed: the encryption "
"password has not been set - please go to personal information -> file "
@@ -513,17 +518,17 @@ msgstr ""
"されていません-個人情報にアクセスしてください-> ファイル暗号化パスワードを設"
"定してください暗号化パスワード"
-#: accounts/notifications.py:31
+#: accounts/notifications.py:33
msgid "Notification of implementation result of encryption change plan"
msgstr "暗号化変更プランの実装結果の通知"
-#: accounts/notifications.py:41
+#: accounts/notifications.py:43
msgid ""
"{} - The encryption change task has been completed. See the attachment for "
"details"
msgstr "{} -暗号化変更タスクが完了しました。詳細は添付ファイルをご覧ください"
-#: accounts/notifications.py:42
+#: accounts/notifications.py:46
msgid ""
"{} - The encryption change task has been completed: the encryption password "
"has not been set - please go to personal information -> file encryption "
@@ -532,46 +537,50 @@ msgstr ""
"{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人"
"情報にアクセスしてください-> ファイル暗号化パスワードを設定してください"
-#: accounts/serializers/account/account.py:19
-#: assets/serializers/asset/common.py:52
+#: accounts/serializers/account/account.py:37
+#: assets/serializers/asset/common.py:55
msgid "Push now"
msgstr ""
-#: accounts/serializers/account/account.py:21
+#: accounts/serializers/account/account.py:39
#: accounts/serializers/account/base.py:64
#, fuzzy
msgid "Has secret"
msgstr "ひみつ"
-#: accounts/serializers/account/account.py:28
-#: assets/serializers/asset/common.py:79
+#: accounts/serializers/account/account.py:46
+#: assets/serializers/asset/common.py:82
msgid "Account template not found"
msgstr ""
-#: accounts/serializers/account/account.py:73
+#: accounts/serializers/account/account.py:84
#, fuzzy
#| msgid "Asset Info"
msgid "Asset not found"
msgstr "資産情報"
-#: accounts/serializers/account/backup.py:27
-#: accounts/serializers/automations/base.py:35
+#: accounts/serializers/account/backup.py:30
+#: accounts/serializers/automations/base.py:36
#: assets/serializers/automations/base.py:34 ops/mixin.py:22 ops/mixin.py:102
#: settings/serializers/auth/ldap.py:66
msgid "Periodic perform"
msgstr "定期的なパフォーマンス"
-#: accounts/serializers/account/backup.py:28
-#: accounts/serializers/automations/gather_accounts.py:23
+#: accounts/serializers/account/backup.py:31
+#: accounts/serializers/automations/base.py:37
#, fuzzy
msgid "Executed amount"
msgstr "実行時間"
-#: accounts/serializers/account/backup.py:30
+#: accounts/serializers/account/backup.py:33
#: accounts/serializers/automations/change_secret.py:57
msgid "Currently only mail sending is supported"
msgstr "現在、メール送信のみがサポートされています"
+#: accounts/serializers/account/base.py:19
+msgid "Secret/Password"
+msgstr "キー/パスワード"
+
#: accounts/serializers/account/base.py:24
msgid "Key password"
msgstr "キーパスワード"
@@ -580,7 +589,7 @@ msgstr "キーパスワード"
msgid "Specific"
msgstr ""
-#: accounts/serializers/automations/base.py:21
+#: accounts/serializers/automations/base.py:22
#: assets/models/automations/base.py:19
#: assets/serializers/automations/base.py:20 ops/models/base.py:17
#: ops/models/job.py:35
@@ -588,28 +597,28 @@ msgstr ""
msgid "Assets"
msgstr "資産"
-#: accounts/serializers/automations/base.py:22
-#: assets/models/asset/common.py:112 assets/models/automations/base.py:18
+#: accounts/serializers/automations/base.py:23
+#: assets/models/asset/common.py:113 assets/models/automations/base.py:18
#: assets/models/cmd_filter.py:32 assets/serializers/automations/base.py:21
#: perms/models/asset_permission.py:67
msgid "Nodes"
msgstr "ノード"
-#: accounts/serializers/automations/base.py:40
-#: assets/models/automations/base.py:105
+#: accounts/serializers/automations/base.py:42
+#: assets/models/automations/base.py:109
#: assets/serializers/automations/base.py:39
#, fuzzy
msgid "Automation snapshot"
msgstr "製造オーダスナップショット"
-#: accounts/serializers/automations/base.py:41 acls/models/command_acl.py:24
+#: accounts/serializers/automations/base.py:43 acls/models/command_acl.py:24
#: acls/serializers/command_acl.py:18 applications/models.py:14
-#: assets/models/_user.py:46 assets/models/automations/base.py:20
+#: assets/models/_user.py:50 assets/models/automations/base.py:20
#: assets/models/cmd_filter.py:74 assets/models/platform.py:76
-#: assets/serializers/asset/common.py:122 assets/serializers/platform.py:86
-#: audits/serializers.py:47
+#: assets/serializers/asset/common.py:118 assets/serializers/platform.py:86
+#: audits/serializers.py:48
#: authentication/serializers/connect_token_secret.py:115 ops/models/job.py:33
-#: perms/serializers/user_permission.py:26 terminal/models/applet/applet.py:27
+#: perms/serializers/user_permission.py:26 terminal/models/applet/applet.py:28
#: terminal/models/component/storage.py:57
#: terminal/models/component/storage.py:146 terminal/serializers/applet.py:28
#: terminal/serializers/session.py:26 terminal/serializers/storage.py:181
@@ -633,21 +642,22 @@ msgid "* Password length range 6-30 bits"
msgstr "* パスワードの長さの範囲6-30ビット"
#: accounts/serializers/automations/change_secret.py:110
-#: assets/models/automations/base.py:114
+#: assets/models/automations/base.py:118
#, fuzzy
msgid "Automation task execution"
msgstr "インスタンスタスクの同期実行"
#: accounts/serializers/automations/change_secret.py:150 audits/const.py:45
-#: audits/models.py:40 common/const/choices.py:18 ops/const.py:51
-#: ops/serializers/celery.py:39 terminal/const.py:59
+#: audits/handler.py:167 audits/models.py:40 common/const/choices.py:18
+#: ops/const.py:51 ops/serializers/celery.py:39 terminal/const.py:59
#: terminal/models/session/sharing.py:103 tickets/views/approve.py:114
msgid "Success"
msgstr "成功"
#: accounts/serializers/automations/change_secret.py:151
-#: assets/const/automation.py:8 audits/const.py:46 common/const/choices.py:19
-#: ops/const.py:53 terminal/const.py:60 xpack/plugins/cloud/const.py:41
+#: assets/const/automation.py:8 audits/const.py:46 audits/handler.py:167
+#: common/const/choices.py:19 ops/const.py:53 terminal/const.py:60
+#: xpack/plugins/cloud/const.py:41
msgid "Failed"
msgstr "失敗しました"
@@ -671,11 +681,11 @@ msgstr "資産ユーザーの収集"
msgid "Push accounts to assets"
msgstr "システムユーザーを資産にプッシュする:"
-#: accounts/tasks/verify_account.py:30
+#: accounts/tasks/verify_account.py:41
msgid "Verify asset account availability"
msgstr ""
-#: accounts/tasks/verify_account.py:36
+#: accounts/tasks/verify_account.py:47
#, fuzzy
msgid "Verify accounts connectivity"
msgstr "テストアカウント接続:"
@@ -713,12 +723,12 @@ msgstr "受け入れられる"
msgid "Review"
msgstr "レビュー担当者"
-#: acls/models/base.py:73 assets/models/_user.py:47
+#: acls/models/base.py:73 assets/models/_user.py:51
#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:93
msgid "Priority"
msgstr "優先順位"
-#: acls/models/base.py:74 assets/models/_user.py:47
+#: acls/models/base.py:74 assets/models/_user.py:51
#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:94
msgid "1-100, the lower the value will be match first"
msgstr "1-100、低い値は最初に一致します"
@@ -740,12 +750,12 @@ msgstr "アクティブ"
#: acls/models/base.py:94 acls/models/login_acl.py:13
#: acls/serializers/base.py:55 acls/serializers/login_acl.py:21
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:30
-#: audits/models.py:49 audits/models.py:99
+#: audits/models.py:49 audits/models.py:98
#: authentication/models/connection_token.py:28
#: authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58
-#: perms/serializers/permission.py:23 rbac/builtin.py:118
+#: perms/serializers/permission.py:23 rbac/builtin.py:119
#: rbac/models/rolebinding.py:41 terminal/backends/command/models.py:20
#: terminal/backends/command/serializers.py:13
#: terminal/models/session/session.py:29 terminal/models/session/sharing.py:32
@@ -895,7 +905,7 @@ msgstr ""
"192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:"
"db8:1a:1110::/64"
-#: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:107
+#: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:108
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8
@@ -912,9 +922,9 @@ msgid "Applications"
msgstr "アプリケーション"
#: applications/models.py:11 assets/models/label.py:21
-#: assets/models/platform.py:75 assets/serializers/asset/common.py:121
+#: assets/models/platform.py:75 assets/serializers/asset/common.py:117
#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:87
-#: assets/serializers/platform.py:129 perms/serializers/user_permission.py:25
+#: assets/serializers/platform.py:126 perms/serializers/user_permission.py:25
#: settings/models.py:35 tickets/models/ticket/apply_application.py:13
msgid "Category"
msgstr "カテゴリ"
@@ -933,7 +943,7 @@ msgid "Can match application"
msgstr "アプリケーションを一致させることができます"
#: applications/serializers/attrs/application_type/clickhouse.py:11
-#: assets/models/asset/common.py:95 assets/models/platform.py:21
+#: assets/models/asset/common.py:96 assets/models/platform.py:21
#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:68
#: xpack/plugins/cloud/serializers/account_attrs.py:73
msgid "Port"
@@ -1029,7 +1039,7 @@ msgid "Device"
msgstr ""
#: assets/const/category.py:13 assets/models/asset/database.py:9
-#: assets/models/asset/database.py:32
+#: assets/models/asset/database.py:24
msgid "Database"
msgstr "データベース"
@@ -1039,11 +1049,11 @@ msgid "Cloud service"
msgstr "クラウドセンター"
#: assets/const/category.py:15 audits/const.py:33
-#: terminal/models/applet/applet.py:21
+#: terminal/models/applet/applet.py:22
msgid "Web"
msgstr ""
-#: assets/const/device.py:7 terminal/models/applet/applet.py:20
+#: assets/const/device.py:7 terminal/models/applet/applet.py:21
#: tickets/const.py:8
msgid "General"
msgstr "一般"
@@ -1072,36 +1082,20 @@ msgstr "MFAタイプ"
msgid "Website"
msgstr "ウェブサイトのアイコン"
-#: assets/models/_user.py:24
-msgid "Automatic managed"
-msgstr "自動管理"
-
#: assets/models/_user.py:25
-msgid "Manually input"
-msgstr "手動入力"
-
-#: assets/models/_user.py:29
-msgid "Common user"
-msgstr "共通ユーザー"
-
-#: assets/models/_user.py:30
-msgid "Admin user"
-msgstr "管理ユーザー"
-
-#: assets/models/_user.py:36
msgid "SSH private key"
msgstr "SSH秘密鍵"
-#: assets/models/_user.py:37
+#: assets/models/_user.py:26
msgid "SSH public key"
msgstr "SSHパブリックキー"
-#: assets/models/_user.py:40 assets/models/cmd_filter.py:40
+#: assets/models/_user.py:27 assets/models/cmd_filter.py:40
#: assets/models/cmd_filter.py:88 assets/models/group.py:23
#: assets/models/platform.py:79 common/db/models.py:37 ops/models/adhoc.py:28
#: ops/models/job.py:41 ops/models/playbook.py:17 rbac/models/role.py:37
-#: settings/models.py:38 terminal/models/applet/applet.py:32
-#: terminal/models/applet/applet.py:137 terminal/models/applet/host.py:110
+#: settings/models.py:38 terminal/models/applet/applet.py:33
+#: terminal/models/applet/applet.py:146 terminal/models/applet/host.py:110
#: terminal/models/component/endpoint.py:24
#: terminal/models/component/endpoint.py:100
#: terminal/models/session/session.py:45 tickets/models/comment.py:32
@@ -1110,124 +1104,140 @@ msgstr "SSHパブリックキー"
msgid "Comment"
msgstr "コメント"
-#: assets/models/_user.py:41 assets/models/automations/base.py:101
+#: assets/models/_user.py:28 assets/models/automations/base.py:105
#: assets/models/cmd_filter.py:41 assets/models/group.py:22
#: common/db/models.py:35 ops/models/base.py:54 ops/models/job.py:106
#: users/models/user.py:932
msgid "Date created"
msgstr "作成された日付"
-#: assets/models/_user.py:42 assets/models/cmd_filter.py:42
+#: assets/models/_user.py:29 assets/models/cmd_filter.py:42
#: common/db/models.py:36
msgid "Date updated"
msgstr "更新日"
-#: assets/models/_user.py:43 assets/models/cmd_filter.py:44
+#: assets/models/_user.py:30 assets/models/cmd_filter.py:44
#: assets/models/cmd_filter.py:91 assets/models/group.py:21
#: common/db/models.py:33 users/models/user.py:722
#: users/serializers/group.py:33
msgid "Created by"
msgstr "によって作成された"
+#: assets/models/_user.py:40
+msgid "Automatic managed"
+msgstr "自動管理"
+
+#: assets/models/_user.py:41
+msgid "Manually input"
+msgstr "手動入力"
+
#: assets/models/_user.py:45
+msgid "Common user"
+msgstr "共通ユーザー"
+
+#: assets/models/_user.py:46 assets/models/_user.py:95
+msgid "Admin user"
+msgstr "管理ユーザー"
+
+#: assets/models/_user.py:49
msgid "Username same with user"
msgstr "ユーザーと同じユーザー名"
-#: assets/models/_user.py:48 authentication/models/connection_token.py:37
+#: assets/models/_user.py:52 authentication/models/connection_token.py:37
#: authentication/serializers/connect_token_secret.py:103
-#: terminal/models/applet/applet.py:30 terminal/serializers/session.py:24
+#: terminal/models/applet/applet.py:31 terminal/serializers/session.py:24
#: terminal/serializers/session.py:45 terminal/serializers/storage.py:68
msgid "Protocol"
msgstr "プロトコル"
-#: assets/models/_user.py:49
+#: assets/models/_user.py:53
msgid "Auto push"
msgstr "オートプッシュ"
-#: assets/models/_user.py:50
+#: assets/models/_user.py:54
msgid "Sudo"
msgstr "すど"
-#: assets/models/_user.py:51 ops/const.py:44
+#: assets/models/_user.py:55 ops/const.py:44
msgid "Shell"
msgstr "シェル"
-#: assets/models/_user.py:52
+#: assets/models/_user.py:56
msgid "Login mode"
msgstr "ログインモード"
-#: assets/models/_user.py:53
+#: assets/models/_user.py:57
msgid "SFTP Root"
msgstr "SFTPルート"
-#: assets/models/_user.py:54
+#: assets/models/_user.py:58
msgid "Home"
msgstr "ホーム"
-#: assets/models/_user.py:55
+#: assets/models/_user.py:59
msgid "System groups"
msgstr "システムグループ"
-#: assets/models/_user.py:58
+#: assets/models/_user.py:62
msgid "User switch"
msgstr "ユーザースイッチ"
-#: assets/models/_user.py:59
+#: assets/models/_user.py:63
msgid "Switch from"
msgstr "から切り替え"
-#: assets/models/_user.py:65
+#: assets/models/_user.py:69
msgid "System user"
msgstr "システムユーザー"
-#: assets/models/_user.py:67
+#: assets/models/_user.py:71
msgid "Can match system user"
msgstr "システムユーザーに一致できます"
-#: assets/models/asset/common.py:108 assets/models/platform.py:112
+#: assets/models/asset/common.py:109 assets/models/platform.py:111
#: authentication/serializers/connect_token_secret.py:107
#: perms/serializers/user_permission.py:23
#: xpack/plugins/cloud/serializers/account_attrs.py:179
msgid "Platform"
msgstr "プラットフォーム"
-#: assets/models/asset/common.py:110 assets/models/domain.py:21
+#: assets/models/asset/common.py:111 assets/models/domain.py:21
#: authentication/serializers/connect_token_secret.py:125
#: perms/serializers/user_permission.py:27
msgid "Domain"
msgstr "ドメイン"
-#: assets/models/asset/common.py:114
+#: assets/models/asset/common.py:115
msgid "Labels"
msgstr "ラベル"
-#: assets/models/asset/common.py:284
+#: assets/models/asset/common.py:289
msgid "Can refresh asset hardware info"
msgstr "資産ハードウェア情報を更新できます"
-#: assets/models/asset/common.py:285
+#: assets/models/asset/common.py:290
msgid "Can test asset connectivity"
msgstr "資産接続をテストできます"
-#: assets/models/asset/common.py:286
+#: assets/models/asset/common.py:291
#, fuzzy
msgid "Can push account to asset"
msgstr "システムユーザーを資産にプッシュできます"
-#: assets/models/asset/common.py:287
+#: assets/models/asset/common.py:292
#, fuzzy
msgid "Can verify account"
msgstr "パスワード/キーの確認"
-#: assets/models/asset/common.py:288
+#: assets/models/asset/common.py:293
msgid "Can match asset"
msgstr "アセットを一致させることができます"
-#: assets/models/asset/common.py:289
+#: assets/models/asset/common.py:294
msgid "Add asset to node"
msgstr "ノードにアセットを追加する"
-#: assets/models/asset/common.py:290
+#: assets/models/asset/common.py:295
msgid "Move asset to node"
msgstr "アセットをノードに移動する"
@@ -1288,24 +1298,24 @@ msgid "Submit selector"
msgstr ""
#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38
-#: assets/serializers/asset/common.py:241 perms/models/asset_permission.py:70
+#: assets/serializers/asset/common.py:237 perms/models/asset_permission.py:70
#: perms/serializers/permission.py:32 rbac/tree.py:36
msgid "Accounts"
msgstr "アカウント"
-#: assets/models/automations/base.py:28 assets/models/automations/base.py:98
+#: assets/models/automations/base.py:28 assets/models/automations/base.py:102
#, fuzzy
msgid "Automation task"
msgstr "自動管理"
-#: assets/models/automations/base.py:91
+#: assets/models/automations/base.py:95
#, fuzzy
msgid "Asset automation task"
msgstr "自動管理"
-#: assets/models/automations/base.py:100 audits/models.py:135
-#: audits/serializers.py:48 ops/models/base.py:49 ops/models/job.py:99
-#: terminal/models/applet/applet.py:136 terminal/models/applet/host.py:107
+#: assets/models/automations/base.py:104 audits/models.py:134
+#: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:99
+#: terminal/models/applet/applet.py:145 terminal/models/applet/host.py:107
#: terminal/models/component/status.py:27 terminal/serializers/applet.py:17
#: terminal/serializers/applet_host.py:90 tickets/models/ticket/general.py:283
#: tickets/serializers/super_ticket.py:13
@@ -1363,7 +1373,9 @@ msgid "Command filter rule"
msgstr "コマンドフィルタルール"
#: assets/models/favorite_asset.py:17
-msgid "Favorite Asset"
+#, fuzzy
+#| msgid "Favorite Asset"
+msgid "Favorite asset"
msgstr "お気に入り"
#: assets/models/gateway.py:35 assets/serializers/domain.py:16
@@ -1387,7 +1399,7 @@ msgstr "デフォルトアセットグループ"
msgid "System"
msgstr "システム"
-#: assets/models/label.py:19 assets/models/node.py:552
+#: assets/models/label.py:19 assets/models/node.py:558
#: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14
#: authentication/models/connection_token.py:25
#: authentication/serializers/connect_token_secret.py:114
@@ -1395,7 +1407,7 @@ msgstr "システム"
msgid "Value"
msgstr "値"
-#: assets/models/label.py:40 assets/serializers/asset/common.py:123
+#: assets/models/label.py:40 assets/serializers/asset/common.py:119
#: assets/serializers/cagegory.py:6 assets/serializers/cagegory.py:13
#: authentication/serializers/connect_token_secret.py:113
#: common/serializers/common.py:81 settings/serializers/sms.py:7
@@ -1406,28 +1418,28 @@ msgstr "ラベル"
msgid "New node"
msgstr "新しいノード"
-#: assets/models/node.py:480
+#: assets/models/node.py:486 audits/backends/db.py:55 audits/backends/db.py:56
msgid "empty"
msgstr "空"
-#: assets/models/node.py:551 perms/models/perm_node.py:28
+#: assets/models/node.py:557 perms/models/perm_node.py:28
msgid "Key"
msgstr "キー"
-#: assets/models/node.py:553 assets/serializers/node.py:20
+#: assets/models/node.py:559 assets/serializers/node.py:20
msgid "Full value"
msgstr "フルバリュー"
-#: assets/models/node.py:557 perms/models/perm_node.py:30
+#: assets/models/node.py:563 perms/models/perm_node.py:30
msgid "Parent key"
msgstr "親キー"
-#: assets/models/node.py:566 perms/serializers/permission.py:28
+#: assets/models/node.py:572 perms/serializers/permission.py:28
#: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:96
msgid "Node"
msgstr "ノード"
-#: assets/models/node.py:569
+#: assets/models/node.py:575
msgid "Can match node"
msgstr "ノードを一致させることができます"
@@ -1516,22 +1528,17 @@ msgstr "シャーセット"
msgid "Domain enabled"
msgstr "ドメイン名"
-#: assets/models/platform.py:85
-#, fuzzy
-msgid "Protocols enabled"
-msgstr "プロトコル"
-
-#: assets/models/platform.py:87
+#: assets/models/platform.py:86
#, fuzzy
msgid "Su enabled"
msgstr "MFA有効化"
-#: assets/models/platform.py:88
+#: assets/models/platform.py:87
#, fuzzy
msgid "Su method"
msgstr "接続タイムアウト"
-#: assets/models/platform.py:90 assets/serializers/platform.py:91
+#: assets/models/platform.py:89 assets/serializers/platform.py:91
#, fuzzy
msgid "Automation"
msgstr "自動管理"
@@ -1541,7 +1548,7 @@ msgstr "自動管理"
msgid "%(value)s is not an even number"
msgstr "%(value)s は偶数ではありません"
-#: assets/serializers/asset/common.py:124 assets/serializers/platform.py:89
+#: assets/serializers/asset/common.py:120 assets/serializers/platform.py:89
#: authentication/serializers/connect_token_secret.py:28
#: authentication/serializers/connect_token_secret.py:65
#: perms/serializers/user_permission.py:24 xpack/plugins/cloud/models.py:107
@@ -1549,30 +1556,36 @@ msgstr "%(value)s は偶数ではありません"
msgid "Protocols"
msgstr "プロトコル"
-#: assets/serializers/asset/common.py:126
+#: assets/serializers/asset/common.py:122
#, fuzzy
#| msgid "Enabled"
msgid "Enabled info"
msgstr "有効化"
-#: assets/serializers/asset/common.py:144
+#: assets/serializers/asset/common.py:140
msgid "Address"
msgstr "アドレス"
-#: assets/serializers/asset/common.py:145
+#: assets/serializers/asset/common.py:141
msgid "Node path"
msgstr "ノードパスです"
-#: assets/serializers/asset/common.py:205
+#: assets/serializers/asset/common.py:201
#, fuzzy
msgid "Platform not exist"
msgstr "アプリが存在しません"
-#: assets/serializers/asset/common.py:221
+#: assets/serializers/asset/common.py:217
#, fuzzy
msgid "Protocol is required: {}"
msgstr "プロトコル重複: {}"
+#: assets/serializers/asset/database.py:24 common/serializers/fields.py:100
+#: tickets/serializers/ticket/common.py:58
+#: xpack/plugins/cloud/serializers/account_attrs.py:56
+msgid "This field is required."
+msgstr "このフィールドは必須です。"
+
#: assets/serializers/asset/host.py:12
msgid "Vendor"
msgstr "ベンダー"
@@ -1757,11 +1770,11 @@ msgstr "一致する資産がない、タスクを停止"
msgid "Audits"
msgstr "監査"
-#: audits/backends/db.py:12
+#: audits/backends/db.py:15
msgid "The text content is too long. Use Elasticsearch to store operation logs"
msgstr "文章の内容が長すぎる。Elasticsearchで操作履歴を保存する"
-#: audits/backends/db.py:25 audits/backends/db.py:27
+#: audits/backends/db.py:76
msgid "Tips"
msgstr "謎々"
@@ -1834,20 +1847,35 @@ msgstr "ターミナル"
msgid "-"
msgstr "-"
-#: audits/handler.py:136
+#: audits/handler.py:116
msgid "Yes"
msgstr "是"
-#: audits/handler.py:136
+#: audits/handler.py:116
msgid "No"
msgstr "否"
-#: audits/models.py:32 audits/models.py:59 audits/models.py:102
+#: audits/handler.py:140
+msgid "{} used account[{}], login method[{}] login the asset."
+msgstr ""
+"{} トムはアカウント[{}]、ログイン方法[{}]を使ってこの資産を登録しました"
+
+#: audits/handler.py:155
+msgid "User {} has executed change auth plan for this account.({})"
+msgstr "ユーザー {} はこのアカウントのために改密計画を実行しました。({})"
+
+#: audits/handler.py:168
+#, fuzzy
+#| msgid "User {} {} it."
+msgid "User {} login into this service.[{}]"
+msgstr "ユーザー {} はそれを {} しました"
+
+#: audits/models.py:32 audits/models.py:59 audits/models.py:101
#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:95
msgid "Remote addr"
msgstr "リモートaddr"
-#: audits/models.py:37 audits/serializers.py:32
+#: audits/models.py:37 audits/serializers.py:33
msgid "Operate"
msgstr "操作"
@@ -1859,7 +1887,7 @@ msgstr "ファイル名"
msgid "File transfer log"
msgstr "ファイル転送ログ"
-#: audits/models.py:53 audits/serializers.py:86
+#: audits/models.py:53 audits/serializers.py:93
msgid "Resource Type"
msgstr "リソースタイプ"
@@ -1867,71 +1895,69 @@ msgstr "リソースタイプ"
msgid "Resource"
msgstr "リソース"
-#: audits/models.py:60 audits/models.py:104
+#: audits/models.py:60 audits/models.py:103
#: terminal/backends/command/serializers.py:41
msgid "Datetime"
msgstr "時間"
-#: audits/models.py:63
-#, fuzzy
-#| msgid "Is active"
-msgid "Is Activity"
-msgstr "アクティブです。"
+#: audits/models.py:62
+msgid "Detail"
+msgstr ""
-#: audits/models.py:93
+#: audits/models.py:92
msgid "Operate log"
msgstr "ログの操作"
-#: audits/models.py:100
+#: audits/models.py:99
msgid "Change by"
msgstr "による変更"
-#: audits/models.py:110
+#: audits/models.py:109
msgid "Password change log"
msgstr "パスワード変更ログ"
-#: audits/models.py:117
+#: audits/models.py:116
msgid "Login type"
msgstr "ログインタイプ"
-#: audits/models.py:119 tickets/models/ticket/login_confirm.py:10
+#: audits/models.py:118 tickets/models/ticket/login_confirm.py:10
msgid "Login ip"
msgstr "ログインIP"
-#: audits/models.py:121
+#: audits/models.py:120
#: authentication/templates/authentication/_msg_different_city.html:11
#: tickets/models/ticket/login_confirm.py:11
msgid "Login city"
msgstr "ログイン都市"
-#: audits/models.py:124 audits/serializers.py:62
+#: audits/models.py:123 audits/serializers.py:63
msgid "User agent"
msgstr "ユーザーエージェント"
-#: audits/models.py:127 audits/serializers.py:46
+#: audits/models.py:126 audits/serializers.py:47
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: users/forms/profile.py:65 users/models/user.py:698
#: users/serializers/profile.py:126
msgid "MFA"
msgstr "MFA"
-#: audits/models.py:137
+#: audits/models.py:136
msgid "Date login"
msgstr "日付ログイン"
-#: audits/models.py:139 audits/serializers.py:64
+#: audits/models.py:138 audits/serializers.py:65
msgid "Authentication backend"
msgstr "認証バックエンド"
-#: audits/models.py:180
+#: audits/models.py:182
msgid "User login log"
msgstr "ユーザーログインログ"
-#: audits/serializers.py:63
+#: audits/serializers.py:64
msgid "Reason display"
msgstr "理由表示"
-#: audits/serializers.py:112
+#: audits/serializers.py:120
#, fuzzy
#| msgid "User {} {} it."
msgid "User {} {} this resource."
@@ -2036,7 +2062,7 @@ msgid "Authentication"
msgstr "認証"
#: authentication/backends/custom.py:58
-#: authentication/backends/oauth2/backends.py:158
+#: authentication/backends/oauth2/backends.py:167
msgid "User invalid, disabled or expired"
msgstr "ユーザーが無効、無効、または期限切れです"
@@ -3086,11 +3112,6 @@ msgstr ""
msgid "File"
msgstr "ファイル名"
-#: common/serializers/fields.py:100 tickets/serializers/ticket/common.py:58
-#: xpack/plugins/cloud/serializers/account_attrs.py:56
-msgid "This field is required."
-msgstr "このフィールドは必須です。"
-
#: common/serializers/fields.py:101
#, fuzzy, python-brace-format
msgid "Invalid pk \"{pk_value}\" - object does not exist."
@@ -3239,7 +3260,7 @@ msgstr "ユーザーが無効になりました。"
msgid "Skip hosts below:"
msgstr ""
-#: ops/api/celery.py:63 ops/api/celery.py:78
+#: ops/api/celery.py:58 ops/api/celery.py:73
msgid "Waiting task start"
msgstr "タスク開始待ち"
@@ -3515,34 +3536,34 @@ msgstr "テストタイムアウト"
msgid "Task"
msgstr "タスク"
-#: ops/tasks.py:28
+#: ops/tasks.py:27
#, fuzzy
msgid "Run ansible task"
msgstr "アセットの実行"
-#: ops/tasks.py:35
+#: ops/tasks.py:43
#, fuzzy
msgid "Run ansible task execution"
msgstr "インスタンスタスクの同期実行"
-#: ops/tasks.py:49
+#: ops/tasks.py:57
msgid "Periodic clear celery tasks"
msgstr "定期的にCeleryタスクをクリア"
-#: ops/tasks.py:51
+#: ops/tasks.py:59
msgid "Clean celery log period"
msgstr "きれいなセロリログ期間"
-#: ops/tasks.py:68
+#: ops/tasks.py:76
#, fuzzy
msgid "Clear celery periodic tasks"
msgstr "きれいなセロリログ期間"
-#: ops/tasks.py:91
+#: ops/tasks.py:99
msgid "Create or update periodic tasks"
msgstr "定期的なタスクの作成または更新"
-#: ops/tasks.py:99
+#: ops/tasks.py:107
#, fuzzy
msgid "Periodic check service performance"
msgstr "定期的なパフォーマンス"
@@ -3617,7 +3638,7 @@ msgstr "組織"
msgid "Org name"
msgstr "組織名"
-#: orgs/models.py:70 rbac/models/role.py:36 terminal/models/applet/applet.py:29
+#: orgs/models.py:70 rbac/models/role.py:36 terminal/models/applet/applet.py:30
#, fuzzy
msgid "Builtin"
msgstr "内蔵"
@@ -3770,27 +3791,27 @@ msgstr "{} 少なくとも1つのシステムロール"
msgid "RBAC"
msgstr "RBAC"
-#: rbac/builtin.py:109
+#: rbac/builtin.py:110
msgid "SystemAdmin"
msgstr "システム管理者"
-#: rbac/builtin.py:112
+#: rbac/builtin.py:113
msgid "SystemAuditor"
msgstr "システム監査人"
-#: rbac/builtin.py:115
+#: rbac/builtin.py:116
msgid "SystemComponent"
msgstr "システムコンポーネント"
-#: rbac/builtin.py:121
+#: rbac/builtin.py:122
msgid "OrgAdmin"
msgstr "組織管理者"
-#: rbac/builtin.py:124
+#: rbac/builtin.py:125
msgid "OrgAuditor"
msgstr "監査員を組織する"
-#: rbac/builtin.py:127
+#: rbac/builtin.py:128
msgid "OrgUser"
msgstr "組織ユーザー"
@@ -3871,7 +3892,7 @@ msgstr "パーマ"
msgid "Users amount"
msgstr "ユーザー数"
-#: rbac/serializers/role.py:28 terminal/models/applet/applet.py:24
+#: rbac/serializers/role.py:28 terminal/models/applet/applet.py:25
msgid "Display name"
msgstr "表示名"
@@ -3939,8 +3960,8 @@ msgstr "タスクセンター"
msgid "My assets"
msgstr "私の資産"
-#: rbac/tree.py:57 terminal/models/applet/applet.py:39
-#: terminal/models/applet/applet.py:133 terminal/models/applet/host.py:27
+#: rbac/tree.py:57 terminal/models/applet/applet.py:40
+#: terminal/models/applet/applet.py:142 terminal/models/applet/host.py:27
msgid "Applet"
msgstr "リモートアプリケーション"
@@ -5356,6 +5377,12 @@ msgstr ""
msgid "Offline video player"
msgstr "オフラインビデオプレーヤー"
+#: terminal/api/applet/applet.py:48 terminal/api/applet/applet.py:51
+#, fuzzy
+#| msgid "Invalid ip"
+msgid "Invalid zip file"
+msgstr "無効なIP"
+
#: terminal/api/component/endpoint.py:31
msgid "Not found protocol query params"
msgstr "プロトコルクエリパラメータが見つかりません"
@@ -5495,20 +5522,24 @@ msgstr "一括作成非サポート"
msgid "Storage is invalid"
msgstr "ストレージが無効です"
-#: terminal/models/applet/applet.py:26
+#: terminal/models/applet/applet.py:27
#, fuzzy
msgid "Author"
msgstr "資産アカウント"
-#: terminal/models/applet/applet.py:31
+#: terminal/models/applet/applet.py:32
msgid "Tags"
msgstr ""
-#: terminal/models/applet/applet.py:35 terminal/serializers/storage.py:157
+#: terminal/models/applet/applet.py:36 terminal/serializers/storage.py:157
msgid "Hosts"
msgstr "ホスト"
-#: terminal/models/applet/applet.py:135 terminal/models/applet/host.py:33
+#: terminal/models/applet/applet.py:81
+msgid "Applet pkg not valid, Missing file {}"
+msgstr ""
+
+#: terminal/models/applet/applet.py:144 terminal/models/applet/host.py:33
#: terminal/models/applet/host.py:105
#, fuzzy
msgid "Hosting"
@@ -6152,7 +6183,7 @@ msgstr "製造オーダスナップショット"
msgid "Please try again"
msgstr "もう一度お試しください"
-#: tickets/models/ticket/general.py:458
+#: tickets/models/ticket/general.py:461
msgid "Super ticket"
msgstr "スーパーチケット"
@@ -7402,6 +7433,15 @@ msgstr "究極のエディション"
msgid "Community edition"
msgstr "コミュニティ版"
+#, fuzzy
+#~ msgid "Protocols enabled"
+#~ msgstr "プロトコル"
+
+#, fuzzy
+#~| msgid "Is active"
+#~ msgid "Is Activity"
+#~ msgstr "アクティブです。"
+
#, fuzzy
#~| msgid "Dynamic user"
#~ msgid "Dynamic username"
@@ -7428,13 +7468,6 @@ msgstr "コミュニティ版"
#~ msgid "Change password method"
#~ msgstr "パスワードの変更"
-#~ msgid "{} used account[{}], login method[{}] login the asset."
-#~ msgstr ""
-#~ "{} トムはアカウント[{}]、ログイン方法[{}]を使ってこの資産を登録しました"
-
-#~ msgid "User {} has executed change auth plan for this account.({})"
-#~ msgstr "ユーザー {} はこのアカウントのために改密計画を実行しました。({})"
-
#~ msgid ""
#~ "The range of ports that Magnus listens on is modified in the "
#~ "configuration file"
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index 230afb603..1dbea7659 100644
--- a/apps/locale/zh/LC_MESSAGES/django.mo
+++ b/apps/locale/zh/LC_MESSAGES/django.mo
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4af8f2ead4a9d5aaf943efea76305d8cad1ff0692758d21a93937601c6f150fd
-size 105736
+oid sha256:0c3f5102d732ffe768f0545cf9271bbba45ba4c159f0a348b518b58cbdb5f20c
+size 105947
diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index 219de87f3..bdbc5b7d0 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/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: 2023-01-16 14:24+0800\n"
+"POT-Creation-Date: 2023-01-31 16:01+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler \n"
"Language-Team: JumpServer team\n"
@@ -23,7 +23,7 @@ msgstr "参数 'action' 必须是 [{}]"
#: accounts/const/account.py:6
#: accounts/serializers/automations/change_secret.py:33
-#: assets/models/_user.py:35 audits/signal_handlers.py:51
+#: assets/models/_user.py:24 audits/signal_handlers.py:51
#: authentication/confirm/password.py:9 authentication/forms.py:32
#: authentication/templates/authentication/login.html:288
#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47
@@ -43,7 +43,7 @@ msgstr "SSH 密钥"
msgid "Access key"
msgstr "Access key"
-#: accounts/const/account.py:9 assets/models/_user.py:38
+#: accounts/const/account.py:9 assets/models/_user.py:48
#: authentication/models/sso_token.py:14
msgid "Token"
msgstr "Token"
@@ -89,93 +89,99 @@ msgstr "验证账号"
msgid "Gather accounts"
msgstr "收集账号"
-#: accounts/const/automation.py:43
+#: accounts/const/automation.py:26
+#, fuzzy
+#| msgid "Verify asset account"
+msgid "Verify gateway account"
+msgstr "账号验证"
+
+#: accounts/const/automation.py:44
msgid "Specific password"
msgstr "指定"
-#: accounts/const/automation.py:44
+#: accounts/const/automation.py:45
msgid "Random"
msgstr ""
-#: accounts/const/automation.py:48 ops/const.py:13
+#: accounts/const/automation.py:49 ops/const.py:13
msgid "Append SSH KEY"
msgstr "追加"
-#: accounts/const/automation.py:49 ops/const.py:14
+#: accounts/const/automation.py:50 ops/const.py:14
msgid "Empty and append SSH KEY"
msgstr "清空所有并添加"
-#: accounts/const/automation.py:50 ops/const.py:15
+#: accounts/const/automation.py:51 ops/const.py:15
msgid "Replace (The key generated by JumpServer) "
msgstr "替换 (仅替换由 JumpServer 生成的密钥)"
-#: accounts/const/automation.py:55
+#: accounts/const/automation.py:56
msgid "On asset create"
msgstr "资产创建时"
-#: accounts/const/automation.py:58
+#: accounts/const/automation.py:59
#, fuzzy
#| msgid "On perm change"
msgid "On perm add user"
msgstr "授权变更时"
-#: accounts/const/automation.py:60
+#: accounts/const/automation.py:61
msgid "On perm add user group"
msgstr ""
-#: accounts/const/automation.py:62
+#: accounts/const/automation.py:63
#, fuzzy
#| msgid "permed assets"
msgid "On perm add asset"
msgstr "授权的资产"
-#: accounts/const/automation.py:64
+#: accounts/const/automation.py:65
#, fuzzy
#| msgid "On perm change"
msgid "On perm add node"
msgstr "授权变更时"
-#: accounts/const/automation.py:66
+#: accounts/const/automation.py:67
#, fuzzy
#| msgid "Permed account"
msgid "On perm add account"
msgstr "授权账号"
-#: accounts/const/automation.py:68
+#: accounts/const/automation.py:69
#, fuzzy
#| msgid "Add asset to node"
msgid "On asset join node"
msgstr "添加资产到节点"
-#: accounts/const/automation.py:70
+#: accounts/const/automation.py:71
#, fuzzy
#| msgid "User group"
msgid "On user join group"
msgstr "用户组"
-#: accounts/const/automation.py:78
+#: accounts/const/automation.py:79
msgid "On perm change"
msgstr "授权变更时"
-#: accounts/const/automation.py:85
+#: accounts/const/automation.py:86
#, fuzzy
#| msgid "Perm ungroup node"
msgid "Inherit from group or node"
msgstr "显示未分组节点"
-#: accounts/const/automation.py:93
+#: accounts/const/automation.py:94
msgid "Create and push"
msgstr "创建并推送到资产"
-#: accounts/const/automation.py:94
+#: accounts/const/automation.py:95
msgid "Only create"
msgstr "仅创建到资产"
-#: accounts/models/account.py:47 accounts/serializers/account/account.py:77
+#: accounts/models/account.py:47 accounts/serializers/account/account.py:88
#: accounts/serializers/automations/change_secret.py:107
#: accounts/serializers/automations/change_secret.py:127 acls/models/base.py:96
-#: acls/serializers/base.py:56 assets/models/asset/common.py:96
-#: assets/models/asset/common.py:281 assets/models/cmd_filter.py:36
+#: acls/serializers/base.py:56 assets/models/asset/common.py:97
+#: assets/models/asset/common.py:286 assets/models/cmd_filter.py:36
#: assets/serializers/domain.py:19 assets/serializers/label.py:27
#: audits/models.py:34 authentication/models/connection_token.py:32
#: perms/models/asset_permission.py:64 perms/serializers/permission.py:27
@@ -186,17 +192,17 @@ msgstr "仅创建到资产"
msgid "Asset"
msgstr "资产"
-#: accounts/models/account.py:51 accounts/serializers/account/account.py:81
+#: accounts/models/account.py:51 accounts/serializers/account/account.py:92
#: authentication/serializers/connect_token_secret.py:49
msgid "Su from"
msgstr "切换自"
#: accounts/models/account.py:53 settings/serializers/auth/cas.py:20
-#: terminal/models/applet/applet.py:25
+#: terminal/models/applet/applet.py:26
msgid "Version"
msgstr "版本"
-#: accounts/models/account.py:55 accounts/serializers/account/account.py:78
+#: accounts/models/account.py:55 accounts/serializers/account/account.py:89
#: users/models/user.py:727
msgid "Source"
msgstr "来源"
@@ -204,7 +210,7 @@ msgstr "来源"
#: accounts/models/account.py:58
#: accounts/serializers/automations/change_secret.py:108
#: accounts/serializers/automations/change_secret.py:128 acls/models/base.py:98
-#: acls/serializers/base.py:57 assets/serializers/asset/common.py:125
+#: acls/serializers/base.py:57 assets/serializers/asset/common.py:121
#: assets/serializers/gateway.py:30 audits/models.py:35 ops/models/base.py:18
#: terminal/backends/command/models.py:22 terminal/models/session/session.py:33
#: tickets/models/ticket/command_confirm.py:13 xpack/plugins/cloud/models.py:85
@@ -227,7 +233,7 @@ msgstr "可以查看资产历史账号"
msgid "Can view asset history account secret"
msgstr "可以查看资产历史账号密码"
-#: accounts/models/account.py:104 accounts/serializers/account/account.py:16
+#: accounts/models/account.py:104 accounts/serializers/account/account.py:34
msgid "Account template"
msgstr "账号模版"
@@ -241,7 +247,7 @@ msgstr "可以更改账号模版密码"
#: accounts/models/automations/backup_account.py:25
#: accounts/models/automations/change_secret.py:47
-#: accounts/serializers/account/backup.py:29
+#: accounts/serializers/account/backup.py:32
#: accounts/serializers/automations/change_secret.py:56
msgid "Recipient"
msgstr "收件人"
@@ -252,7 +258,7 @@ msgid "Account backup plan"
msgstr "账号备份计划"
#: accounts/models/automations/backup_account.py:77
-#: assets/models/automations/base.py:102 audits/models.py:41
+#: assets/models/automations/base.py:106 audits/models.py:41
#: ops/models/base.py:55 ops/models/celery.py:63 ops/models/job.py:107
#: perms/models/asset_permission.py:72 terminal/models/applet/host.py:108
#: terminal/models/session/session.py:43
@@ -272,13 +278,14 @@ msgid "Account backup snapshot"
msgstr "账号备份快照"
#: accounts/models/automations/backup_account.py:88
-#: accounts/serializers/automations/base.py:42
-#: assets/models/automations/base.py:109
+#: accounts/serializers/account/backup.py:39
+#: accounts/serializers/automations/base.py:44
+#: assets/models/automations/base.py:113
#: assets/serializers/automations/base.py:40
msgid "Trigger mode"
msgstr "触发模式"
-#: accounts/models/automations/backup_account.py:91 audits/models.py:130
+#: accounts/models/automations/backup_account.py:91 audits/models.py:129
#: terminal/models/session/sharing.py:107 xpack/plugins/cloud/models.py:176
msgid "Reason"
msgstr "原因"
@@ -341,7 +348,7 @@ msgid "Can add push account execution"
msgstr "创建收集账号执行"
#: accounts/models/automations/change_secret.py:17 accounts/models/base.py:36
-#: accounts/serializers/account/account.py:114
+#: accounts/serializers/account/account.py:125
#: accounts/serializers/account/base.py:16
#: accounts/serializers/automations/change_secret.py:46
#: authentication/serializers/connect_token_secret.py:40
@@ -356,7 +363,6 @@ msgstr "密文策略"
#: accounts/models/automations/change_secret.py:23
#: accounts/models/automations/change_secret.py:72 accounts/models/base.py:38
-#: accounts/serializers/account/base.py:19
#: authentication/models/temp_token.py:10
#: authentication/templates/authentication/_access_key_modal.html:31
#: settings/serializers/auth/radius.py:19
@@ -384,7 +390,7 @@ msgid "Date started"
msgstr "开始日期"
#: accounts/models/automations/change_secret.py:74
-#: assets/models/automations/base.py:103 ops/models/base.py:56
+#: assets/models/automations/base.py:107 ops/models/base.py:56
#: ops/models/celery.py:64 ops/models/job.py:108
#: terminal/models/applet/host.py:109
msgid "Date finished"
@@ -411,7 +417,7 @@ msgstr "触发方式"
#: accounts/models/automations/push_account.py:14 accounts/models/base.py:34
#: acls/serializers/base.py:18 acls/serializers/base.py:49
-#: assets/models/_user.py:34 audits/models.py:115 authentication/forms.py:25
+#: assets/models/_user.py:23 audits/models.py:114 authentication/forms.py:25
#: authentication/forms.py:27 authentication/models/temp_token.py:9
#: authentication/templates/authentication/_msg_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.html:9
@@ -423,7 +429,7 @@ msgstr "用户名"
#: accounts/models/automations/push_account.py:15 acls/models/base.py:77
#: acls/serializers/base.py:81 assets/models/cmd_filter.py:81
-#: audits/models.py:51 audits/serializers.py:75
+#: audits/models.py:51 audits/serializers.py:82
#: authentication/serializers/connect_token_secret.py:108
#: authentication/templates/authentication/_access_key_modal.html:34
msgid "Action"
@@ -439,18 +445,18 @@ msgstr "账号验证"
#: accounts/models/base.py:33 acls/models/base.py:71
#: acls/models/command_acl.py:21 acls/serializers/base.py:34
-#: applications/models.py:9 assets/models/_user.py:33
-#: assets/models/asset/common.py:94 assets/models/asset/common.py:106
+#: applications/models.py:9 assets/models/_user.py:22
+#: assets/models/asset/common.py:95 assets/models/asset/common.py:107
#: assets/models/cmd_filter.py:21 assets/models/domain.py:18
#: assets/models/group.py:20 assets/models/label.py:18
#: assets/models/platform.py:20 assets/models/platform.py:74
-#: assets/serializers/asset/common.py:143 assets/serializers/platform.py:128
+#: assets/serializers/asset/common.py:139 assets/serializers/platform.py:125
#: authentication/serializers/connect_token_secret.py:102 ops/mixin.py:20
#: ops/models/adhoc.py:22 ops/models/celery.py:15 ops/models/celery.py:57
#: ops/models/job.py:25 ops/models/playbook.py:14 orgs/models.py:69
#: perms/models/asset_permission.py:56 rbac/models/role.py:29
#: settings/models.py:33 settings/serializers/sms.py:6
-#: terminal/models/applet/applet.py:23 terminal/models/component/endpoint.py:12
+#: terminal/models/applet/applet.py:24 terminal/models/component/endpoint.py:12
#: terminal/models/component/endpoint.py:90
#: terminal/models/component/storage.py:26 terminal/models/component/task.py:15
#: terminal/models/component/terminal.py:79 users/forms/profile.py:33
@@ -463,11 +469,11 @@ msgstr "名称"
msgid "Privileged"
msgstr "特权账号"
-#: accounts/models/base.py:40 assets/models/asset/common.py:113
+#: accounts/models/base.py:40 assets/models/asset/common.py:114
#: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39
#: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:106
-#: terminal/models/applet/applet.py:28 users/serializers/user.py:158
+#: terminal/models/applet/applet.py:29 users/serializers/user.py:158
msgid "Is active"
msgstr "激活"
@@ -481,7 +487,7 @@ msgid ""
"for details"
msgstr "{} - 账号备份任务已完成, 详情见附件"
-#: accounts/notifications.py:20
+#: accounts/notifications.py:21
msgid ""
"{} - The account backup passage task has been completed: the encryption "
"password has not been set - please go to personal information -> file "
@@ -490,17 +496,17 @@ msgstr ""
"{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设"
"置加密密码"
-#: accounts/notifications.py:31
+#: accounts/notifications.py:33
msgid "Notification of implementation result of encryption change plan"
msgstr "改密计划任务结果通知"
-#: accounts/notifications.py:41
+#: accounts/notifications.py:43
msgid ""
"{} - The encryption change task has been completed. See the attachment for "
"details"
msgstr "{} - 改密任务已完成, 详情见附件"
-#: accounts/notifications.py:42
+#: accounts/notifications.py:46
msgid ""
"{} - The encryption change task has been completed: the encryption password "
"has not been set - please go to personal information -> file encryption "
@@ -509,42 +515,46 @@ msgstr ""
"{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加"
"密密码"
-#: accounts/serializers/account/account.py:19
-#: assets/serializers/asset/common.py:52
+#: accounts/serializers/account/account.py:37
+#: assets/serializers/asset/common.py:55
msgid "Push now"
msgstr "立即推送"
-#: accounts/serializers/account/account.py:21
+#: accounts/serializers/account/account.py:39
#: accounts/serializers/account/base.py:64
msgid "Has secret"
msgstr "已托管密码"
-#: accounts/serializers/account/account.py:28
-#: assets/serializers/asset/common.py:79
+#: accounts/serializers/account/account.py:46
+#: assets/serializers/asset/common.py:82
msgid "Account template not found"
msgstr "账号模版未找到"
-#: accounts/serializers/account/account.py:73
+#: accounts/serializers/account/account.py:84
msgid "Asset not found"
msgstr "资产不存在"
-#: accounts/serializers/account/backup.py:27
-#: accounts/serializers/automations/base.py:35
+#: accounts/serializers/account/backup.py:30
+#: accounts/serializers/automations/base.py:36
#: assets/serializers/automations/base.py:34 ops/mixin.py:22 ops/mixin.py:102
#: settings/serializers/auth/ldap.py:66
msgid "Periodic perform"
msgstr "定时执行"
-#: accounts/serializers/account/backup.py:28
-#: accounts/serializers/automations/gather_accounts.py:23
+#: accounts/serializers/account/backup.py:31
+#: accounts/serializers/automations/base.py:37
msgid "Executed amount"
msgstr "执行次数"
-#: accounts/serializers/account/backup.py:30
+#: accounts/serializers/account/backup.py:33
#: accounts/serializers/automations/change_secret.py:57
msgid "Currently only mail sending is supported"
msgstr "当前只支持邮件发送"
+#: accounts/serializers/account/base.py:19
+msgid "Secret/Password"
+msgstr "密钥/密码"
+
#: accounts/serializers/account/base.py:24
msgid "Key password"
msgstr "密钥密码"
@@ -553,7 +563,7 @@ msgstr "密钥密码"
msgid "Specific"
msgstr "指定的"
-#: accounts/serializers/automations/base.py:21
+#: accounts/serializers/automations/base.py:22
#: assets/models/automations/base.py:19
#: assets/serializers/automations/base.py:20 ops/models/base.py:17
#: ops/models/job.py:35
@@ -561,27 +571,27 @@ msgstr "指定的"
msgid "Assets"
msgstr "资产"
-#: accounts/serializers/automations/base.py:22
-#: assets/models/asset/common.py:112 assets/models/automations/base.py:18
+#: accounts/serializers/automations/base.py:23
+#: assets/models/asset/common.py:113 assets/models/automations/base.py:18
#: assets/models/cmd_filter.py:32 assets/serializers/automations/base.py:21
#: perms/models/asset_permission.py:67
msgid "Nodes"
msgstr "节点"
-#: accounts/serializers/automations/base.py:40
-#: assets/models/automations/base.py:105
+#: accounts/serializers/automations/base.py:42
+#: assets/models/automations/base.py:109
#: assets/serializers/automations/base.py:39
msgid "Automation snapshot"
msgstr "工单快照"
-#: accounts/serializers/automations/base.py:41 acls/models/command_acl.py:24
+#: accounts/serializers/automations/base.py:43 acls/models/command_acl.py:24
#: acls/serializers/command_acl.py:18 applications/models.py:14
-#: assets/models/_user.py:46 assets/models/automations/base.py:20
+#: assets/models/_user.py:50 assets/models/automations/base.py:20
#: assets/models/cmd_filter.py:74 assets/models/platform.py:76
-#: assets/serializers/asset/common.py:122 assets/serializers/platform.py:86
-#: audits/serializers.py:47
+#: assets/serializers/asset/common.py:118 assets/serializers/platform.py:86
+#: audits/serializers.py:48
#: authentication/serializers/connect_token_secret.py:115 ops/models/job.py:33
-#: perms/serializers/user_permission.py:26 terminal/models/applet/applet.py:27
+#: perms/serializers/user_permission.py:26 terminal/models/applet/applet.py:28
#: terminal/models/component/storage.py:57
#: terminal/models/component/storage.py:146 terminal/serializers/applet.py:28
#: terminal/serializers/session.py:26 terminal/serializers/storage.py:181
@@ -605,20 +615,21 @@ msgid "* Password length range 6-30 bits"
msgstr "* 密码长度范围 6-30 位"
#: accounts/serializers/automations/change_secret.py:110
-#: assets/models/automations/base.py:114
+#: assets/models/automations/base.py:118
msgid "Automation task execution"
msgstr "自动化任务执行历史"
#: accounts/serializers/automations/change_secret.py:150 audits/const.py:45
-#: audits/models.py:40 common/const/choices.py:18 ops/const.py:51
-#: ops/serializers/celery.py:39 terminal/const.py:59
+#: audits/handler.py:167 audits/models.py:40 common/const/choices.py:18
+#: ops/const.py:51 ops/serializers/celery.py:39 terminal/const.py:59
#: terminal/models/session/sharing.py:103 tickets/views/approve.py:114
msgid "Success"
msgstr "成功"
#: accounts/serializers/automations/change_secret.py:151
-#: assets/const/automation.py:8 audits/const.py:46 common/const/choices.py:19
-#: ops/const.py:53 terminal/const.py:60 xpack/plugins/cloud/const.py:41
+#: assets/const/automation.py:8 audits/const.py:46 audits/handler.py:167
+#: common/const/choices.py:19 ops/const.py:53 terminal/const.py:60
+#: xpack/plugins/cloud/const.py:41
msgid "Failed"
msgstr "失败"
@@ -640,11 +651,11 @@ msgstr "收集资产上的账号"
msgid "Push accounts to assets"
msgstr "推送账号到资产"
-#: accounts/tasks/verify_account.py:30
+#: accounts/tasks/verify_account.py:41
msgid "Verify asset account availability"
msgstr ""
-#: accounts/tasks/verify_account.py:36
+#: accounts/tasks/verify_account.py:47
msgid "Verify accounts connectivity"
msgstr "测试账号可连接性"
@@ -681,12 +692,12 @@ msgstr "接受"
msgid "Review"
msgstr "审批"
-#: acls/models/base.py:73 assets/models/_user.py:47
+#: acls/models/base.py:73 assets/models/_user.py:51
#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:93
msgid "Priority"
msgstr "优先级"
-#: acls/models/base.py:74 assets/models/_user.py:47
+#: acls/models/base.py:74 assets/models/_user.py:51
#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:94
msgid "1-100, the lower the value will be match first"
msgstr "优先级可选范围为 1-100 (数值越小越优先)"
@@ -708,12 +719,12 @@ msgstr "激活中"
#: acls/models/base.py:94 acls/models/login_acl.py:13
#: acls/serializers/base.py:55 acls/serializers/login_acl.py:21
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:30
-#: audits/models.py:49 audits/models.py:99
+#: audits/models.py:49 audits/models.py:98
#: authentication/models/connection_token.py:28
#: authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58
-#: perms/serializers/permission.py:23 rbac/builtin.py:118
+#: perms/serializers/permission.py:23 rbac/builtin.py:119
#: rbac/models/rolebinding.py:41 terminal/backends/command/models.py:20
#: terminal/backends/command/serializers.py:13
#: terminal/models/session/session.py:29 terminal/models/session/sharing.py:32
@@ -861,7 +872,7 @@ msgstr ""
"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, "
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64"
-#: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:107
+#: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:108
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8
@@ -878,9 +889,9 @@ msgid "Applications"
msgstr "应用管理"
#: applications/models.py:11 assets/models/label.py:21
-#: assets/models/platform.py:75 assets/serializers/asset/common.py:121
+#: assets/models/platform.py:75 assets/serializers/asset/common.py:117
#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:87
-#: assets/serializers/platform.py:129 perms/serializers/user_permission.py:25
+#: assets/serializers/platform.py:126 perms/serializers/user_permission.py:25
#: settings/models.py:35 tickets/models/ticket/apply_application.py:13
msgid "Category"
msgstr "类别"
@@ -899,7 +910,7 @@ msgid "Can match application"
msgstr "匹配应用"
#: applications/serializers/attrs/application_type/clickhouse.py:11
-#: assets/models/asset/common.py:95 assets/models/platform.py:21
+#: assets/models/asset/common.py:96 assets/models/platform.py:21
#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:68
#: xpack/plugins/cloud/serializers/account_attrs.py:73
msgid "Port"
@@ -990,7 +1001,7 @@ msgid "Device"
msgstr "网络设备"
#: assets/const/category.py:13 assets/models/asset/database.py:9
-#: assets/models/asset/database.py:32
+#: assets/models/asset/database.py:24
msgid "Database"
msgstr "数据库"
@@ -999,11 +1010,11 @@ msgid "Cloud service"
msgstr "云服务"
#: assets/const/category.py:15 audits/const.py:33
-#: terminal/models/applet/applet.py:21
+#: terminal/models/applet/applet.py:22
msgid "Web"
msgstr "Web"
-#: assets/const/device.py:7 terminal/models/applet/applet.py:20
+#: assets/const/device.py:7 terminal/models/applet/applet.py:21
#: tickets/const.py:8
msgid "General"
msgstr "一般"
@@ -1028,36 +1039,20 @@ msgstr "所有类型"
msgid "Website"
msgstr "网站"
-#: assets/models/_user.py:24
-msgid "Automatic managed"
-msgstr "托管密码"
-
#: assets/models/_user.py:25
-msgid "Manually input"
-msgstr "手动输入"
-
-#: assets/models/_user.py:29
-msgid "Common user"
-msgstr "普通用户"
-
-#: assets/models/_user.py:30
-msgid "Admin user"
-msgstr "特权用户"
-
-#: assets/models/_user.py:36
msgid "SSH private key"
msgstr "SSH密钥"
-#: assets/models/_user.py:37
+#: assets/models/_user.py:26
msgid "SSH public key"
msgstr "SSH公钥"
-#: assets/models/_user.py:40 assets/models/cmd_filter.py:40
+#: assets/models/_user.py:27 assets/models/cmd_filter.py:40
#: assets/models/cmd_filter.py:88 assets/models/group.py:23
#: assets/models/platform.py:79 common/db/models.py:37 ops/models/adhoc.py:28
#: ops/models/job.py:41 ops/models/playbook.py:17 rbac/models/role.py:37
-#: settings/models.py:38 terminal/models/applet/applet.py:32
-#: terminal/models/applet/applet.py:137 terminal/models/applet/host.py:110
+#: settings/models.py:38 terminal/models/applet/applet.py:33
+#: terminal/models/applet/applet.py:146 terminal/models/applet/host.py:110
#: terminal/models/component/endpoint.py:24
#: terminal/models/component/endpoint.py:100
#: terminal/models/session/session.py:45 tickets/models/comment.py:32
@@ -1066,124 +1061,140 @@ msgstr "SSH公钥"
msgid "Comment"
msgstr "备注"
-#: assets/models/_user.py:41 assets/models/automations/base.py:101
+#: assets/models/_user.py:28 assets/models/automations/base.py:105
#: assets/models/cmd_filter.py:41 assets/models/group.py:22
#: common/db/models.py:35 ops/models/base.py:54 ops/models/job.py:106
#: users/models/user.py:932
msgid "Date created"
msgstr "创建日期"
-#: assets/models/_user.py:42 assets/models/cmd_filter.py:42
+#: assets/models/_user.py:29 assets/models/cmd_filter.py:42
#: common/db/models.py:36
msgid "Date updated"
msgstr "更新日期"
-#: assets/models/_user.py:43 assets/models/cmd_filter.py:44
+#: assets/models/_user.py:30 assets/models/cmd_filter.py:44
#: assets/models/cmd_filter.py:91 assets/models/group.py:21
#: common/db/models.py:33 users/models/user.py:722
#: users/serializers/group.py:33
msgid "Created by"
msgstr "创建者"
+#: assets/models/_user.py:40
+msgid "Automatic managed"
+msgstr "托管密码"
+
+#: assets/models/_user.py:41
+msgid "Manually input"
+msgstr "手动输入"
+
#: assets/models/_user.py:45
+msgid "Common user"
+msgstr "普通用户"
+
+#: assets/models/_user.py:46 assets/models/_user.py:95
+msgid "Admin user"
+msgstr "特权用户"
+
+#: assets/models/_user.py:49
msgid "Username same with user"
msgstr "用户名与用户相同"
-#: assets/models/_user.py:48 authentication/models/connection_token.py:37
+#: assets/models/_user.py:52 authentication/models/connection_token.py:37
#: authentication/serializers/connect_token_secret.py:103
-#: terminal/models/applet/applet.py:30 terminal/serializers/session.py:24
+#: terminal/models/applet/applet.py:31 terminal/serializers/session.py:24
#: terminal/serializers/session.py:45 terminal/serializers/storage.py:68
msgid "Protocol"
msgstr "协议"
-#: assets/models/_user.py:49
+#: assets/models/_user.py:53
msgid "Auto push"
msgstr "自动推送"
-#: assets/models/_user.py:50
+#: assets/models/_user.py:54
msgid "Sudo"
msgstr "Sudo"
-#: assets/models/_user.py:51 ops/const.py:44
+#: assets/models/_user.py:55 ops/const.py:44
msgid "Shell"
msgstr "Shell"
-#: assets/models/_user.py:52
+#: assets/models/_user.py:56
msgid "Login mode"
msgstr "认证方式"
-#: assets/models/_user.py:53
+#: assets/models/_user.py:57
msgid "SFTP Root"
msgstr "SFTP根路径"
-#: assets/models/_user.py:54
+#: assets/models/_user.py:58
msgid "Home"
msgstr "家目录"
-#: assets/models/_user.py:55
+#: assets/models/_user.py:59
msgid "System groups"
msgstr "用户组"
-#: assets/models/_user.py:58
+#: assets/models/_user.py:62
msgid "User switch"
msgstr "用户切换"
-#: assets/models/_user.py:59
+#: assets/models/_user.py:63
msgid "Switch from"
msgstr "切换自"
-#: assets/models/_user.py:65
+#: assets/models/_user.py:69
msgid "System user"
msgstr "系统用户"
-#: assets/models/_user.py:67
+#: assets/models/_user.py:71
msgid "Can match system user"
msgstr "可以匹配系统用户"
-#: assets/models/asset/common.py:108 assets/models/platform.py:112
+#: assets/models/asset/common.py:109 assets/models/platform.py:111
#: authentication/serializers/connect_token_secret.py:107
#: perms/serializers/user_permission.py:23
#: xpack/plugins/cloud/serializers/account_attrs.py:179
msgid "Platform"
msgstr "系统平台"
-#: assets/models/asset/common.py:110 assets/models/domain.py:21
+#: assets/models/asset/common.py:111 assets/models/domain.py:21
#: authentication/serializers/connect_token_secret.py:125
#: perms/serializers/user_permission.py:27
msgid "Domain"
msgstr "网域"
-#: assets/models/asset/common.py:114
+#: assets/models/asset/common.py:115
msgid "Labels"
msgstr "标签管理"
-#: assets/models/asset/common.py:284
+#: assets/models/asset/common.py:289
msgid "Can refresh asset hardware info"
msgstr "可以更新资产硬件信息"
-#: assets/models/asset/common.py:285
+#: assets/models/asset/common.py:290
msgid "Can test asset connectivity"
msgstr "可以测试资产连接性"
-#: assets/models/asset/common.py:286
+#: assets/models/asset/common.py:291
msgid "Can push account to asset"
msgstr "可以推送账号到资产"
-#: assets/models/asset/common.py:287
+#: assets/models/asset/common.py:292
#, fuzzy
#| msgid "Verify account"
msgid "Can verify account"
msgstr "验证账号"
-#: assets/models/asset/common.py:288
+#: assets/models/asset/common.py:293
msgid "Can match asset"
msgstr "可以匹配资产"
-#: assets/models/asset/common.py:289
+#: assets/models/asset/common.py:294
msgid "Add asset to node"
msgstr "添加资产到节点"
-#: assets/models/asset/common.py:290
+#: assets/models/asset/common.py:295
msgid "Move asset to node"
msgstr "移动资产到节点"
@@ -1238,24 +1249,24 @@ msgid "Submit selector"
msgstr "确认按钮选择器"
#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38
-#: assets/serializers/asset/common.py:241 perms/models/asset_permission.py:70
+#: assets/serializers/asset/common.py:237 perms/models/asset_permission.py:70
#: perms/serializers/permission.py:32 rbac/tree.py:36
msgid "Accounts"
msgstr "账号管理"
-#: assets/models/automations/base.py:28 assets/models/automations/base.py:98
+#: assets/models/automations/base.py:28 assets/models/automations/base.py:102
msgid "Automation task"
msgstr "自动化任务"
-#: assets/models/automations/base.py:91
+#: assets/models/automations/base.py:95
#, fuzzy
#| msgid "Automation task"
msgid "Asset automation task"
msgstr "自动化任务"
-#: assets/models/automations/base.py:100 audits/models.py:135
-#: audits/serializers.py:48 ops/models/base.py:49 ops/models/job.py:99
-#: terminal/models/applet/applet.py:136 terminal/models/applet/host.py:107
+#: assets/models/automations/base.py:104 audits/models.py:134
+#: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:99
+#: terminal/models/applet/applet.py:145 terminal/models/applet/host.py:107
#: terminal/models/component/status.py:27 terminal/serializers/applet.py:17
#: terminal/serializers/applet_host.py:90 tickets/models/ticket/general.py:283
#: tickets/serializers/super_ticket.py:13
@@ -1311,7 +1322,9 @@ msgid "Command filter rule"
msgstr "命令过滤规则"
#: assets/models/favorite_asset.py:17
-msgid "Favorite Asset"
+#, fuzzy
+#| msgid "Favorite Asset"
+msgid "Favorite asset"
msgstr "收藏的资产"
#: assets/models/gateway.py:35 assets/serializers/domain.py:16
@@ -1335,7 +1348,7 @@ msgstr "默认资产组"
msgid "System"
msgstr "系统"
-#: assets/models/label.py:19 assets/models/node.py:552
+#: assets/models/label.py:19 assets/models/node.py:558
#: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14
#: authentication/models/connection_token.py:25
#: authentication/serializers/connect_token_secret.py:114
@@ -1343,7 +1356,7 @@ msgstr "系统"
msgid "Value"
msgstr "值"
-#: assets/models/label.py:40 assets/serializers/asset/common.py:123
+#: assets/models/label.py:40 assets/serializers/asset/common.py:119
#: assets/serializers/cagegory.py:6 assets/serializers/cagegory.py:13
#: authentication/serializers/connect_token_secret.py:113
#: common/serializers/common.py:81 settings/serializers/sms.py:7
@@ -1354,28 +1367,28 @@ msgstr "标签"
msgid "New node"
msgstr "新节点"
-#: assets/models/node.py:480
+#: assets/models/node.py:486 audits/backends/db.py:55 audits/backends/db.py:56
msgid "empty"
msgstr "空"
-#: assets/models/node.py:551 perms/models/perm_node.py:28
+#: assets/models/node.py:557 perms/models/perm_node.py:28
msgid "Key"
msgstr "键"
-#: assets/models/node.py:553 assets/serializers/node.py:20
+#: assets/models/node.py:559 assets/serializers/node.py:20
msgid "Full value"
msgstr "全称"
-#: assets/models/node.py:557 perms/models/perm_node.py:30
+#: assets/models/node.py:563 perms/models/perm_node.py:30
msgid "Parent key"
msgstr "ssh私钥"
-#: assets/models/node.py:566 perms/serializers/permission.py:28
+#: assets/models/node.py:572 perms/serializers/permission.py:28
#: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:96
msgid "Node"
msgstr "节点"
-#: assets/models/node.py:569
+#: assets/models/node.py:575
msgid "Can match node"
msgstr "可以匹配节点"
@@ -1457,19 +1470,15 @@ msgstr "编码"
msgid "Domain enabled"
msgstr "启用网域"
-#: assets/models/platform.py:85
-msgid "Protocols enabled"
-msgstr "启用协议"
-
-#: assets/models/platform.py:87
+#: assets/models/platform.py:86
msgid "Su enabled"
msgstr "启用账号切换"
-#: assets/models/platform.py:88
+#: assets/models/platform.py:87
msgid "Su method"
msgstr "账号切换方式"
-#: assets/models/platform.py:90 assets/serializers/platform.py:91
+#: assets/models/platform.py:89 assets/serializers/platform.py:91
msgid "Automation"
msgstr "自动化"
@@ -1478,7 +1487,7 @@ msgstr "自动化"
msgid "%(value)s is not an even number"
msgstr "%(value)s is not an even number"
-#: assets/serializers/asset/common.py:124 assets/serializers/platform.py:89
+#: assets/serializers/asset/common.py:120 assets/serializers/platform.py:89
#: authentication/serializers/connect_token_secret.py:28
#: authentication/serializers/connect_token_secret.py:65
#: perms/serializers/user_permission.py:24 xpack/plugins/cloud/models.py:107
@@ -1486,28 +1495,34 @@ msgstr "%(value)s is not an even number"
msgid "Protocols"
msgstr "协议组"
-#: assets/serializers/asset/common.py:126
+#: assets/serializers/asset/common.py:122
#, fuzzy
#| msgid "Enabled"
msgid "Enabled info"
msgstr "启用"
-#: assets/serializers/asset/common.py:144
+#: assets/serializers/asset/common.py:140
msgid "Address"
msgstr "地址"
-#: assets/serializers/asset/common.py:145
+#: assets/serializers/asset/common.py:141
msgid "Node path"
msgstr "节点路径"
-#: assets/serializers/asset/common.py:205
+#: assets/serializers/asset/common.py:201
msgid "Platform not exist"
msgstr "平台不存在"
-#: assets/serializers/asset/common.py:221
+#: assets/serializers/asset/common.py:217
msgid "Protocol is required: {}"
msgstr "协议是必填的: {}"
+#: assets/serializers/asset/database.py:24 common/serializers/fields.py:100
+#: tickets/serializers/ticket/common.py:58
+#: xpack/plugins/cloud/serializers/account_attrs.py:56
+msgid "This field is required."
+msgstr "该字段是必填项。"
+
#: assets/serializers/asset/host.py:12
msgid "Vendor"
msgstr "制造商"
@@ -1686,11 +1701,11 @@ msgstr "没有匹配到资产,结束任务"
msgid "Audits"
msgstr "日志审计"
-#: audits/backends/db.py:12
+#: audits/backends/db.py:15
msgid "The text content is too long. Use Elasticsearch to store operation logs"
msgstr "文字内容太长。请使用 Elasticsearch 存储操作日志"
-#: audits/backends/db.py:25 audits/backends/db.py:27
+#: audits/backends/db.py:76
msgid "Tips"
msgstr "提示"
@@ -1762,20 +1777,34 @@ msgstr "终端"
msgid "-"
msgstr "-"
-#: audits/handler.py:136
+#: audits/handler.py:116
msgid "Yes"
msgstr "是"
-#: audits/handler.py:136
+#: audits/handler.py:116
msgid "No"
msgstr "否"
-#: audits/models.py:32 audits/models.py:59 audits/models.py:102
+#: audits/handler.py:140
+msgid "{} used account[{}], login method[{}] login the asset."
+msgstr "{} 使用账户[{}], 登录方式[{}]登录了这个资产."
+
+#: audits/handler.py:155
+msgid "User {} has executed change auth plan for this account.({})"
+msgstr "用户 {} 为这个账号执行了改密计划.({})"
+
+#: audits/handler.py:168
+#, fuzzy
+#| msgid "User {} {} it."
+msgid "User {} login into this service.[{}]"
+msgstr "用户 {} {}了它."
+
+#: audits/models.py:32 audits/models.py:59 audits/models.py:101
#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:95
msgid "Remote addr"
msgstr "远端地址"
-#: audits/models.py:37 audits/serializers.py:32
+#: audits/models.py:37 audits/serializers.py:33
msgid "Operate"
msgstr "操作"
@@ -1787,7 +1816,7 @@ msgstr "文件名"
msgid "File transfer log"
msgstr "文件管理"
-#: audits/models.py:53 audits/serializers.py:86
+#: audits/models.py:53 audits/serializers.py:93
msgid "Resource Type"
msgstr "资源类型"
@@ -1795,71 +1824,69 @@ msgstr "资源类型"
msgid "Resource"
msgstr "资源"
-#: audits/models.py:60 audits/models.py:104
+#: audits/models.py:60 audits/models.py:103
#: terminal/backends/command/serializers.py:41
msgid "Datetime"
msgstr "日期"
-#: audits/models.py:63
-#, fuzzy
-#| msgid "Is active"
-msgid "Is Activity"
-msgstr "激活"
+#: audits/models.py:62
+msgid "Detail"
+msgstr ""
-#: audits/models.py:93
+#: audits/models.py:92
msgid "Operate log"
msgstr "操作日志"
-#: audits/models.py:100
+#: audits/models.py:99
msgid "Change by"
msgstr "修改者"
-#: audits/models.py:110
+#: audits/models.py:109
msgid "Password change log"
msgstr "改密日志"
-#: audits/models.py:117
+#: audits/models.py:116
msgid "Login type"
msgstr "登录方式"
-#: audits/models.py:119 tickets/models/ticket/login_confirm.py:10
+#: audits/models.py:118 tickets/models/ticket/login_confirm.py:10
msgid "Login ip"
msgstr "登录IP"
-#: audits/models.py:121
+#: audits/models.py:120
#: authentication/templates/authentication/_msg_different_city.html:11
#: tickets/models/ticket/login_confirm.py:11
msgid "Login city"
msgstr "登录城市"
-#: audits/models.py:124 audits/serializers.py:62
+#: audits/models.py:123 audits/serializers.py:63
msgid "User agent"
msgstr "用户代理"
-#: audits/models.py:127 audits/serializers.py:46
+#: audits/models.py:126 audits/serializers.py:47
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: users/forms/profile.py:65 users/models/user.py:698
#: users/serializers/profile.py:126
msgid "MFA"
msgstr "MFA"
-#: audits/models.py:137
+#: audits/models.py:136
msgid "Date login"
msgstr "登录日期"
-#: audits/models.py:139 audits/serializers.py:64
+#: audits/models.py:138 audits/serializers.py:65
msgid "Authentication backend"
msgstr "认证方式"
-#: audits/models.py:180
+#: audits/models.py:182
msgid "User login log"
msgstr "用户登录日志"
-#: audits/serializers.py:63
+#: audits/serializers.py:64
msgid "Reason display"
msgstr "原因描述"
-#: audits/serializers.py:112
+#: audits/serializers.py:120
#, fuzzy
#| msgid "User {} {} it."
msgid "User {} {} this resource."
@@ -1962,7 +1989,7 @@ msgid "Authentication"
msgstr "认证"
#: authentication/backends/custom.py:58
-#: authentication/backends/oauth2/backends.py:158
+#: authentication/backends/oauth2/backends.py:167
msgid "User invalid, disabled or expired"
msgstr "用户无效,已禁用或已过期"
@@ -2985,11 +3012,6 @@ msgstr "节点"
msgid "File"
msgstr "文件"
-#: common/serializers/fields.py:100 tickets/serializers/ticket/common.py:58
-#: xpack/plugins/cloud/serializers/account_attrs.py:56
-msgid "This field is required."
-msgstr "该字段是必填项。"
-
#: common/serializers/fields.py:101
#, python-brace-format
msgid "Invalid pk \"{pk_value}\" - object does not exist."
@@ -3128,7 +3150,7 @@ msgstr "Ansible 已禁用"
msgid "Skip hosts below:"
msgstr "跳过以下主机: "
-#: ops/api/celery.py:63 ops/api/celery.py:78
+#: ops/api/celery.py:58 ops/api/celery.py:73
msgid "Waiting task start"
msgstr "等待任务开始"
@@ -3392,31 +3414,31 @@ msgstr "花费时间"
msgid "Task"
msgstr "任务"
-#: ops/tasks.py:28
+#: ops/tasks.py:27
msgid "Run ansible task"
msgstr "运行 Ansible 任务"
-#: ops/tasks.py:35
+#: ops/tasks.py:43
msgid "Run ansible task execution"
msgstr "开始执行 Ansible 任务"
-#: ops/tasks.py:49
+#: ops/tasks.py:57
msgid "Periodic clear celery tasks"
msgstr "周期清理不可用任务"
-#: ops/tasks.py:51
+#: ops/tasks.py:59
msgid "Clean celery log period"
msgstr "定期清除任务日志"
-#: ops/tasks.py:68
+#: ops/tasks.py:76
msgid "Clear celery periodic tasks"
msgstr "清理周期任务"
-#: ops/tasks.py:91
+#: ops/tasks.py:99
msgid "Create or update periodic tasks"
msgstr "创建或更新周期任务"
-#: ops/tasks.py:99
+#: ops/tasks.py:107
msgid "Periodic check service performance"
msgstr "周期检测服务性能"
@@ -3489,7 +3511,7 @@ msgstr "组织"
msgid "Org name"
msgstr "组织名称"
-#: orgs/models.py:70 rbac/models/role.py:36 terminal/models/applet/applet.py:29
+#: orgs/models.py:70 rbac/models/role.py:36 terminal/models/applet/applet.py:30
msgid "Builtin"
msgstr "内置的"
@@ -3637,27 +3659,27 @@ msgstr "{} 至少有一个系统角色"
msgid "RBAC"
msgstr "RBAC"
-#: rbac/builtin.py:109
+#: rbac/builtin.py:110
msgid "SystemAdmin"
msgstr "系统管理员"
-#: rbac/builtin.py:112
+#: rbac/builtin.py:113
msgid "SystemAuditor"
msgstr "系统审计员"
-#: rbac/builtin.py:115
+#: rbac/builtin.py:116
msgid "SystemComponent"
msgstr "系统组件"
-#: rbac/builtin.py:121
+#: rbac/builtin.py:122
msgid "OrgAdmin"
msgstr "组织管理员"
-#: rbac/builtin.py:124
+#: rbac/builtin.py:125
msgid "OrgAuditor"
msgstr "组织审计员"
-#: rbac/builtin.py:127
+#: rbac/builtin.py:128
msgid "OrgUser"
msgstr "组织用户"
@@ -3737,7 +3759,7 @@ msgstr "权限"
msgid "Users amount"
msgstr "用户数量"
-#: rbac/serializers/role.py:28 terminal/models/applet/applet.py:24
+#: rbac/serializers/role.py:28 terminal/models/applet/applet.py:25
msgid "Display name"
msgstr "显示名称"
@@ -3805,8 +3827,8 @@ msgstr "任务中心"
msgid "My assets"
msgstr "我的资产"
-#: rbac/tree.py:57 terminal/models/applet/applet.py:39
-#: terminal/models/applet/applet.py:133 terminal/models/applet/host.py:27
+#: rbac/tree.py:57 terminal/models/applet/applet.py:40
+#: terminal/models/applet/applet.py:142 terminal/models/applet/host.py:27
msgid "Applet"
msgstr "远程应用"
@@ -5181,6 +5203,12 @@ msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远
msgid "Offline video player"
msgstr "离线录像播放器"
+#: terminal/api/applet/applet.py:48 terminal/api/applet/applet.py:51
+#, fuzzy
+#| msgid "Invalid ip"
+msgid "Invalid zip file"
+msgstr "无效IP"
+
#: terminal/api/component/endpoint.py:31
msgid "Not found protocol query params"
msgstr ""
@@ -5318,19 +5346,23 @@ msgstr "不支持批量创建"
msgid "Storage is invalid"
msgstr "存储无效"
-#: terminal/models/applet/applet.py:26
+#: terminal/models/applet/applet.py:27
msgid "Author"
msgstr "作者"
-#: terminal/models/applet/applet.py:31
+#: terminal/models/applet/applet.py:32
msgid "Tags"
msgstr "标签"
-#: terminal/models/applet/applet.py:35 terminal/serializers/storage.py:157
+#: terminal/models/applet/applet.py:36 terminal/serializers/storage.py:157
msgid "Hosts"
msgstr "主机"
-#: terminal/models/applet/applet.py:135 terminal/models/applet/host.py:33
+#: terminal/models/applet/applet.py:81
+msgid "Applet pkg not valid, Missing file {}"
+msgstr ""
+
+#: terminal/models/applet/applet.py:144 terminal/models/applet/host.py:33
#: terminal/models/applet/host.py:105
msgid "Hosting"
msgstr "宿主机"
@@ -5953,7 +5985,7 @@ msgstr "工单快照"
msgid "Please try again"
msgstr "请再次尝试"
-#: tickets/models/ticket/general.py:458
+#: tickets/models/ticket/general.py:461
msgid "Super ticket"
msgstr "超级工单"
@@ -7185,6 +7217,14 @@ msgstr "旗舰版"
msgid "Community edition"
msgstr "社区版"
+#~ msgid "Protocols enabled"
+#~ msgstr "启用协议"
+
+#, fuzzy
+#~| msgid "Is active"
+#~ msgid "Is Activity"
+#~ msgstr "激活"
+
#~ msgid "Dynamic username"
#~ msgstr "同名账号"
@@ -7203,12 +7243,6 @@ msgstr "社区版"
#~ msgid "Change password method"
#~ msgstr "更改密码方式"
-#~ msgid "{} used account[{}], login method[{}] login the asset."
-#~ msgstr "{} 使用账户[{}], 登录方式[{}]登录了这个资产."
-
-#~ msgid "User {} has executed change auth plan for this account.({})"
-#~ msgstr "用户 {} 为这个账号执行了改密计划.({})"
-
#~ msgid "Applet host"
#~ msgstr "远程应用发布机"
From 2ea8e30ca5dcaa43eb6ee6cb915734cc6f9eaece Mon Sep 17 00:00:00 2001
From: fit2bot <68588906+fit2bot@users.noreply.github.com>
Date: Tue, 31 Jan 2023 16:17:45 +0800
Subject: [PATCH 13/92] =?UTF-8?q?fix:=20=E8=B4=A6=E5=8F=B7=E5=A4=87?=
=?UTF-8?q?=E4=BB=BD=E6=97=A0=E6=B3=95=E6=89=A7=E8=A1=8C=20(#9379)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: feng <1304903146@qq.com>
---
apps/accounts/serializers/account/backup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/accounts/serializers/account/backup.py b/apps/accounts/serializers/account/backup.py
index 57d4c9ccc..0c601c3f4 100644
--- a/apps/accounts/serializers/account/backup.py
+++ b/apps/accounts/serializers/account/backup.py
@@ -36,7 +36,7 @@ class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSer
class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer):
- trigger = LabeledChoiceField(choices=Trigger.choices, label=_("Trigger mode"))
+ trigger = LabeledChoiceField(choices=Trigger.choices, label=_("Trigger mode"), read_only=True)
class Meta:
model = AccountBackupExecution
From 9ec7a8ac61b61f771ee44442f7938f43a4a7abf0 Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 17:46:56 +0800
Subject: [PATCH 14/92] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20asset=20info?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/assets/api/asset/asset.py | 8 ++-
apps/assets/models/asset/common.py | 52 +++++++++----------
apps/assets/serializers/asset/common.py | 26 +++++++---
.../serializers/connect_token_secret.py | 3 +-
apps/ops/ansible/inventory.py | 2 +-
apps/terminal/applets/navicat/app.py | 3 +-
6 files changed, 55 insertions(+), 39 deletions(-)
diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py
index c0dda34c7..a02d35121 100644
--- a/apps/assets/api/asset/asset.py
+++ b/apps/assets/api/asset/asset.py
@@ -2,7 +2,6 @@
#
import django_filters
from django.db.models import Q
-from django.utils.translation import ugettext_lazy as _
from rest_framework.decorators import action
from rest_framework.response import Response
@@ -72,11 +71,13 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
("platform", serializers.PlatformSerializer),
("suggestion", serializers.MiniAssetSerializer),
("gateways", serializers.GatewaySerializer),
+ ("spec_info", serializers.SpecSerializer),
)
rbac_perms = (
("match", "assets.match_asset"),
("platform", "assets.view_platform"),
("gateways", "assets.view_gateway"),
+ ("spec_info", "assets.view_asset"),
)
extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend]
@@ -94,6 +95,11 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
serializer = super().get_serializer(instance=asset.platform)
return Response(serializer.data)
+ @action(methods=["GET"], detail=True, url_path="spec-info")
+ def spec_info(self, *args, **kwargs):
+ asset = super().get_object()
+ return Response(asset.spec_info)
+
@action(methods=["GET"], detail=True, url_path="gateways")
def gateways(self, *args, **kwargs):
asset = self.get_object()
diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py
index 60f2f3c14..dfbf2ecf0 100644
--- a/apps/assets/models/asset/common.py
+++ b/apps/assets/models/asset/common.py
@@ -10,8 +10,8 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from assets import const
-from common.utils import lazyproperty
from common.db.fields import EncryptMixin
+from common.utils import lazyproperty
from orgs.mixins.models import OrgManager, JMSOrgBaseModel
from ..base import AbsConnectivity
from ..platform import Platform
@@ -113,45 +113,47 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
- info = models.JSONField(verbose_name='Info', default=dict, blank=True)
+ info = models.JSONField(verbose_name='Info', default=dict, blank=True) # 资产的一些信息,如 硬件信息
objects = AssetManager.from_queryset(AssetQuerySet)()
def __str__(self):
return '{0.name}({0.address})'.format(self)
- @property
- def specific(self):
- instance = getattr(self, self.category, None)
- if not instance:
- return {}
- specific_fields = self.get_specific_fields(instance)
+ @staticmethod
+ def get_spec_values(instance, fields):
info = {}
- for i in specific_fields:
+ for i in fields:
v = getattr(instance, i.name)
if isinstance(i, models.JSONField) and not isinstance(v, (list, dict)):
v = json.loads(v)
info[i.name] = v
return info
- @property
+ @lazyproperty
def spec_info(self):
instance = getattr(self, self.category, None)
if not instance:
- return []
- specific_fields = self.get_specific_fields(instance)
- info = [
- {
- 'label': i.verbose_name,
- 'name': i.name,
- 'value': getattr(instance, i.name)
- }
- for i in specific_fields
- ]
- return info
+ return {}
+ spec_fields = self.get_spec_fields(instance)
+ return self.get_spec_values(instance, spec_fields)
+
+ @staticmethod
+ def get_spec_fields(instance, secret=False):
+ spec_fields = [i for i in instance._meta.local_fields if i.name != 'asset_ptr']
+ spec_fields = [i for i in spec_fields if isinstance(i, EncryptMixin) == secret]
+ return spec_fields
@lazyproperty
- def enabled_info(self):
+ def secret_info(self):
+ instance = getattr(self, self.category, None)
+ if not instance:
+ return {}
+ spec_fields = self.get_spec_fields(instance, secret=True)
+ return self.get_spec_values(instance, spec_fields)
+
+ @lazyproperty
+ def auto_info(self):
platform = self.platform
automation = self.platform.automation
return {
@@ -165,12 +167,6 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
'gather_accounts_enabled': automation.gather_accounts_enabled,
}
- @staticmethod
- def get_specific_fields(instance):
- specific_fields = [i for i in instance._meta.local_fields if i.name != 'asset_ptr']
- specific_fields = [i for i in specific_fields if not isinstance(i, EncryptMixin)]
- return specific_fields
-
def get_target_ip(self):
return self.address
diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py
index 89d5a66eb..47c866603 100644
--- a/apps/assets/serializers/asset/common.py
+++ b/apps/assets/serializers/asset/common.py
@@ -18,7 +18,7 @@ __all__ = [
'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer',
'AssetTaskSerializer', 'AssetsTaskSerializer', 'AssetProtocolsSerializer',
'AssetDetailSerializer', 'DetailMixin', 'AssetAccountSerializer',
- 'AccountSecretSerializer'
+ 'AccountSecretSerializer', 'SpecSerializer'
]
@@ -113,13 +113,25 @@ class AccountSecretSerializer(SecretReadableMixin, CommonModelSerializer):
}
+class SpecSerializer(serializers.Serializer):
+ # 数据库
+ db_name = serializers.CharField(label=_("Database"), max_length=128, required=False)
+ use_ssl = serializers.BooleanField(label=_("Use SSL"), required=False)
+ allow_invalid_cert = serializers.BooleanField(label=_("Allow invalid cert"), required=False)
+ # Web
+ autofill = serializers.CharField(label=_("Auto fill"), required=False)
+ username_selector = serializers.CharField(label=_("Username selector"), required=False)
+ password_selector = serializers.CharField(label=_("Password selector"), required=False)
+ submit_selector = serializers.CharField(label=_("Submit selector"), required=False)
+ script = serializers.JSONField(label=_("Script"), required=False)
+
+
class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSerializer):
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type'))
labels = AssetLabelSerializer(many=True, required=False, label=_('Label'))
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'))
accounts = AssetAccountSerializer(many=True, required=False, write_only=True, label=_('Account'))
- enabled_info = serializers.DictField(read_only=True, label=_('Enabled info'))
class Meta:
model = Asset
@@ -127,11 +139,11 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
fields_small = fields_mini + ['is_active', 'comment']
fields_fk = ['domain', 'platform']
fields_m2m = [
- 'nodes', 'labels', 'protocols', 'nodes_display', 'accounts'
+ 'nodes', 'labels', 'protocols',
+ 'nodes_display', 'accounts'
]
read_only_fields = [
- 'category', 'type', 'info', 'enabled_info',
- 'connectivity', 'date_verified',
+ 'category', 'type', 'connectivity', 'date_verified',
'created_by', 'date_created'
]
fields = fields_small + fields_fk + fields_m2m + read_only_fields
@@ -235,11 +247,13 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
class DetailMixin(serializers.Serializer):
accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts'))
+ spec_info = serializers.DictField(label=_('Spec info'), read_only=True)
+ auto_info = serializers.DictField(read_only=True, label=_('Auto info'))
def get_field_names(self, declared_fields, info):
names = super().get_field_names(declared_fields, info)
names.extend([
- 'accounts', 'info', 'specific', 'spec_info'
+ 'accounts', 'info', 'spec_info', 'auto_info'
])
return names
diff --git a/apps/authentication/serializers/connect_token_secret.py b/apps/authentication/serializers/connect_token_secret.py
index 4585111ea..41ee8d116 100644
--- a/apps/authentication/serializers/connect_token_secret.py
+++ b/apps/authentication/serializers/connect_token_secret.py
@@ -31,7 +31,8 @@ class _ConnectionTokenAssetSerializer(serializers.ModelSerializer):
model = Asset
fields = [
'id', 'name', 'address', 'protocols',
- 'category', 'type', 'org_id', 'specific'
+ 'category', 'type', 'org_id', 'spec_info',
+ 'secret_info',
]
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index e50a0801f..243f1b319 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -105,7 +105,7 @@ class JMSInventory:
'id': str(asset.id), 'name': asset.name, 'address': asset.address,
'type': asset.type, 'category': asset.category,
'protocol': asset.protocol, 'port': asset.port,
- 'specific': asset.specific,
+ 'specific': asset.spec,
'protocols': [{'name': p.name, 'port': p.port} for p in protocols],
},
'jms_account': {
diff --git a/apps/terminal/applets/navicat/app.py b/apps/terminal/applets/navicat/app.py
index 33d89d2d2..37c80f144 100644
--- a/apps/terminal/applets/navicat/app.py
+++ b/apps/terminal/applets/navicat/app.py
@@ -11,7 +11,6 @@ if sys.platform == 'win32':
)
from common import wait_pid, BaseApplication
-
_default_path = r'C:\Program Files\PremiumSoft\Navicat Premium 16\navicat.exe'
@@ -24,7 +23,7 @@ class AppletApplication(BaseApplication):
self.privileged = self.account.privileged
self.host = self.asset.address
self.port = self.asset.get_protocol_port(self.protocol)
- self.db = self.asset.specific.db_name
+ self.db = self.asset.spec.db_name
self.name = '%s-%s' % (self.host, self.db)
self.pid = None
self.app = None
From 41154d3793aeba3f476d1928f9f1de7ab17e7aa7 Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 17:57:06 +0800
Subject: [PATCH 15/92] asset: specific to spec_info
---
.../change_secret/database/mongodb/main.yml | 12 ++--
.../change_secret/database/oracle/main.yml | 10 ++--
.../database/postgresql/main.yml | 10 ++--
.../change_secret/database/sqlserver/main.yml | 58 +++++++++----------
.../gather_accounts/database/mongodb/main.yml | 2 +-
.../gather_accounts/database/oracle/main.yml | 2 +-
.../database/postgresql/main.yml | 2 +-
.../verify_account/database/mongodb/main.yml | 2 +-
.../verify_account/database/oracle/main.yml | 2 +-
.../database/postgresql/main.yml | 2 +-
.../database/sqlserver/main.yml | 2 +-
.../gather_facts/database/mongodb/main.yml | 2 +-
.../gather_facts/database/oracle/main.yml | 2 +-
.../gather_facts/database/postgresql/main.yml | 2 +-
.../ping/database/mongodb/main.yml | 2 +-
.../automations/ping/database/oracle/main.yml | 2 +-
.../ping/database/postgresql/main.yml | 2 +-
.../ping/database/sqlserver/main.yml | 2 +-
apps/terminal/applets/chrome/app.py | 4 +-
apps/terminal/applets/navicat/app.py | 2 +-
20 files changed, 62 insertions(+), 62 deletions(-)
diff --git a/apps/accounts/automations/change_secret/database/mongodb/main.yml b/apps/accounts/automations/change_secret/database/mongodb/main.yml
index 02a568e0b..95702c8bb 100644
--- a/apps/accounts/automations/change_secret/database/mongodb/main.yml
+++ b/apps/accounts/automations/change_secret/database/mongodb/main.yml
@@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
register: db_info
- name: Display MongoDB version
@@ -24,8 +24,8 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
- db: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
+ db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
when: db_info is succeeded
@@ -37,7 +37,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
when:
- - db_info is succeeded
- - change_info is succeeded
+ - db_info is succeeded
+ - change_info is succeeded
diff --git a/apps/accounts/automations/change_secret/database/oracle/main.yml b/apps/accounts/automations/change_secret/database/oracle/main.yml
index c7b20a8db..a58d776a7 100644
--- a/apps/accounts/automations/change_secret/database/oracle/main.yml
+++ b/apps/accounts/automations/change_secret/database/oracle/main.yml
@@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ jms_account.mode }}"
register: db_info
@@ -25,7 +25,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ jms_account.mode }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
@@ -38,8 +38,8 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ account.mode }}"
when:
- - db_info is succeeded
- - change_info is succeeded
+ - db_info is succeeded
+ - change_info is succeeded
diff --git a/apps/accounts/automations/change_secret/database/postgresql/main.yml b/apps/accounts/automations/change_secret/database/postgresql/main.yml
index ada11bbd6..6a903171f 100644
--- a/apps/accounts/automations/change_secret/database/postgresql/main.yml
+++ b/apps/accounts/automations/change_secret/database/postgresql/main.yml
@@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_db: "{{ jms_asset.specific.db_name }}"
+ login_db: "{{ jms_asset.spec_info.db_name }}"
register: db_info
- name: Display PostgreSQL version
@@ -24,7 +24,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- db: "{{ jms_asset.specific.db_name }}"
+ db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
when: db_info is succeeded
@@ -36,7 +36,7 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- db: "{{ jms_asset.specific.db_name }}"
+ db: "{{ jms_asset.spec_info.db_name }}"
when:
- - db_info is succeeded
- - change_info is succeeded
+ - db_info is succeeded
+ - change_info is succeeded
diff --git a/apps/accounts/automations/change_secret/database/sqlserver/main.yml b/apps/accounts/automations/change_secret/database/sqlserver/main.yml
index a617a1434..d3ea188b0 100644
--- a/apps/accounts/automations/change_secret/database/sqlserver/main.yml
+++ b/apps/accounts/automations/change_secret/database/sqlserver/main.yml
@@ -10,38 +10,38 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- name: '{{ jms_asset.specific.db_name }}'
+ name: '{{ jms_asset.spec_info.db_name }}'
script: |
SELECT @@version
register: db_info
- - name: SQLServer version
- set_fact:
- info:
- version: "{{ db_info.query_results[0][0][0][0].splitlines()[0] }}"
- - debug:
- var: info
+ - name: SQLServer version
+ set_fact:
+ info:
+ version: "{{ db_info.query_results[0][0][0][0].splitlines()[0] }}"
+ - debug:
+ var: info
- - name: Change SQLServer password
- community.general.mssql_script:
- login_user: "{{ jms_account.username }}"
- login_password: "{{ jms_account.secret }}"
- login_host: "{{ jms_asset.address }}"
- login_port: "{{ jms_asset.port }}"
- name: '{{ jms_asset.specific.db_name }}'
- script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
- when: db_info is succeeded
- register: change_info
+ - name: Change SQLServer password
+ community.general.mssql_script:
+ login_user: "{{ jms_account.username }}"
+ login_password: "{{ jms_account.secret }}"
+ login_host: "{{ jms_asset.address }}"
+ login_port: "{{ jms_asset.port }}"
+ name: '{{ jms_asset.spec_info.db_name }}'
+ script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
+ when: db_info is succeeded
+ register: change_info
- - name: Verify password
- community.general.mssql_script:
- login_user: "{{ account.username }}"
- login_password: "{{ account.secret }}"
- login_host: "{{ jms_asset.address }}"
- login_port: "{{ jms_asset.port }}"
- name: '{{ jms_asset.specific.db_name }}'
- script: |
- SELECT @@version
- when:
- - db_info is succeeded
- - change_info is succeeded
+ - name: Verify password
+ community.general.mssql_script:
+ login_user: "{{ account.username }}"
+ login_password: "{{ account.secret }}"
+ login_host: "{{ jms_asset.address }}"
+ login_port: "{{ jms_asset.port }}"
+ name: '{{ jms_asset.spec_info.db_name }}'
+ script: |
+ SELECT @@version
+ when:
+ - db_info is succeeded
+ - change_info is succeeded
diff --git a/apps/accounts/automations/gather_accounts/database/mongodb/main.yml b/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
index fd7a296b7..b94f2be1d 100644
--- a/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
+++ b/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
@@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
filter: users
register: db_info
diff --git a/apps/accounts/automations/gather_accounts/database/oracle/main.yml b/apps/accounts/automations/gather_accounts/database/oracle/main.yml
index a0e20ff7b..f492583f4 100644
--- a/apps/accounts/automations/gather_accounts/database/oracle/main.yml
+++ b/apps/accounts/automations/gather_accounts/database/oracle/main.yml
@@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ jms_account.mode }}"
filter: users
register: db_info
diff --git a/apps/accounts/automations/gather_accounts/database/postgresql/main.yml b/apps/accounts/automations/gather_accounts/database/postgresql/main.yml
index f282b390d..c1c25c8b4 100644
--- a/apps/accounts/automations/gather_accounts/database/postgresql/main.yml
+++ b/apps/accounts/automations/gather_accounts/database/postgresql/main.yml
@@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_db: "{{ jms_asset.specific.db_name }}"
+ login_db: "{{ jms_asset.spec_info.db_name }}"
filter: "roles"
register: db_info
diff --git a/apps/accounts/automations/verify_account/database/mongodb/main.yml b/apps/accounts/automations/verify_account/database/mongodb/main.yml
index 1cf79b694..a65eee915 100644
--- a/apps/accounts/automations/verify_account/database/mongodb/main.yml
+++ b/apps/accounts/automations/verify_account/database/mongodb/main.yml
@@ -10,4 +10,4 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
diff --git a/apps/accounts/automations/verify_account/database/oracle/main.yml b/apps/accounts/automations/verify_account/database/oracle/main.yml
index ed4091401..12896f09a 100644
--- a/apps/accounts/automations/verify_account/database/oracle/main.yml
+++ b/apps/accounts/automations/verify_account/database/oracle/main.yml
@@ -10,5 +10,5 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ jms_account.mode }}"
diff --git a/apps/accounts/automations/verify_account/database/postgresql/main.yml b/apps/accounts/automations/verify_account/database/postgresql/main.yml
index 08db4d869..cb6344b39 100644
--- a/apps/accounts/automations/verify_account/database/postgresql/main.yml
+++ b/apps/accounts/automations/verify_account/database/postgresql/main.yml
@@ -10,4 +10,4 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- db: "{{ jms_asset.specific.db_name }}"
+ db: "{{ jms_asset.spec_info.db_name }}"
diff --git a/apps/accounts/automations/verify_account/database/sqlserver/main.yml b/apps/accounts/automations/verify_account/database/sqlserver/main.yml
index 256803702..bb079fa59 100644
--- a/apps/accounts/automations/verify_account/database/sqlserver/main.yml
+++ b/apps/accounts/automations/verify_account/database/sqlserver/main.yml
@@ -10,6 +10,6 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- name: '{{ jms_asset.specific.db_name }}'
+ name: '{{ jms_asset.spec_info.db_name }}'
script: |
SELECT @@version
diff --git a/apps/assets/automations/gather_facts/database/mongodb/main.yml b/apps/assets/automations/gather_facts/database/mongodb/main.yml
index 37ce8bbd3..7cfe212a4 100644
--- a/apps/assets/automations/gather_facts/database/mongodb/main.yml
+++ b/apps/assets/automations/gather_facts/database/mongodb/main.yml
@@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
register: db_info
- name: Define info by set_fact
diff --git a/apps/assets/automations/gather_facts/database/oracle/main.yml b/apps/assets/automations/gather_facts/database/oracle/main.yml
index 21ab639a4..f88fc11a1 100644
--- a/apps/assets/automations/gather_facts/database/oracle/main.yml
+++ b/apps/assets/automations/gather_facts/database/oracle/main.yml
@@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ jms_account.mode }}"
register: db_info
diff --git a/apps/assets/automations/gather_facts/database/postgresql/main.yml b/apps/assets/automations/gather_facts/database/postgresql/main.yml
index a3c481b48..302fe1f9d 100644
--- a/apps/assets/automations/gather_facts/database/postgresql/main.yml
+++ b/apps/assets/automations/gather_facts/database/postgresql/main.yml
@@ -10,7 +10,7 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_db: "{{ jms_asset.specific.db_name }}"
+ login_db: "{{ jms_asset.spec_info.db_name }}"
register: db_info
- name: Define info by set_fact
diff --git a/apps/assets/automations/ping/database/mongodb/main.yml b/apps/assets/automations/ping/database/mongodb/main.yml
index 867c51ace..f89c44b1e 100644
--- a/apps/assets/automations/ping/database/mongodb/main.yml
+++ b/apps/assets/automations/ping/database/mongodb/main.yml
@@ -10,4 +10,4 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
diff --git a/apps/assets/automations/ping/database/oracle/main.yml b/apps/assets/automations/ping/database/oracle/main.yml
index fefad7148..9cc6695ac 100644
--- a/apps/assets/automations/ping/database/oracle/main.yml
+++ b/apps/assets/automations/ping/database/oracle/main.yml
@@ -10,5 +10,5 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_database: "{{ jms_asset.specific.db_name }}"
+ login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ jms_account.mode }}"
diff --git a/apps/assets/automations/ping/database/postgresql/main.yml b/apps/assets/automations/ping/database/postgresql/main.yml
index e97b3946d..f48c7d4f7 100644
--- a/apps/assets/automations/ping/database/postgresql/main.yml
+++ b/apps/assets/automations/ping/database/postgresql/main.yml
@@ -10,4 +10,4 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- login_db: "{{ jms_asset.specific.db_name }}"
+ login_db: "{{ jms_asset.spec_info.db_name }}"
diff --git a/apps/assets/automations/ping/database/sqlserver/main.yml b/apps/assets/automations/ping/database/sqlserver/main.yml
index 839a785a5..ca242538e 100644
--- a/apps/assets/automations/ping/database/sqlserver/main.yml
+++ b/apps/assets/automations/ping/database/sqlserver/main.yml
@@ -10,6 +10,6 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
- name: '{{ jms_asset.specific.db_name }}'
+ name: '{{ jms_asset.spec_info.db_name }}'
script: |
SELECT @@version
diff --git a/apps/terminal/applets/chrome/app.py b/apps/terminal/applets/chrome/app.py
index 89ae3fc3b..7d9b12c36 100644
--- a/apps/terminal/applets/chrome/app.py
+++ b/apps/terminal/applets/chrome/app.py
@@ -89,11 +89,11 @@ class WebAPP(object):
self.extra_data = self.asset.specific
self._steps = list()
- autofill_type = self.asset.specific.autofill
+ autofill_type = self.asset.spec_info.autofill
if autofill_type == "basic":
self._steps = self._default_custom_steps()
elif autofill_type == "script":
- script_list = self.asset.specific.script
+ script_list = self.asset.spec_info.script
steps = sorted(script_list, key=lambda step_item: step_item.step)
for item in steps:
val = item.value
diff --git a/apps/terminal/applets/navicat/app.py b/apps/terminal/applets/navicat/app.py
index 37c80f144..f7447007b 100644
--- a/apps/terminal/applets/navicat/app.py
+++ b/apps/terminal/applets/navicat/app.py
@@ -23,7 +23,7 @@ class AppletApplication(BaseApplication):
self.privileged = self.account.privileged
self.host = self.asset.address
self.port = self.asset.get_protocol_port(self.protocol)
- self.db = self.asset.spec.db_name
+ self.db = self.asset.spec_info.db_name
self.name = '%s-%s' % (self.host, self.db)
self.pid = None
self.app = None
From ef51e74b8ef900ea501b53b623043ec6366dd393 Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 18:06:44 +0800
Subject: [PATCH 16/92] =?UTF-8?q?perf=EF=BC=9A=20=E7=BB=A7=E7=BB=AD?=
=?UTF-8?q?=E6=9B=BF=E6=8D=A2=20spec?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/accounts/models/base.py | 2 +-
apps/accounts/serializers/account/base.py | 6 +++---
apps/ops/ansible/inventory.py | 2 +-
apps/perms/serializers/user_permission.py | 6 +++---
apps/terminal/applets/chrome/app.py | 10 +++++-----
5 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/apps/accounts/models/base.py b/apps/accounts/models/base.py
index ffae7051b..37b97f21a 100644
--- a/apps/accounts/models/base.py
+++ b/apps/accounts/models/base.py
@@ -50,7 +50,7 @@ class BaseAccount(JMSOrgBaseModel):
return bool(self.username)
@property
- def specific(self):
+ def spec_info(self):
data = {}
if self.secret_type != SecretType.SSH_KEY:
return data
diff --git a/apps/accounts/serializers/account/base.py b/apps/accounts/serializers/account/base.py
index d92c4cc9c..fb5b51d6f 100644
--- a/apps/accounts/serializers/account/base.py
+++ b/apps/accounts/serializers/account/base.py
@@ -68,14 +68,14 @@ class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
fields_mini = ['id', 'name', 'username']
fields_small = fields_mini + [
'secret_type', 'secret', 'has_secret', 'passphrase',
- 'privileged', 'is_active', 'specific',
+ 'privileged', 'is_active', 'spec_info',
]
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
fields = fields_small + fields_other
read_only_fields = [
- 'has_secret', 'specific',
+ 'has_secret', 'spec_info',
'date_verified', 'created_by', 'date_created',
]
extra_kwargs = {
- 'specific': {'label': _('Specific')},
+ 'spec_info': {'label': _('Spec info')},
}
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index 243f1b319..0a3ce43af 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -105,7 +105,7 @@ class JMSInventory:
'id': str(asset.id), 'name': asset.name, 'address': asset.address,
'type': asset.type, 'category': asset.category,
'protocol': asset.protocol, 'port': asset.port,
- 'specific': asset.spec,
+ 'spec_info': asset.spec_info,
'protocols': [{'name': p.name, 'port': p.port} for p in protocols],
},
'jms_account': {
diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py
index 8e22623fc..27fde71d1 100644
--- a/apps/perms/serializers/user_permission.py
+++ b/apps/perms/serializers/user_permission.py
@@ -4,13 +4,13 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
+from accounts.models import Account
from assets.const import Category, AllTypes
from assets.models import Node, Asset, Platform
-from accounts.models import Account
from assets.serializers.asset.common import AssetProtocolsSerializer
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
-from perms.serializers.permission import ActionChoicesField
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
+from perms.serializers.permission import ActionChoicesField
__all__ = [
'NodePermedSerializer', 'AssetPermedSerializer',
@@ -32,7 +32,7 @@ class AssetPermedSerializer(OrgResourceModelSerializerMixin):
"id", "name", "address", 'domain', 'platform',
"comment", "org_id", "is_active",
]
- fields = only_fields + ['protocols', 'category', 'type', 'specific'] + ['org_name']
+ fields = only_fields + ['protocols', 'category', 'type', 'spec_info'] + ['org_name']
read_only_fields = fields
diff --git a/apps/terminal/applets/chrome/app.py b/apps/terminal/applets/chrome/app.py
index 7d9b12c36..491f1e09d 100644
--- a/apps/terminal/applets/chrome/app.py
+++ b/apps/terminal/applets/chrome/app.py
@@ -87,7 +87,7 @@ class WebAPP(object):
self.account = account
self.platform = platform
- self.extra_data = self.asset.specific
+ self.extra_data = self.asset.spec_info
self._steps = list()
autofill_type = self.asset.spec_info.autofill
if autofill_type == "basic":
@@ -105,24 +105,24 @@ class WebAPP(object):
def _default_custom_steps(self) -> list:
account = self.account
- specific_property = self.asset.specific
+ spec_info = self.asset.spec_info
default_steps = [
Step({
"step": 1,
"value": account.username,
- "target": specific_property.username_selector,
+ "target": spec_info.username_selector,
"command": "type"
}),
Step({
"step": 2,
"value": account.secret,
- "target": specific_property.password_selector,
+ "target": spec_info.password_selector,
"command": "type"
}),
Step({
"step": 3,
"value": "",
- "target": specific_property.submit_selector,
+ "target": spec_info.submit_selector,
"command": "click"
})
]
From ae79584faa80491213fb9242029ddf253c017d83 Mon Sep 17 00:00:00 2001
From: Bai
Date: Tue, 31 Jan 2023 18:07:55 +0800
Subject: [PATCH 17/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=B4=A6?=
=?UTF-8?q?=E5=8F=B7=E5=88=97=E8=A1=A8=20secret=20=E5=AD=97=E6=AE=B5?=
=?UTF-8?q?=E5=90=8D=E7=A7=B0=20=E5=AF=86=E9=92=A5/=E5=AF=86=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../migrations/0100_auto_20220711_1413.py | 76 +++++++++++++++++--
1 file changed, 71 insertions(+), 5 deletions(-)
diff --git a/apps/assets/migrations/0100_auto_20220711_1413.py b/apps/assets/migrations/0100_auto_20220711_1413.py
index 62fa1cbd5..16e75cbae 100644
--- a/apps/assets/migrations/0100_auto_20220711_1413.py
+++ b/apps/assets/migrations/0100_auto_20220711_1413.py
@@ -1,17 +1,17 @@
# Generated by Django 3.2.12 on 2022-07-11 06:13
import time
-from django.db import migrations
+from django.db import migrations, models
from assets.models import Platform
-def migrate_accounts(apps, schema_editor):
+def migrate_asset_accounts(apps, schema_editor):
auth_book_model = apps.get_model('assets', 'AuthBook')
account_model = apps.get_model('accounts', 'Account')
count = 0
bulk_size = 1000
- print("\n\tStart migrate accounts")
+ print("\n\tStart migrate asset accounts")
while True:
start = time.time()
auth_books = auth_book_model.objects \
@@ -71,11 +71,76 @@ def migrate_accounts(apps, schema_editor):
accounts.append(account)
account_model.objects.bulk_create(accounts, ignore_conflicts=True)
- print("\t - Create accounts: {}-{} using: {:.2f}s".format(
+ print("\t - Create asset accounts: {}-{} using: {:.2f}s".format(
count - len(auth_books), count, time.time() - start
))
+def migrate_db_accounts(apps, schema_editor):
+ app_perm_model = apps.get_model('perms', 'ApplicationPermission')
+ account_model = apps.get_model('accounts', 'Account')
+ perms = app_perm_model.objects.filter(category__in=['db', 'cloud'])
+
+ same_attrs = [
+ 'id', 'username', 'comment', 'date_created', 'date_updated',
+ 'created_by', 'org_id',
+ ]
+ auth_attrs = ['password', 'private_key', 'token']
+ all_attrs = same_attrs + auth_attrs
+
+ print("\n\tStart migrate app accounts")
+
+ index = 0
+ total = perms.count()
+
+ for perm in perms:
+ index += 1
+ start = time.time()
+
+ system_users = perm.system_users.all()
+ accounts = []
+ for s in system_users:
+ values = {'version': 1}
+ values.update({attr: getattr(s, attr, '') for attr in all_attrs})
+ values['created_by'] = str(s.id)
+
+ auth_infos = []
+ username = values['username']
+ for attr in auth_attrs:
+ secret = values.pop(attr, None)
+ if not secret:
+ continue
+
+ if attr == 'private_key':
+ secret_type = 'ssh_key'
+ name = f'{username}(ssh key)'
+ elif attr == 'token':
+ secret_type = 'token'
+ name = f'{username}(token)'
+ else:
+ secret_type = attr
+ name = username
+ auth_infos.append((name, secret_type, secret))
+
+ if not auth_infos:
+ auth_infos.append((username, 'password', ''))
+
+ for name, secret_type, secret in auth_infos:
+ account = account_model(**values, name=name, secret=secret, secret_type=secret_type)
+ accounts.append(account)
+
+ apps = perm.applications.all()
+ for app in apps:
+ for account in accounts:
+ setattr(account, 'asset_id', str(app.id))
+
+ account_model.objects.bulk_create(accounts, ignore_conflicts=True)
+
+ print("\t - Progress ({}/{}), Create app accounts: {} using: {:.2f}s".format(
+ index, total, len(accounts), time.time() - start
+ ))
+
+
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
@@ -83,5 +148,6 @@ class Migration(migrations.Migration):
]
operations = [
- migrations.RunPython(migrate_accounts),
+ migrations.RunPython(migrate_asset_accounts),
+ migrations.RunPython(migrate_db_accounts),
]
From 211a0abe9e3d3eb9ac8cfc49fe3f773affc00daa Mon Sep 17 00:00:00 2001
From: jiangweidong
Date: Thu, 19 Jan 2023 09:57:58 +0800
Subject: [PATCH 18/92] =?UTF-8?q?feat:=20=E6=B5=8B=E8=AF=95=E5=8F=AF?=
=?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=80=A7mongodb=E6=94=AF=E6=8C=81ssl?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../change_secret/database/mongodb/main.yml | 10 +++++++
.../verify_account/database/mongodb/main.yml | 5 ++++
.../gather_facts/database/mongodb/main.yml | 5 ++++
.../ping/database/mongodb/main.yml | 5 ++++
apps/ops/ansible/inventory.py | 26 +++++++++++++++++++
5 files changed, 51 insertions(+)
diff --git a/apps/accounts/automations/change_secret/database/mongodb/main.yml b/apps/accounts/automations/change_secret/database/mongodb/main.yml
index 02a568e0b..7f0ae2c6e 100644
--- a/apps/accounts/automations/change_secret/database/mongodb/main.yml
+++ b/apps/accounts/automations/change_secret/database/mongodb/main.yml
@@ -11,6 +11,11 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
+ ssl: "{{ jms_asset.specific.use_ssl }}"
+ ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.specific.client_key }}"
+ connection_options:
+ - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
register: db_info
- name: Display MongoDB version
@@ -38,6 +43,11 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
+ ssl: "{{ jms_asset.specific.use_ssl }}"
+ ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.specific.client_key }}"
+ connection_options:
+ - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
when:
- db_info is succeeded
- change_info is succeeded
diff --git a/apps/accounts/automations/verify_account/database/mongodb/main.yml b/apps/accounts/automations/verify_account/database/mongodb/main.yml
index 1cf79b694..4467ead07 100644
--- a/apps/accounts/automations/verify_account/database/mongodb/main.yml
+++ b/apps/accounts/automations/verify_account/database/mongodb/main.yml
@@ -11,3 +11,8 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
+ ssl: "{{ jms_asset.specific.use_ssl }}"
+ ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.specific.client_key }}"
+ connection_options:
+ - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
diff --git a/apps/assets/automations/gather_facts/database/mongodb/main.yml b/apps/assets/automations/gather_facts/database/mongodb/main.yml
index 37ce8bbd3..c94c82cd1 100644
--- a/apps/assets/automations/gather_facts/database/mongodb/main.yml
+++ b/apps/assets/automations/gather_facts/database/mongodb/main.yml
@@ -11,6 +11,11 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
+ ssl: "{{ jms_asset.specific.use_ssl }}"
+ ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.specific.client_key }}"
+ connection_options:
+ - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
register: db_info
- name: Define info by set_fact
diff --git a/apps/assets/automations/ping/database/mongodb/main.yml b/apps/assets/automations/ping/database/mongodb/main.yml
index 867c51ace..c1e1ed496 100644
--- a/apps/assets/automations/ping/database/mongodb/main.yml
+++ b/apps/assets/automations/ping/database/mongodb/main.yml
@@ -11,3 +11,8 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
+ ssl: "{{ jms_asset.specific.use_ssl }}"
+ ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.specific.client_key }}"
+ connection_options:
+ - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index e50a0801f..6b0e528a5 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -98,6 +98,30 @@ class JMSInventory:
if gateway:
host.update(self.make_proxy_command(gateway))
+ @staticmethod
+ def write_cert_to_file(filename, content):
+ if not content:
+ return ''
+ with open(filename, 'w') as f:
+ f.write(content)
+ return filename
+
+ def convert_cert_to_file(self, host, path_dir):
+ specific = host.get('jms_asset', {}).get('specific')
+ if not specific:
+ return host
+
+ cert_dir = os.path.join(path_dir, 'certs')
+ if not os.path.exists(cert_dir):
+ os.makedirs(cert_dir, 0o700, True)
+
+ for i in ('ca_cert', 'client_key', 'client_cert'):
+ result = self.write_cert_to_file(
+ os.path.join(cert_dir, i), specific.get(i)
+ )
+ host['jms_asset']['specific'][i] = result
+ return host
+
def asset_to_host(self, asset, account, automation, protocols, platform):
host = {
'name': '{}'.format(asset.name),
@@ -178,6 +202,8 @@ class JMSInventory:
if not automation.ansible_enabled:
host['error'] = _('Ansible disabled')
+ else:
+ host = self.convert_cert_to_file(host, path_dir)
if self.host_callback is not None:
host = self.host_callback(
From 633e12bf86694ba57695bdff8dfdb46cabdeae15 Mon Sep 17 00:00:00 2001
From: jiangweidong
Date: Thu, 19 Jan 2023 10:14:31 +0800
Subject: [PATCH 19/92] =?UTF-8?q?feat:=20=E5=85=B6=E4=BB=96mongodb?=
=?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E6=93=8D=E4=BD=9C=E6=94=AF=E6=8C=81?=
=?UTF-8?q?ssl?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../automations/change_secret/database/mongodb/main.yml | 5 +++++
.../automations/gather_accounts/database/mongodb/main.yml | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/apps/accounts/automations/change_secret/database/mongodb/main.yml b/apps/accounts/automations/change_secret/database/mongodb/main.yml
index 7f0ae2c6e..517274263 100644
--- a/apps/accounts/automations/change_secret/database/mongodb/main.yml
+++ b/apps/accounts/automations/change_secret/database/mongodb/main.yml
@@ -30,6 +30,11 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
+ ssl: "{{ jms_asset.specific.use_ssl }}"
+ ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.specific.client_key }}"
+ connection_options:
+ - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
db: "{{ jms_asset.specific.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
diff --git a/apps/accounts/automations/gather_accounts/database/mongodb/main.yml b/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
index fd7a296b7..d2478d7b5 100644
--- a/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
+++ b/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
@@ -11,6 +11,11 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
+ ssl: "{{ jms_asset.specific.use_ssl }}"
+ ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.specific.client_key }}"
+ connection_options:
+ - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
filter: users
register: db_info
From 6cda829f673867b7efd92bb09d4bc229d4cbf90e Mon Sep 17 00:00:00 2001
From: jiangweidong
Date: Thu, 19 Jan 2023 10:36:27 +0800
Subject: [PATCH 20/92] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=88=9B?=
=?UTF-8?q?=E5=BB=BA=E8=AF=81=E4=B9=A6=E6=96=87=E4=BB=B6=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/ops/ansible/inventory.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index 6b0e528a5..084c1e733 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -100,26 +100,26 @@ class JMSInventory:
@staticmethod
def write_cert_to_file(filename, content):
- if not content:
- return ''
with open(filename, 'w') as f:
f.write(content)
return filename
def convert_cert_to_file(self, host, path_dir):
- specific = host.get('jms_asset', {}).get('specific')
- if not specific:
+ specific = host.get('jms_asset', {}).get('specific', {})
+ cert_fields = ('ca_cert', 'client_key', 'client_cert')
+ filtered = list(filter(lambda x: specific.get(x), cert_fields))
+ if not filtered:
return host
cert_dir = os.path.join(path_dir, 'certs')
if not os.path.exists(cert_dir):
os.makedirs(cert_dir, 0o700, True)
- for i in ('ca_cert', 'client_key', 'client_cert'):
+ for f in filtered:
result = self.write_cert_to_file(
- os.path.join(cert_dir, i), specific.get(i)
+ os.path.join(cert_dir, f), specific.get(f)
)
- host['jms_asset']['specific'][i] = result
+ host['jms_asset']['specific'][f] = result
return host
def asset_to_host(self, asset, account, automation, protocols, platform):
From be670872e58b5e295156accdac0f3b846129e150 Mon Sep 17 00:00:00 2001
From: jiangweidong
Date: Sun, 29 Jan 2023 16:25:16 +0800
Subject: [PATCH 21/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E9=80=BB?=
=?UTF-8?q?=E8=BE=91=EF=BC=8C=E5=B0=86=E8=AF=81=E4=B9=A6=E6=96=87=E4=BB=B6?=
=?UTF-8?q?=E8=BD=AC=E6=8D=A2=E6=94=BE=E5=88=B0manager=E4=B8=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../change_secret/database/mongodb/main.yml | 8 ++---
.../automations/change_secret/manager.py | 10 +++++--
.../gather_accounts/database/mongodb/main.yml | 8 ++---
apps/assets/automations/base/manager.py | 29 +++++++++++++++++++
apps/ops/ansible/inventory.py | 26 -----------------
5 files changed, 45 insertions(+), 36 deletions(-)
diff --git a/apps/accounts/automations/change_secret/database/mongodb/main.yml b/apps/accounts/automations/change_secret/database/mongodb/main.yml
index 517274263..c64ad7c4c 100644
--- a/apps/accounts/automations/change_secret/database/mongodb/main.yml
+++ b/apps/accounts/automations/change_secret/database/mongodb/main.yml
@@ -31,10 +31,10 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
ssl: "{{ jms_asset.specific.use_ssl }}"
- ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
- ssl_certfile: "{{ jms_asset.specific.client_key }}"
- connection_options:
- - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
+ ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.specific.client_key }}"
+ connection_options:
+ - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
db: "{{ jms_asset.specific.db_name }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
diff --git a/apps/accounts/automations/change_secret/manager.py b/apps/accounts/automations/change_secret/manager.py
index 4360a9311..41b0a4844 100644
--- a/apps/accounts/automations/change_secret/manager.py
+++ b/apps/accounts/automations/change_secret/manager.py
@@ -70,8 +70,14 @@ class ChangeSecretManager(AccountBasePlaybookManager):
else:
return self.secret_generator.get_secret()
- def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
- host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs)
+ def host_callback(
+ self, host, asset=None, account=None,
+ automation=None, path_dir=None, **kwargs
+ ):
+ host = super().host_callback(
+ host, asset=asset, account=account, automation=automation,
+ path_dir=path_dir, **kwargs
+ )
if host.get('error'):
return host
diff --git a/apps/accounts/automations/gather_accounts/database/mongodb/main.yml b/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
index d2478d7b5..c4d6c4ee3 100644
--- a/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
+++ b/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
@@ -12,10 +12,10 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.specific.db_name }}"
ssl: "{{ jms_asset.specific.use_ssl }}"
- ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
- ssl_certfile: "{{ jms_asset.specific.client_key }}"
- connection_options:
- - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
+ ssl_ca_certs: "{{ jms_asset.specific.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.specific.client_key }}"
+ connection_options:
+ - tlsAllowInvalidHostnames: "{{ jms_asset.specific.allow_invalid_cert}}"
filter: users
register: db_info
diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py
index 50a5160e2..f9d06bb73 100644
--- a/apps/assets/automations/base/manager.py
+++ b/apps/assets/automations/base/manager.py
@@ -63,6 +63,33 @@ class BasePlaybookManager:
os.makedirs(path, exist_ok=True, mode=0o755)
return path
+ @staticmethod
+ def write_cert_to_file(filename, content):
+ with open(filename, 'w') as f:
+ f.write(content)
+ return filename
+
+ def convert_cert_to_file(self, host, path_dir):
+ if not path_dir:
+ return host
+
+ specific = host.get('jms_asset', {}).get('specific', {})
+ cert_fields = ('ca_cert', 'client_key', 'client_cert')
+ filtered = list(filter(lambda x: specific.get(x), cert_fields))
+ if not filtered:
+ return host
+
+ cert_dir = os.path.join(path_dir, 'certs')
+ if not os.path.exists(cert_dir):
+ os.makedirs(cert_dir, 0o700, True)
+
+ for f in filtered:
+ result = self.write_cert_to_file(
+ os.path.join(cert_dir, f), specific.get(f)
+ )
+ host['jms_asset']['specific'][f] = result
+ return host
+
def host_callback(self, host, automation=None, **kwargs):
enabled_attr = '{}_enabled'.format(self.__class__.method_type())
method_attr = '{}_method'.format(self.__class__.method_type())
@@ -75,6 +102,8 @@ class BasePlaybookManager:
if not method_enabled:
host['error'] = _('{} disabled'.format(self.__class__.method_type()))
return host
+
+ host = self.convert_cert_to_file(host, kwargs.get('path_dir'))
return host
@staticmethod
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index 084c1e733..e50a0801f 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -98,30 +98,6 @@ class JMSInventory:
if gateway:
host.update(self.make_proxy_command(gateway))
- @staticmethod
- def write_cert_to_file(filename, content):
- with open(filename, 'w') as f:
- f.write(content)
- return filename
-
- def convert_cert_to_file(self, host, path_dir):
- specific = host.get('jms_asset', {}).get('specific', {})
- cert_fields = ('ca_cert', 'client_key', 'client_cert')
- filtered = list(filter(lambda x: specific.get(x), cert_fields))
- if not filtered:
- return host
-
- cert_dir = os.path.join(path_dir, 'certs')
- if not os.path.exists(cert_dir):
- os.makedirs(cert_dir, 0o700, True)
-
- for f in filtered:
- result = self.write_cert_to_file(
- os.path.join(cert_dir, f), specific.get(f)
- )
- host['jms_asset']['specific'][f] = result
- return host
-
def asset_to_host(self, asset, account, automation, protocols, platform):
host = {
'name': '{}'.format(asset.name),
@@ -202,8 +178,6 @@ class JMSInventory:
if not automation.ansible_enabled:
host['error'] = _('Ansible disabled')
- else:
- host = self.convert_cert_to_file(host, path_dir)
if self.host_callback is not None:
host = self.host_callback(
From 03c0d2edbd6c30895543df31d0ae34e43a9836ff Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 18:51:04 +0800
Subject: [PATCH 22/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connectivity?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/accounts/automations/verify_account/manager.py | 4 ++--
apps/assets/automations/ping/manager.py | 6 +++---
apps/assets/automations/ping_gateway/manager.py | 10 +++++-----
apps/assets/const/automation.py | 4 ++--
4 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/apps/accounts/automations/verify_account/manager.py b/apps/accounts/automations/verify_account/manager.py
index 12c247849..fc25794bf 100644
--- a/apps/accounts/automations/verify_account/manager.py
+++ b/apps/accounts/automations/verify_account/manager.py
@@ -1,7 +1,7 @@
from django.db.models import QuerySet
-from common.utils import get_logger
from accounts.const import AutomationTypes, Connectivity
+from common.utils import get_logger
from ..base.manager import PushOrVerifyHostCallbackMixin, AccountBasePlaybookManager
logger = get_logger(__name__)
@@ -29,4 +29,4 @@ class VerifyAccountManager(PushOrVerifyHostCallbackMixin, AccountBasePlaybookMan
def on_host_error(self, host, error, result):
account = self.host_account_mapper.get(host)
- account.set_connectivity(Connectivity.FAILED)
+ account.set_connectivity(Connectivity.ERR)
diff --git a/apps/assets/automations/ping/manager.py b/apps/assets/automations/ping/manager.py
index 305771f0b..0f166d0ad 100644
--- a/apps/assets/automations/ping/manager.py
+++ b/apps/assets/automations/ping/manager.py
@@ -1,5 +1,5 @@
-from common.utils import get_logger
from assets.const import AutomationTypes, Connectivity
+from common.utils import get_logger
from ..base.manager import BasePlaybookManager
logger = get_logger(__name__)
@@ -28,7 +28,7 @@ class PingManager(BasePlaybookManager):
def on_host_error(self, host, error, result):
asset, account = self.host_asset_and_account_mapper.get(host)
- asset.set_connectivity(Connectivity.FAILED)
+ asset.set_connectivity(Connectivity.ERR)
if not account:
return
- account.set_connectivity(Connectivity.FAILED)
+ account.set_connectivity(Connectivity.ERR)
diff --git a/apps/assets/automations/ping_gateway/manager.py b/apps/assets/automations/ping_gateway/manager.py
index 5ffbc2734..858717665 100644
--- a/apps/assets/automations/ping_gateway/manager.py
+++ b/apps/assets/automations/ping_gateway/manager.py
@@ -1,12 +1,12 @@
import socket
-import paramiko
+import paramiko
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
-from common.utils import get_logger
-from assets.models import Gateway
from assets.const import AutomationTypes, Connectivity
+from assets.models import Gateway
+from common.utils import get_logger
logger = get_logger(__name__)
@@ -100,10 +100,10 @@ class PingGatewayManager:
@staticmethod
def on_host_error(gateway, account, error):
logger.info('\033[31m {} -> {} 原因: {} \033[0m\n'.format(gateway, account, error))
- gateway.set_connectivity(Connectivity.FAILED)
+ gateway.set_connectivity(Connectivity.ERR)
if not account:
return
- account.set_connectivity(Connectivity.FAILED)
+ account.set_connectivity(Connectivity.ERR)
@staticmethod
def before_runner_start():
diff --git a/apps/assets/const/automation.py b/apps/assets/const/automation.py
index a811cb50b..831b11b66 100644
--- a/apps/assets/const/automation.py
+++ b/apps/assets/const/automation.py
@@ -3,9 +3,9 @@ from django.utils.translation import ugettext_lazy as _
class Connectivity(TextChoices):
- UNKNOWN = 'unknown', _('Unknown')
+ UNKNOWN = '-', _('Unknown')
OK = 'ok', _('Ok')
- FAILED = 'failed', _('Failed')
+ ERR = 'err', _('Error')
class AutomationTypes(TextChoices):
From 6bbb1f7e86d2d3d7ffb549cb9442a5bbb7c64efb Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 19:02:24 +0800
Subject: [PATCH 23/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connectivity?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/assets/migrations/0075_auto_20210705_1759.py | 6 +++---
apps/assets/migrations/0097_auto_20220426_1558.py | 8 ++++++++
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/apps/assets/migrations/0075_auto_20210705_1759.py b/apps/assets/migrations/0075_auto_20210705_1759.py
index 84e970075..686d61bf0 100644
--- a/apps/assets/migrations/0075_auto_20210705_1759.py
+++ b/apps/assets/migrations/0075_auto_20210705_1759.py
@@ -13,7 +13,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='asset',
name='connectivity',
- field=models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity'),
+ field=models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-', max_length=16, verbose_name='Connectivity'),
),
migrations.AddField(
model_name='asset',
@@ -23,7 +23,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='authbook',
name='connectivity',
- field=models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity'),
+ field=models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-', max_length=16, verbose_name='Connectivity'),
),
migrations.AddField(
model_name='authbook',
@@ -33,7 +33,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='historicalauthbook',
name='connectivity',
- field=models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity'),
+ field=models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-', max_length=16, verbose_name='Connectivity'),
),
migrations.AddField(
model_name='historicalauthbook',
diff --git a/apps/assets/migrations/0097_auto_20220426_1558.py b/apps/assets/migrations/0097_auto_20220426_1558.py
index ec7f64084..2a16841d5 100644
--- a/apps/assets/migrations/0097_auto_20220426_1558.py
+++ b/apps/assets/migrations/0097_auto_20220426_1558.py
@@ -34,6 +34,13 @@ def migrate_macos_platform(apps, schema_editor):
platform_model.objects.using(db_alias).filter(id=old_macos.id).delete()
+def migrate_connectivity(apps, schema_editor):
+ db_alias = schema_editor.connection.alias
+ asset_model = apps.get_model('assets', 'Asset')
+ asset_model.objects.using(db_alias).filter(connectivity='unknown').update(connectivity='-')
+ asset_model.objects.using(db_alias).filter(connectivity='failed').update(connectivity='err')
+
+
class Migration(migrations.Migration):
dependencies = [
('assets', '0096_auto_20220426_1550'),
@@ -43,4 +50,5 @@ class Migration(migrations.Migration):
migrations.RunPython(create_internal_platforms),
migrations.RunPython(update_user_platforms),
migrations.RunPython(migrate_macos_platform),
+ migrations.RunPython(migrate_connectivity),
]
From ae1b134f70516677d1f00c2abd3827f307dbce25 Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 19:30:38 +0800
Subject: [PATCH 24/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20spec=20for?=
=?UTF-8?q?=20cert?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../change_secret/database/mongodb/main.yml | 12 ++++++------
.../gather_accounts/database/mongodb/main.yml | 4 ++--
.../verify_account/database/mongodb/main.yml | 4 ++--
.../gather_facts/database/mongodb/main.yml | 4 ++--
.../automations/ping/database/mongodb/main.yml | 4 ++--
5 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/apps/accounts/automations/change_secret/database/mongodb/main.yml b/apps/accounts/automations/change_secret/database/mongodb/main.yml
index 880234c4f..42ccd78ea 100644
--- a/apps/accounts/automations/change_secret/database/mongodb/main.yml
+++ b/apps/accounts/automations/change_secret/database/mongodb/main.yml
@@ -12,8 +12,8 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
- ssl_ca_certs: "{{ jms_asset.spec_info.ca_cert }}"
- ssl_certfile: "{{ jms_asset.spec_info.client_key }}"
+ ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: db_info
@@ -31,8 +31,8 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
- ssl_ca_certs: "{{ jms_asset.spec_info.ca_cert }}"
- ssl_certfile: "{{ jms_asset.spec_info.client_key }}"
+ ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
db: "{{ jms_asset.spec_info.db_name }}"
@@ -49,8 +49,8 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
- ssl_ca_certs: "{{ jms_asset.spec_info.ca_cert }}"
- ssl_certfile: "{{ jms_asset.spec_info.client_key }}"
+ ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
when:
diff --git a/apps/accounts/automations/gather_accounts/database/mongodb/main.yml b/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
index 1ec0a3af0..452241f6a 100644
--- a/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
+++ b/apps/accounts/automations/gather_accounts/database/mongodb/main.yml
@@ -12,8 +12,8 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
- ssl_ca_certs: "{{ jms_asset.spec_info.ca_cert }}"
- ssl_certfile: "{{ jms_asset.spec_info.client_key }}"
+ ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
filter: users
diff --git a/apps/accounts/automations/verify_account/database/mongodb/main.yml b/apps/accounts/automations/verify_account/database/mongodb/main.yml
index ec974f160..261fe63ca 100644
--- a/apps/accounts/automations/verify_account/database/mongodb/main.yml
+++ b/apps/accounts/automations/verify_account/database/mongodb/main.yml
@@ -12,7 +12,7 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
- ssl_ca_certs: "{{ jms_asset.spec_info.ca_cert }}"
- ssl_certfile: "{{ jms_asset.spec_info.client_key }}"
+ ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
diff --git a/apps/assets/automations/gather_facts/database/mongodb/main.yml b/apps/assets/automations/gather_facts/database/mongodb/main.yml
index 1b5602308..084a27348 100644
--- a/apps/assets/automations/gather_facts/database/mongodb/main.yml
+++ b/apps/assets/automations/gather_facts/database/mongodb/main.yml
@@ -12,8 +12,8 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
- ssl_ca_certs: "{{ jms_asset.spec_info.ca_cert }}"
- ssl_certfile: "{{ jms_asset.spec_info.client_key }}"
+ ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: db_info
diff --git a/apps/assets/automations/ping/database/mongodb/main.yml b/apps/assets/automations/ping/database/mongodb/main.yml
index adf42b2fa..23a06c08d 100644
--- a/apps/assets/automations/ping/database/mongodb/main.yml
+++ b/apps/assets/automations/ping/database/mongodb/main.yml
@@ -12,7 +12,7 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
- ssl_ca_certs: "{{ jms_asset.spec_info.ca_cert }}"
- ssl_certfile: "{{ jms_asset.spec_info.client_key }}"
+ ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
+ ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
From 34b740583e90a51d1b1d728eaf9d1057bf939244 Mon Sep 17 00:00:00 2001
From: ibuler
Date: Tue, 31 Jan 2023 19:37:16 +0800
Subject: [PATCH 25/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20asset=20info?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/accounts/migrations/0001_initial.py | 3 +--
apps/assets/migrations/0106_auto_20221228_1838.py | 6 +-----
2 files changed, 2 insertions(+), 7 deletions(-)
diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py
index 201a5141d..6e211a7fc 100644
--- a/apps/accounts/migrations/0001_initial.py
+++ b/apps/accounts/migrations/0001_initial.py
@@ -29,8 +29,7 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
- ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')],
- default='unknown', max_length=16, verbose_name='Connectivity')),
+ ('connectivity', models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-', max_length=16, verbose_name='Connectivity')),
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
diff --git a/apps/assets/migrations/0106_auto_20221228_1838.py b/apps/assets/migrations/0106_auto_20221228_1838.py
index 9637bbd1d..ce7718ccd 100644
--- a/apps/assets/migrations/0106_auto_20221228_1838.py
+++ b/apps/assets/migrations/0106_auto_20221228_1838.py
@@ -52,11 +52,7 @@ class Migration(migrations.Migration):
migrations.AlterModelOptions(
name='asset',
options={'ordering': ['name'],
- 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'),
- ('test_assetconnectivity', 'Can test asset connectivity'),
- ('push_assetaccount', 'Can push account to asset'),
- ('match_asset', 'Can match asset'), ('add_assettonode', 'Add asset to node'),
- ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'},
+ 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'), ('test_assetconnectivity', 'Can test asset connectivity'), ('push_assetaccount', 'Can push account to asset'), ('test_account', 'Can verify account'), ('match_asset', 'Can match asset'), ('add_assettonode', 'Add asset to node'), ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'},
),
migrations.AlterUniqueTogether(
name='accountbackupplan',
From 142edd9438bb21f13db05c0636dadc4d29cea355 Mon Sep 17 00:00:00 2001
From: Eric
Date: Wed, 1 Feb 2023 10:27:12 +0800
Subject: [PATCH 26/92] perf: update applet download and deployment
---
apps/templates/resource_download.html | 22 +++++++------------
apps/terminal/applets/chrome/common.py | 2 +-
apps/terminal/applets/navicat/common.py | 10 ++++-----
.../deploy_applet_host/playbook.yml | 14 +++++++++++-
4 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html
index df0577307..14a7b30c2 100644
--- a/apps/templates/resource_download.html
+++ b/apps/templates/resource_download.html
@@ -20,10 +20,10 @@ p {
{% trans 'JumpServer Client, currently used to launch the client, now only support launch RDP SSH client, The Telnet client will next' %}
@@ -33,7 +33,7 @@ p {
{% trans 'macOS needs to download the client to connect RDP asset, which comes with Windows' %}
@@ -42,22 +42,16 @@ p {
{% trans 'Windows Remote application publisher tools' %}
{% trans 'OpenSSH is a program used to connect remote applications in the Windows Remote Application Publisher' %}
- {% if XPACK_ENABLED %}
- {% trans 'Jmservisor is the program used to pull up remote applications in Windows Remote Application publisher' %}
-
- {% endif %}
JumpServer {% trans 'Offline video player' %} v0.1.5
diff --git a/apps/terminal/applets/chrome/common.py b/apps/terminal/applets/chrome/common.py
index 4bffa5081..dbac3a22b 100644
--- a/apps/terminal/applets/chrome/common.py
+++ b/apps/terminal/applets/chrome/common.py
@@ -129,7 +129,7 @@ class Asset(DictObj):
address: str
protocols: list[Protocol]
category: Category
- specific: Specific
+ spec_info: Specific
def get_protocol_port(self, protocol):
for item in self.protocols:
diff --git a/apps/terminal/applets/navicat/common.py b/apps/terminal/applets/navicat/common.py
index f1e6429de..1802c8d86 100644
--- a/apps/terminal/applets/navicat/common.py
+++ b/apps/terminal/applets/navicat/common.py
@@ -1,11 +1,11 @@
import abc
-import subprocess
+import base64
+import json
import locale
+import os
+import subprocess
import sys
import time
-import os
-import json
-import base64
from subprocess import CREATE_NO_WINDOW
_blockInput = None
@@ -125,7 +125,7 @@ class Asset(DictObj):
address: str
protocols: list[Protocol]
category: Category
- specific: Specific
+ spec_info: Specific
def get_protocol_port(self, protocol):
for item in self.protocols:
diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml
index 3d86ea52a..763df39a8 100644
--- a/apps/terminal/automations/deploy_applet_host/playbook.yml
+++ b/apps/terminal/automations/deploy_applet_host/playbook.yml
@@ -13,7 +13,7 @@
RDS_fSingleSessionPerUser: 1
RDS_MaxDisconnectionTime: 60000
RDS_RemoteAppLogoffTimeLimit: 0
- TinkerInstaller: Tinker_Installer_v0.0.1.exe
+ TinkerInstaller: Tinker_Installer.exe
tasks:
- name: Install RDS-Licensing (RDS)
@@ -151,6 +151,18 @@
state: present
arguments:
- /quiet
+ -
+ - name: Download navicat161_premium_en package (navicat)
+ ansible.windows.win_get_url:
+ url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/navicat161_premium_en_x64.exe"
+ dest: "{{ ansible_env.TEMP }}\\navicat161_premium_en_x64.exe"
+
+ - name: Install navicat (navicat)
+ ansible.windows.win_package:
+ path: "{{ ansible_env.TEMP }}\\navicat161_premium_en_x64.exe"
+ state: present
+ arguments:
+ - /SILENT
- name: Generate tinkerd component config
ansible.windows.win_shell:
From 4956e1147ec38a4cad6c7a6cfc94f4658c7732c0 Mon Sep 17 00:00:00 2001
From: ibuler
Date: Wed, 1 Feb 2023 11:26:57 +0800
Subject: [PATCH 27/92] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=94=A8?=
=?UTF-8?q?=E6=88=B7=E5=88=9B=E5=BB=BA=20=E8=A7=92=E8=89=B2=E8=AE=BE?=
=?UTF-8?q?=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/users/serializers/user.py | 24 +++++++++++++-----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py
index 7baa4117b..7e2c6ea33 100644
--- a/apps/users/serializers/user.py
+++ b/apps/users/serializers/user.py
@@ -5,8 +5,8 @@ from functools import partial
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
-from common.serializers.fields import EncryptedField, ObjectRelatedField, LabeledChoiceField
from common.serializers import CommonBulkSerializerMixin
+from common.serializers.fields import EncryptedField, ObjectRelatedField, LabeledChoiceField
from common.utils import pretty_string, get_logger
from common.validators import PhoneValidator
from rbac.builtin import BuiltinRole
@@ -25,24 +25,25 @@ __all__ = [
logger = get_logger(__file__)
+def default_system_roles():
+ return [BuiltinRole.system_user.get_role()]
+
+
+def default_org_roles():
+ return [BuiltinRole.org_user.get_role()]
+
+
class RolesSerializerMixin(serializers.Serializer):
system_roles = ObjectRelatedField(
queryset=Role.system_roles, attrs=('id', 'display_name'),
- label=_("System roles"), many=True
+ label=_("System roles"), many=True, default=default_system_roles
)
org_roles = ObjectRelatedField(
queryset=Role.org_roles, attrs=('id', 'display_name'),
- label=_("Org roles"), many=True
+ label=_("Org roles"), many=True, required=False,
+ default=default_org_roles
)
- @staticmethod
- def get_system_roles_display(user):
- return user.system_roles.display
-
- @staticmethod
- def get_org_roles_display(user):
- return user.org_roles.display
-
def pop_roles_if_need(self, fields):
request = self.context.get("request")
view = self.context.get("view")
@@ -55,6 +56,7 @@ class RolesSerializerMixin(serializers.Serializer):
action = view.action or "list"
if action in ("partial_bulk_update", "bulk_update", "partial_update", "update"):
action = "create"
+
model_cls_field_mapper = {
SystemRoleBinding: ["system_roles"],
OrgRoleBinding: ["org_roles"],
From 023ca297527aa676eb4624ac268c75395ab1b2ba Mon Sep 17 00:00:00 2001
From: ibuler
Date: Wed, 1 Feb 2023 12:39:38 +0800
Subject: [PATCH 28/92] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=94=A8?=
=?UTF-8?q?=E6=88=B7=E5=88=9B=E5=BB=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/audits/utils.py | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/apps/audits/utils.py b/apps/audits/utils.py
index 6f8f9e730..3ff42177c 100644
--- a/apps/audits/utils.py
+++ b/apps/audits/utils.py
@@ -1,17 +1,14 @@
-import csv
import codecs
-
+import csv
from itertools import chain
-from django.http import HttpResponse
from django.db import models
+from django.http import HttpResponse
-from settings.serializers import SettingsSerializer
from common.utils import validate_ip, get_ip_city, get_logger
-from common.db import fields
+from settings.serializers import SettingsSerializer
from .const import DEFAULT_CITY
-
logger = get_logger(__name__)
@@ -95,7 +92,7 @@ def _get_instance_field_value(
def model_to_dict_for_operate_log(
- instance, include_model_fields=True, include_related_fields=True
+ instance, include_model_fields=True, include_related_fields=False
):
model_need_continue_fields = ['date_updated']
m2m_need_continue_fields = ['history_passwords']
@@ -106,7 +103,7 @@ def model_to_dict_for_operate_log(
if include_related_fields:
opts = instance._meta
- for f in chain(opts.many_to_many, opts.related_objects):
+ for f in opts.many_to_many:
value = []
if instance.pk is not None:
related_name = getattr(f, 'attname', '') or getattr(f, 'related_name', '')
From 66c58a2084b4b97676737777e4b8218135a6f10b Mon Sep 17 00:00:00 2001
From: fit2bot <68588906+fit2bot@users.noreply.github.com>
Date: Wed, 1 Feb 2023 14:43:58 +0800
Subject: [PATCH 29/92] =?UTF-8?q?perf:=20=E8=B4=A6=E5=8F=B7=20=E6=A8=A1?=
=?UTF-8?q?=E7=89=88=E5=88=9B=E5=BB=BA=20(#9386)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: feng <1304903146@qq.com>
---
apps/accounts/serializers/account/account.py | 73 +++++++++-----------
apps/assets/serializers/asset/common.py | 32 ++-------
2 files changed, 36 insertions(+), 69 deletions(-)
diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py
index 702762be9..a834756f0 100644
--- a/apps/accounts/serializers/account/account.py
+++ b/apps/accounts/serializers/account/account.py
@@ -11,53 +11,32 @@ from .base import BaseAccountSerializer
class AccountSerializerCreateValidateMixin:
- replace_attrs: callable
+ id: str
+ template: bool
push_now: bool
+ replace_attrs: callable
- def validate(self, attrs):
- _id = attrs.pop('id', None)
- if _id:
+ def to_internal_value(self, data):
+ self.id = data.pop('id', None)
+ self.push_now = data.pop('push_now', False)
+ self.template = data.pop('template', False)
+ return super().to_internal_value(data)
+
+ def set_secret(self, attrs):
+ _id = self.id
+ template = self.template
+
+ if _id and template:
account_template = AccountTemplate.objects.get(id=_id)
attrs['secret'] = account_template.secret
- account_template = attrs.pop('template', None)
- if account_template:
- self.replace_attrs(account_template, attrs)
- self.push_now = attrs.pop('push_now', False)
+ elif _id and not template:
+ account = Account.objects.get(id=_id)
+ attrs['secret'] = account.secret
+
+ def validate(self, attrs):
+ self.set_secret(attrs)
return super().validate(attrs)
-
-class AccountSerializerCreateMixin(
- AccountSerializerCreateValidateMixin, BulkModelSerializer
-):
- template = serializers.UUIDField(
- required=False, allow_null=True, write_only=True,
- label=_('Account template')
- )
- push_now = serializers.BooleanField(
- default=False, label=_("Push now"), write_only=True
- )
- has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
-
- @staticmethod
- def validate_template(value):
- try:
- return AccountTemplate.objects.get(id=value)
- except AccountTemplate.DoesNotExist:
- raise serializers.ValidationError(_('Account template not found'))
-
- @staticmethod
- def replace_attrs(account_template: AccountTemplate, attrs: dict):
- exclude_fields = [
- '_state', 'org_id', 'id', 'date_created',
- 'date_updated'
- ]
- template_attrs = {
- k: v for k, v in account_template.__dict__.items()
- if k not in exclude_fields
- }
- for k, v in template_attrs.items():
- attrs.setdefault(k, v)
-
def create(self, validated_data):
instance = super().create(validated_data)
if self.push_now:
@@ -65,6 +44,18 @@ class AccountSerializerCreateMixin(
return instance
+class AccountSerializerCreateMixin(
+ AccountSerializerCreateValidateMixin, BulkModelSerializer
+):
+ template = serializers.BooleanField(
+ default=False, label=_("Template"), write_only=True
+ )
+ push_now = serializers.BooleanField(
+ default=False, label=_("Push now"), write_only=True
+ )
+ has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
+
+
class AccountAssetSerializer(serializers.ModelSerializer):
platform = ObjectRelatedField(read_only=True)
diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py
index 47c866603..7f39e2378 100644
--- a/apps/assets/serializers/asset/common.py
+++ b/apps/assets/serializers/asset/common.py
@@ -54,6 +54,9 @@ class AssetAccountSerializer(
push_now = serializers.BooleanField(
default=False, label=_("Push now"), write_only=True
)
+ template = serializers.BooleanField(
+ default=False, label=_("Template"), write_only=True
+ )
class Meta:
model = Account
@@ -62,7 +65,7 @@ class AssetAccountSerializer(
'version', 'secret_type',
]
fields_write_only = [
- 'secret', 'push_now'
+ 'secret', 'push_now', 'template'
]
fields = fields_mini + fields_write_only
extra_kwargs = {
@@ -74,33 +77,6 @@ class AssetAccountSerializer(
value = self.initial_data.get('username')
return value
- @staticmethod
- def validate_template(value):
- try:
- return AccountTemplate.objects.get(id=value)
- except AccountTemplate.DoesNotExist:
- raise serializers.ValidationError(_('Account template not found'))
-
- @staticmethod
- def replace_attrs(account_template: AccountTemplate, attrs: dict):
- exclude_fields = [
- '_state', 'org_id', 'id', 'date_created',
- 'date_updated'
- ]
- template_attrs = {
- k: v for k, v in account_template.__dict__.items()
- if k not in exclude_fields
- }
- for k, v in template_attrs.items():
- attrs.setdefault(k, v)
-
- def create(self, validated_data):
- from accounts.tasks import push_accounts_to_assets
- instance = super().create(validated_data)
- if self.push_now:
- push_accounts_to_assets.delay([instance.id], [instance.asset_id])
- return instance
-
class AccountSecretSerializer(SecretReadableMixin, CommonModelSerializer):
class Meta:
From acab0b765cbfbce8f14b2ec4e03967dc0a83da1f Mon Sep 17 00:00:00 2001
From: Bai
Date: Wed, 1 Feb 2023 14:58:49 +0800
Subject: [PATCH 30/92] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=9B?=
=?UTF-8?q?=E5=BB=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/audits/signal_handlers.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py
index 8cc396496..d5da3d1f7 100644
--- a/apps/audits/signal_handlers.py
+++ b/apps/audits/signal_handlers.py
@@ -121,8 +121,7 @@ def signal_of_operate_log_whether_continue(sender, instance, created, update_fie
if instance._meta.object_name == 'Terminal' and created:
condition = False
# last_login 改变是最后登录日期, 每次登录都会改变
- if instance._meta.object_name == 'User' and \
- update_fields and 'last_login' in update_fields:
+ if instance._meta.object_name == 'User' and update_fields and 'last_login' in update_fields:
condition = False
# 不在记录白名单中,跳过
if sender._meta.object_name not in MODELS_NEED_RECORD:
@@ -137,8 +136,10 @@ def on_object_pre_create_or_update(sender, instance=None, raw=False, using=None,
)
if not ok:
return
- instance_before_data = {'id': instance.id}
- raw_instance = type(instance).objects.filter(pk=instance.id).first()
+ # users.PrivateToken Model 没有 id 有 pk字段
+ instance_id = getattr(instance, 'id', getattr(instance, 'pk', None))
+ instance_before_data = {'id': instance_id}
+ raw_instance = type(instance).objects.filter(pk=instance_id).first()
if raw_instance:
instance_before_data = model_to_dict(raw_instance)
operate_log_id = str(uuid.uuid4())
From e324c46f87e98ade7fe09c0c77d8dff52ec5770d Mon Sep 17 00:00:00 2001
From: jiangweidong <80373698+Hi-JWD@users.noreply.github.com>
Date: Wed, 1 Feb 2023 15:06:10 +0800
Subject: [PATCH 31/92] =?UTF-8?q?fix:=20PrivateToken=E5=88=9B=E5=BB=BA?=
=?UTF-8?q?=E6=8A=A5=E9=94=99=20(#9387)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Jiangjie.Bai
---
apps/audits/handler.py | 2 +-
.../0021_alter_operatelog_resource_id.py | 18 ++++++++++++++++++
apps/audits/models.py | 2 +-
apps/audits/signal_handlers.py | 2 ++
4 files changed, 22 insertions(+), 2 deletions(-)
create mode 100644 apps/audits/migrations/0021_alter_operatelog_resource_id.py
diff --git a/apps/audits/handler.py b/apps/audits/handler.py
index 9d521824b..41fd59c6c 100644
--- a/apps/audits/handler.py
+++ b/apps/audits/handler.py
@@ -194,7 +194,7 @@ class OperatorLogHandler(metaclass=Singleton):
remote_addr = get_request_ip(current_request)
if resource_display is None:
resource_display = self.get_resource_display(resource)
- resource_id = resource.id if resource is not None else ''
+ resource_id = getattr(resource, 'pk', '')
before, after = self.data_processing(before, after)
if not force and not any([before, after]):
# 前后都没变化,没必要生成日志,除非手动强制保存
diff --git a/apps/audits/migrations/0021_alter_operatelog_resource_id.py b/apps/audits/migrations/0021_alter_operatelog_resource_id.py
new file mode 100644
index 000000000..9dadae738
--- /dev/null
+++ b/apps/audits/migrations/0021_alter_operatelog_resource_id.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.16 on 2023-02-01 06:52
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('audits', '0020_auto_20230117_1004'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='operatelog',
+ name='resource_id',
+ field=models.CharField(blank=True, db_index=True, default='', max_length=128, verbose_name='Resource'),
+ ),
+ ]
diff --git a/apps/audits/models.py b/apps/audits/models.py
index 86c7597a7..93fb6733b 100644
--- a/apps/audits/models.py
+++ b/apps/audits/models.py
@@ -53,7 +53,7 @@ class OperateLog(OrgModelMixin):
resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type"))
resource = models.CharField(max_length=128, verbose_name=_("Resource"))
resource_id = models.CharField(
- max_length=36, blank=True, default='', db_index=True,
+ max_length=128, blank=True, default='', db_index=True,
verbose_name=_("Resource")
)
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py
index d5da3d1f7..2f209e09b 100644
--- a/apps/audits/signal_handlers.py
+++ b/apps/audits/signal_handlers.py
@@ -136,10 +136,12 @@ def on_object_pre_create_or_update(sender, instance=None, raw=False, using=None,
)
if not ok:
return
+
# users.PrivateToken Model 没有 id 有 pk字段
instance_id = getattr(instance, 'id', getattr(instance, 'pk', None))
instance_before_data = {'id': instance_id}
raw_instance = type(instance).objects.filter(pk=instance_id).first()
+
if raw_instance:
instance_before_data = model_to_dict(raw_instance)
operate_log_id = str(uuid.uuid4())
From 4762939daeb8f3c8c5088c5b66eb85cb18bf5076 Mon Sep 17 00:00:00 2001
From: jiangweidong
Date: Wed, 1 Feb 2023 15:13:07 +0800
Subject: [PATCH 32/92] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96audits=E7=9A=84?=
=?UTF-8?q?migrations?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../migrations/0020_auto_20230117_1004.py | 5 +++++
.../0021_alter_operatelog_resource_id.py | 18 ------------------
2 files changed, 5 insertions(+), 18 deletions(-)
delete mode 100644 apps/audits/migrations/0021_alter_operatelog_resource_id.py
diff --git a/apps/audits/migrations/0020_auto_20230117_1004.py b/apps/audits/migrations/0020_auto_20230117_1004.py
index d3c2c4c70..4261bd635 100644
--- a/apps/audits/migrations/0020_auto_20230117_1004.py
+++ b/apps/audits/migrations/0020_auto_20230117_1004.py
@@ -47,4 +47,9 @@ class Migration(migrations.Migration):
migrations.RunPython(migrate_operate_log_after_before),
migrations.RemoveField(model_name='operatelog', name='after', ),
migrations.RemoveField(model_name='operatelog', name='before', ),
+ migrations.AlterField(
+ model_name='operatelog',
+ name='resource_id',
+ field=models.CharField(blank=True, db_index=True, default='', max_length=128, verbose_name='Resource'),
+ ),
]
diff --git a/apps/audits/migrations/0021_alter_operatelog_resource_id.py b/apps/audits/migrations/0021_alter_operatelog_resource_id.py
deleted file mode 100644
index 9dadae738..000000000
--- a/apps/audits/migrations/0021_alter_operatelog_resource_id.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 3.2.16 on 2023-02-01 06:52
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('audits', '0020_auto_20230117_1004'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='operatelog',
- name='resource_id',
- field=models.CharField(blank=True, db_index=True, default='', max_length=128, verbose_name='Resource'),
- ),
- ]
From 4e2c2b652f8c3d5c9cbc2adbd15e8282c6ed45e7 Mon Sep 17 00:00:00 2001
From: Bai
Date: Wed, 1 Feb 2023 15:48:30 +0800
Subject: [PATCH 33/92] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=9B?=
=?UTF-8?q?=E5=BB=BA=E6=95=B0=E6=8D=AE=E5=BA=93=E6=97=B6=E5=8C=85=E5=90=AB?=
=?UTF-8?q?=E8=B4=A6=E5=8F=B7=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/accounts/serializers/account/account.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py
index a834756f0..cc13aced8 100644
--- a/apps/accounts/serializers/account/account.py
+++ b/apps/accounts/serializers/account/account.py
@@ -17,10 +17,11 @@ class AccountSerializerCreateValidateMixin:
replace_attrs: callable
def to_internal_value(self, data):
- self.id = data.pop('id', None)
- self.push_now = data.pop('push_now', False)
- self.template = data.pop('template', False)
- return super().to_internal_value(data)
+ ret = super().to_internal_value(data)
+ self.id = ret.pop('id', None)
+ self.push_now = ret.pop('push_now', False)
+ self.template = ret.pop('template', False)
+ return ret
def set_secret(self, attrs):
_id = self.id
From 1169677286439216a030fd8ab17f49b5f7f4d6ba Mon Sep 17 00:00:00 2001
From: ibuler
Date: Wed, 1 Feb 2023 16:43:43 +0800
Subject: [PATCH 34/92] =?UTF-8?q?perf:=20=E9=87=8D=E6=9E=84=20notification?=
=?UTF-8?q?s=20site=20msg?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/audits/signal_handlers.py | 2 +-
apps/notifications/api/site_msgs.py | 29 ++++----
apps/notifications/filters.py | 4 +-
.../migrations/0004_auto_20230201_1614.py | 72 ++++++++++++++++++
apps/notifications/models/site_msg.py | 16 ++--
apps/notifications/serializers/site_msgs.py | 23 ++++--
apps/notifications/signal_handlers.py | 4 +-
apps/notifications/site_msg.py | 73 ++++++-------------
apps/notifications/urls/api_urls.py | 10 +--
apps/terminal/notifications.py | 20 ++---
10 files changed, 156 insertions(+), 97 deletions(-)
create mode 100644 apps/notifications/migrations/0004_auto_20230201_1614.py
diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py
index 8cc396496..4f8be3670 100644
--- a/apps/audits/signal_handlers.py
+++ b/apps/audits/signal_handlers.py
@@ -291,7 +291,7 @@ def on_django_start_set_operate_log_monitor_models(sender, **kwargs):
}
exclude_models = {
'UserPasswordHistory', 'ContentType',
- 'SiteMessage', 'SiteMessageUsers',
+ 'MessageContent', 'SiteMessage',
'PlatformAutomation', 'PlatformProtocol', 'Protocol',
'HistoricalAccount', 'GatheredUser', 'ApprovalRule',
'BaseAutomation', 'CeleryTask', 'Command', 'JobAuditLog',
diff --git a/apps/notifications/api/site_msgs.py b/apps/notifications/api/site_msgs.py
index 5c9257c25..399f332dc 100644
--- a/apps/notifications/api/site_msgs.py
+++ b/apps/notifications/api/site_msgs.py
@@ -1,17 +1,16 @@
-from rest_framework.response import Response
-from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.decorators import action
+from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
+from rest_framework.response import Response
-from common.utils.http import is_true
-from common.permissions import IsValidUser
-from common.const.http import GET, PATCH, POST
from common.api import JMSGenericViewSet
+from common.const.http import GET, PATCH, POST
+from common.permissions import IsValidUser
+from common.utils.http import is_true
from ..serializers import (
- SiteMessageDetailSerializer, SiteMessageIdsSerializer,
+ SiteMessageSerializer, SiteMessageIdsSerializer,
SiteMessageSendSerializer,
)
from ..site_msg import SiteMessageUtil
-from ..filters import SiteMsgFilter
__all__ = ('SiteMessageViewSet',)
@@ -19,11 +18,11 @@ __all__ = ('SiteMessageViewSet',)
class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JMSGenericViewSet):
permission_classes = (IsValidUser,)
serializer_classes = {
- 'default': SiteMessageDetailSerializer,
+ 'default': SiteMessageSerializer,
'mark_as_read': SiteMessageIdsSerializer,
'send': SiteMessageSendSerializer,
}
- filterset_class = SiteMsgFilter
+ filterset_fields = ('has_read',)
def get_queryset(self):
user = self.request.user
@@ -44,9 +43,9 @@ class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JMSGenericViewSet):
@action(methods=[PATCH], detail=False, url_path='mark-as-read')
def mark_as_read(self, request, **kwargs):
user = request.user
- seri = self.get_serializer(data=request.data)
- seri.is_valid(raise_exception=True)
- ids = seri.validated_data['ids']
+ s = self.get_serializer(data=request.data)
+ s.is_valid(raise_exception=True)
+ ids = s.validated_data['ids']
SiteMessageUtil.mark_msgs_as_read(user.id, ids)
return Response({'detail': 'ok'})
@@ -58,7 +57,7 @@ class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JMSGenericViewSet):
@action(methods=[POST], detail=False)
def send(self, request, **kwargs):
- seri = self.get_serializer(data=request.data)
- seri.is_valid(raise_exception=True)
- SiteMessageUtil.send_msg(**seri.validated_data, sender=request.user)
+ s = self.get_serializer(data=request.data)
+ s.is_valid(raise_exception=True)
+ SiteMessageUtil.send_msg(**s.validated_data, sender=request.user)
return Response({'detail': 'ok'})
diff --git a/apps/notifications/filters.py b/apps/notifications/filters.py
index c28b8a3f2..ee11eca2b 100644
--- a/apps/notifications/filters.py
+++ b/apps/notifications/filters.py
@@ -1,7 +1,7 @@
import django_filters
from common.drf.filters import BaseFilterSet
-from .models import SiteMessage
+from .models import MessageContent
class SiteMsgFilter(BaseFilterSet):
@@ -14,5 +14,5 @@ class SiteMsgFilter(BaseFilterSet):
has_read = django_filters.BooleanFilter(method='do_nothing')
class Meta:
- model = SiteMessage
+ model = MessageContent
fields = ('has_read',)
diff --git a/apps/notifications/migrations/0004_auto_20230201_1614.py b/apps/notifications/migrations/0004_auto_20230201_1614.py
new file mode 100644
index 000000000..a59aeb8c2
--- /dev/null
+++ b/apps/notifications/migrations/0004_auto_20230201_1614.py
@@ -0,0 +1,72 @@
+# Generated by Django 3.2.14 on 2023-02-01 08:14
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('users', '0041_auto_20221220_1956'),
+ ('notifications', '0003_auto_20221220_1956'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='sitemessageusers',
+ name='sitemessage',
+ ),
+ migrations.RemoveField(
+ model_name='sitemessageusers',
+ name='user',
+ ),
+ migrations.DeleteModel(
+ name='SiteMessage',
+ ),
+ migrations.DeleteModel(
+ name='SiteMessageUsers',
+ ),
+ migrations.CreateModel(
+ name='MessageContent',
+ fields=[
+ ('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
+ ('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
+ ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
+ ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
+ ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
+ ('subject', models.CharField(max_length=1024)),
+ ('message', models.TextField()),
+ ('is_broadcast', models.BooleanField(default=False)),
+ ('groups', models.ManyToManyField(to='users.UserGroup')),
+ ('sender', models.ForeignKey(db_constraint=False, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='send_site_message', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='SiteMessage',
+ fields=[
+ ('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
+ ('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
+ ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
+ ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
+ ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
+ ('has_read', models.BooleanField(default=False)),
+ ('read_at', models.DateTimeField(default=None, null=True)),
+ ('content', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='notifications.messagecontent')),
+ ('user', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.AddField(
+ model_name='messagecontent',
+ name='users',
+ field=models.ManyToManyField(related_name='recv_site_messages', through='notifications.SiteMessage', to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/apps/notifications/models/site_msg.py b/apps/notifications/models/site_msg.py
index 170f21a3f..826aa7016 100644
--- a/apps/notifications/models/site_msg.py
+++ b/apps/notifications/models/site_msg.py
@@ -2,24 +2,24 @@ from django.db import models
from common.db.models import JMSBaseModel
-__all__ = ('SiteMessageUsers', 'SiteMessage')
+__all__ = ('SiteMessage', 'MessageContent')
-class SiteMessageUsers(JMSBaseModel):
- sitemessage = models.ForeignKey('notifications.SiteMessage', on_delete=models.CASCADE, db_constraint=False,
- related_name='m2m_sitemessageusers')
- user = models.ForeignKey('users.User', on_delete=models.CASCADE, db_constraint=False,
- related_name='m2m_sitemessageusers')
+class SiteMessage(JMSBaseModel):
+ content = models.ForeignKey('notifications.MessageContent', on_delete=models.CASCADE,
+ db_constraint=False, related_name='messages')
+ user = models.ForeignKey('users.User', on_delete=models.CASCADE, db_constraint=False)
has_read = models.BooleanField(default=False)
read_at = models.DateTimeField(default=None, null=True)
comment = ''
-class SiteMessage(JMSBaseModel):
+class MessageContent(JMSBaseModel):
subject = models.CharField(max_length=1024)
message = models.TextField()
users = models.ManyToManyField(
- 'users.User', through=SiteMessageUsers, related_name='recv_site_messages'
+ 'users.User', through=SiteMessage,
+ related_name='recv_site_messages'
)
groups = models.ManyToManyField('users.UserGroup')
is_broadcast = models.BooleanField(default=False)
diff --git a/apps/notifications/serializers/site_msgs.py b/apps/notifications/serializers/site_msgs.py
index 1f157add5..351a04eac 100644
--- a/apps/notifications/serializers/site_msgs.py
+++ b/apps/notifications/serializers/site_msgs.py
@@ -1,7 +1,7 @@
-from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
+from rest_framework.serializers import ModelSerializer
-from ..models import SiteMessage
+from ..models import MessageContent
class SenderMixin(ModelSerializer):
@@ -15,12 +15,23 @@ class SenderMixin(ModelSerializer):
return ''
-class SiteMessageDetailSerializer(SenderMixin, ModelSerializer):
+class MessageContentSerializer(SenderMixin, ModelSerializer):
class Meta:
- model = SiteMessage
+ model = MessageContent
fields = [
- 'id', 'subject', 'message', 'has_read', 'read_at',
- 'date_created', 'date_updated', 'sender',
+ 'id', 'subject', 'message',
+ 'date_created', 'date_updated',
+ 'sender',
+ ]
+
+
+class SiteMessageSerializer(SenderMixin, ModelSerializer):
+ content = MessageContentSerializer(read_only=True)
+
+ class Meta:
+ model = MessageContent
+ fields = [
+ 'id', 'has_read', 'read_at', 'content', 'date_created'
]
diff --git a/apps/notifications/signal_handlers.py b/apps/notifications/signal_handlers.py
index c0b1f1c1c..74b700643 100644
--- a/apps/notifications/signal_handlers.py
+++ b/apps/notifications/signal_handlers.py
@@ -12,7 +12,7 @@ from common.utils import get_logger
from common.utils.connection import RedisPubSub
from notifications.backends import BACKEND
from users.models import User
-from .models import SiteMessage, SystemMsgSubscription, UserMsgSubscription
+from .models import MessageContent, SystemMsgSubscription, UserMsgSubscription
from .notifications import SystemMessage
logger = get_logger(__name__)
@@ -26,7 +26,7 @@ class NewSiteMsgSubPub(LazyObject):
new_site_msg_chan = NewSiteMsgSubPub()
-@receiver(post_save, sender=SiteMessage)
+@receiver(post_save, sender=MessageContent)
@on_transaction_commit
def on_site_message_create(sender, instance, created, **kwargs):
if not created:
diff --git a/apps/notifications/site_msg.py b/apps/notifications/site_msg.py
index faa36b5f4..d4e604a51 100644
--- a/apps/notifications/site_msg.py
+++ b/apps/notifications/site_msg.py
@@ -1,10 +1,9 @@
-from django.db.models import F, Q
from django.db import transaction
-from common.utils.timezone import local_now
from common.utils import get_logger
+from common.utils.timezone import local_now
from users.models import User
-from .models import SiteMessage as SiteMessageModel, SiteMessageUsers
+from .models import MessageContent as SiteMessageModel, SiteMessage
logger = get_logger(__file__)
@@ -17,11 +16,6 @@ class SiteMessageUtil:
if not any((user_ids, group_ids, is_broadcast)):
raise ValueError('No recipient is specified')
- logger.info(f'Site message send: '
- f'user_ids={user_ids} '
- f'group_ids={group_ids} '
- f'subject={subject} '
- f'message={message}')
with transaction.atomic():
site_msg = SiteMessageModel.objects.create(
subject=subject, message=message,
@@ -30,65 +24,46 @@ class SiteMessageUtil:
if is_broadcast:
user_ids = User.objects.all().values_list('id', flat=True)
- else:
- if group_ids:
- site_msg.groups.add(*group_ids)
+ elif group_ids:
+ site_msg.groups.add(*group_ids)
- user_ids_from_group = User.groups.through.objects.filter(
- usergroup_id__in=group_ids
- ).values_list('user_id', flat=True)
- user_ids = [*user_ids, *user_ids_from_group]
+ user_ids_from_group = User.groups.through.objects.filter(
+ usergroup_id__in=group_ids
+ ).values_list('user_id', flat=True)
+ user_ids = [*user_ids, *user_ids_from_group]
site_msg.users.add(*user_ids)
@classmethod
def get_user_all_msgs(cls, user_id):
- site_msgs = SiteMessageModel.objects.filter(
- m2m_sitemessageusers__user_id=user_id
- ).distinct().annotate(
- has_read=F('m2m_sitemessageusers__has_read'),
- read_at=F('m2m_sitemessageusers__read_at')
- ).order_by('-date_created')
-
- return site_msgs
+ site_msg_rels = SiteMessage.objects \
+ .filter(user=user_id) \
+ .prefetch_related('content') \
+ .order_by('-date_created')
+ return site_msg_rels
@classmethod
def get_user_all_msgs_count(cls, user_id):
- site_msgs_count = SiteMessageModel.objects.filter(
- m2m_sitemessageusers__user_id=user_id
+ site_msgs_count = SiteMessage.objects.filter(
+ user_id=user_id
).distinct().count()
return site_msgs_count
@classmethod
def filter_user_msgs(cls, user_id, has_read=False):
- site_msgs = SiteMessageModel.objects.filter(
- m2m_sitemessageusers__user_id=user_id,
- m2m_sitemessageusers__has_read=has_read
- ).distinct().annotate(
- has_read=F('m2m_sitemessageusers__has_read'),
- read_at=F('m2m_sitemessageusers__read_at')
- ).order_by('-date_created')
-
- return site_msgs
+ return cls.get_user_all_msgs(user_id).filter(has_read=has_read)
@classmethod
def get_user_unread_msgs_count(cls, user_id):
- site_msgs_count = SiteMessageModel.objects.filter(
- m2m_sitemessageusers__user_id=user_id,
- m2m_sitemessageusers__has_read=False
- ).distinct().count()
+ site_msgs_count = SiteMessage.objects \
+ .filter(user=user_id, has_read=False) \
+ .values_list('content', flat=True) \
+ .distinct().count()
return site_msgs_count
@classmethod
def mark_msgs_as_read(cls, user_id, msg_ids=None):
- q = Q(user_id=user_id) & Q(has_read=False)
- if msg_ids is not None:
- q &= Q(sitemessage_id__in=msg_ids)
- site_msg_users = SiteMessageUsers.objects.filter(q)
-
- for site_msg_user in site_msg_users:
- site_msg_user.has_read = True
- site_msg_user.read_at = local_now()
-
- SiteMessageUsers.objects.bulk_update(
- site_msg_users, fields=('has_read', 'read_at'))
+ site_msgs = SiteMessage.objects.filter(user_id=user_id)
+ if msg_ids:
+ site_msgs = site_msgs.filter(id__in=msg_ids)
+ site_msgs.update(has_read=True, read_at=local_now())
diff --git a/apps/notifications/urls/api_urls.py b/apps/notifications/urls/api_urls.py
index a65dd7b79..f35d7754f 100644
--- a/apps/notifications/urls/api_urls.py
+++ b/apps/notifications/urls/api_urls.py
@@ -1,7 +1,6 @@
-
-from rest_framework_bulk.routes import BulkRouter
-from django.urls import path
from django.conf import settings
+from django.urls import path
+from rest_framework_bulk.routes import BulkRouter
from notifications import api
@@ -10,11 +9,12 @@ app_name = 'notifications'
router = BulkRouter()
router.register('system-msg-subscription', api.SystemMsgSubscriptionViewSet, 'system-msg-subscription')
router.register('user-msg-subscription', api.UserMsgSubscriptionViewSet, 'user-msg-subscription')
-router.register('site-message', api.SiteMessageViewSet, 'site-message')
+router.register('site-messages', api.SiteMessageViewSet, 'site-message')
urlpatterns = [
path('backends/', api.BackendListView.as_view(), name='backends')
-] + router.urls
+]
+urlpatterns += router.urls
if settings.DEBUG:
urlpatterns += [
diff --git a/apps/terminal/notifications.py b/apps/terminal/notifications.py
index 700bfde9b..a6b1a69a6 100644
--- a/apps/terminal/notifications.py
+++ b/apps/terminal/notifications.py
@@ -1,18 +1,17 @@
from typing import Callable
-from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.template.loader import render_to_string
+from django.utils.translation import ugettext_lazy as _
-from users.models import User
from common.utils import get_logger, reverse
-from notifications.notifications import SystemMessage
-from terminal.models import Session, Command
-from notifications.models import SystemMsgSubscription
-from notifications.backends import BACKEND
from common.utils import lazyproperty
from common.utils.timezone import local_now_display
-
+from notifications.backends import BACKEND
+from notifications.models import SystemMsgSubscription
+from notifications.notifications import SystemMessage
+from terminal.models import Session, Command
+from users.models import User
logger = get_logger(__name__)
@@ -75,8 +74,11 @@ class CommandAlertMessage(CommandAlertMixin, SystemMessage):
@classmethod
def gen_test_msg(cls):
- command = Command.objects.first().to_dict()
- command['session'] = Session.objects.first().id
+ command = Command.objects.first()
+ if not command:
+ command = Command(user='test', asset='test', input='test', session='111111111')
+ else:
+ command['session'] = Session.objects.first().id
return cls(command)
def get_html_msg(self) -> dict:
From fc34980f20108ce17f0884e80fa39e3df19c34c7 Mon Sep 17 00:00:00 2001
From: Bai
Date: Wed, 1 Feb 2023 17:03:42 +0800
Subject: [PATCH 35/92] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=B8=BB?=
=?UTF-8?q?=E6=9C=BA=E8=AF=A6=E6=83=85=E6=98=BE=E7=A4=BA=E7=A1=AC=E4=BB=B6?=
=?UTF-8?q?=E4=BF=A1=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/assets/api/asset/asset.py | 2 +-
apps/assets/api/asset/host.py | 12 +++++++++++-
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py
index a02d35121..d6107631c 100644
--- a/apps/assets/api/asset/asset.py
+++ b/apps/assets/api/asset/asset.py
@@ -71,7 +71,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
("platform", serializers.PlatformSerializer),
("suggestion", serializers.MiniAssetSerializer),
("gateways", serializers.GatewaySerializer),
- ("spec_info", serializers.SpecSerializer),
+ ("spec_info", serializers.SpecSerializer)
)
rbac_perms = (
("match", "assets.match_asset"),
diff --git a/apps/assets/api/asset/host.py b/apps/assets/api/asset/host.py
index d2ddc954d..3e11b8ad5 100644
--- a/apps/assets/api/asset/host.py
+++ b/apps/assets/api/asset/host.py
@@ -1,5 +1,8 @@
from assets.models import Host, Asset
-from assets.serializers import HostSerializer
+from assets.serializers import HostSerializer, HostInfoSerializer
+from rest_framework.decorators import action
+from rest_framework.response import Response
+
from .asset import AssetViewSet
__all__ = ['HostViewSet']
@@ -12,4 +15,11 @@ class HostViewSet(AssetViewSet):
def get_serializer_classes(self):
serializer_classes = super().get_serializer_classes()
serializer_classes['default'] = HostSerializer
+ serializer_classes['info'] = HostInfoSerializer
return serializer_classes
+
+ @action(methods=["GET"], detail=True, url_path="info")
+ def info(self, *args, **kwargs):
+ asset = super().get_object()
+ return Response(asset.info)
+
From 7b95859015e625c11451691d7ba01bfbd51730fa Mon Sep 17 00:00:00 2001
From: fit2bot <68588906+fit2bot@users.noreply.github.com>
Date: Wed, 1 Feb 2023 18:20:56 +0800
Subject: [PATCH 36/92] perf: device ansible (#9396)
Co-authored-by: feng <1304903146@qq.com>
---
apps/assets/const/device.py | 4 ++--
apps/assets/const/web.py | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py
index a1913994c..d00996dd6 100644
--- a/apps/assets/const/device.py
+++ b/apps/assets/const/device.py
@@ -31,11 +31,11 @@ class DeviceTypes(BaseType):
def _get_automation_constrains(cls) -> dict:
return {
'*': {
- 'ansible_enabled': True,
+ 'ansible_enabled': False,
'ansible_config': {
'ansible_connection': 'local',
},
- 'ping_enabled': True,
+ 'ping_enabled': False,
'gather_facts_enabled': False,
'gather_accounts_enabled': False,
'verify_account_enabled': False,
diff --git a/apps/assets/const/web.py b/apps/assets/const/web.py
index 88ff7f8f9..42ea995ac 100644
--- a/apps/assets/const/web.py
+++ b/apps/assets/const/web.py
@@ -20,6 +20,8 @@ class WebTypes(BaseType):
def _get_automation_constrains(cls) -> dict:
constrains = {
'*': {
+ 'ansible_enabled': False,
+ 'ping_enabled': False,
'gather_facts_enabled': False,
'verify_account_enabled': False,
'change_secret_enabled': False,
From 9d898f0aec9da06530fef9c04432f2c0fc559e14 Mon Sep 17 00:00:00 2001
From: Aaron3S
Date: Wed, 18 Jan 2023 18:03:33 +0800
Subject: [PATCH 37/92] playbook ide
---
apps/ops/api/playbook.py | 189 +++++++++++++++++-
apps/ops/const.py | 5 +
.../ops/migrations/0025_auto_20230117_1130.py | 23 +++
apps/ops/models/playbook.py | 11 +-
apps/ops/serializers/playbook.py | 2 +-
apps/ops/urls/api_urls.py | 1 +
6 files changed, 219 insertions(+), 12 deletions(-)
create mode 100644 apps/ops/migrations/0025_auto_20230117_1130.py
diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py
index aaafacd58..c51868c24 100644
--- a/apps/ops/api/playbook.py
+++ b/apps/ops/api/playbook.py
@@ -1,13 +1,19 @@
import os
+import shutil
import zipfile
from django.conf import settings
+from django.shortcuts import get_object_or_404
+
from orgs.mixins.api import OrgBulkModelViewSet
from ..exception import PlaybookNoValidEntry
from ..models import Playbook
from ..serializers.playbook import PlaybookSerializer
-__all__ = ["PlaybookViewSet"]
+__all__ = ["PlaybookViewSet", "PlaybookFileBrowserAPIView"]
+
+from rest_framework.views import APIView
+from rest_framework.response import Response
def unzip_playbook(src, dist):
@@ -31,12 +37,175 @@ class PlaybookViewSet(OrgBulkModelViewSet):
def perform_create(self, serializer):
instance = serializer.save()
- src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name)
- dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
- unzip_playbook(src_path, dest_path)
- valid_entry = ('main.yml', 'main.yaml', 'main')
- for f in os.listdir(dest_path):
- if f in valid_entry:
- return
- os.remove(dest_path)
- raise PlaybookNoValidEntry
+ if instance.create_method == 'blank':
+ dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
+ os.makedirs(dest_path)
+ with open(os.path.join(dest_path, 'main.yml'), 'w') as f:
+ f.write('## write your playbook here')
+
+ if instance.create_method == 'upload':
+ src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name)
+ dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
+ unzip_playbook(src_path, dest_path)
+ valid_entry = ('main.yml', 'main.yaml', 'main')
+ for f in os.listdir(dest_path):
+ if f in valid_entry:
+ return
+ os.remove(dest_path)
+ raise PlaybookNoValidEntry
+
+
+class PlaybookFileBrowserAPIView(APIView):
+ rbac_perms = ()
+ permission_classes = ()
+
+ def get(self, request, **kwargs):
+ playbook_id = kwargs.get('pk')
+ playbook = get_object_or_404(Playbook, id=playbook_id)
+ work_path = playbook.work_dir
+ file_key = request.query_params.get('key', '')
+ if file_key:
+ file_path = os.path.join(work_path, file_key)
+ with open(file_path, 'r') as f:
+ content = f.read()
+ return Response({'content': content})
+ else:
+ expand_key = request.query_params.get('expand', '')
+ nodes = self.generate_tree(playbook, work_path, expand_key)
+ return Response(nodes)
+
+ def post(self, request, **kwargs):
+ playbook_id = kwargs.get('pk')
+ playbook = get_object_or_404(Playbook, id=playbook_id)
+ work_path = playbook.work_dir
+
+ parent_key = request.data.get('key', '')
+ if parent_key == 'root':
+ parent_key = ''
+ if os.path.dirname(parent_key) == 'root':
+ parent_key = os.path.basename(parent_key)
+ full_path = os.path.join(work_path, parent_key)
+
+ is_directory = request.data.get('is_directory', False)
+ content = request.data.get('content', '')
+
+ if is_directory:
+ new_file_path = os.path.join(full_path, 'new_dir')
+ i = 0
+ while os.path.exists(new_file_path):
+ i += 1
+ new_file_path = os.path.join(full_path, 'new_dir({})'.format(i))
+ os.makedirs(new_file_path)
+ else:
+ new_file_path = os.path.join(full_path, 'new_file.yml')
+ i = 0
+ while os.path.exists(new_file_path):
+ i += 1
+ new_file_path = os.path.join(full_path, 'new_file({}).yml'.format(i))
+ with open(new_file_path, 'w') as f:
+ f.write(content)
+
+ relative_path = os.path.relpath(os.path.dirname(new_file_path), work_path)
+ new_node = {
+ "name": os.path.basename(new_file_path),
+ "title": os.path.basename(new_file_path),
+ "id": os.path.join(relative_path, os.path.basename(new_file_path))
+ if not os.path.join(relative_path, os.path.basename(new_file_path)).startswith('.')
+ else os.path.basename(new_file_path),
+ "isParent": is_directory,
+ "pId": relative_path if not relative_path.startswith('.') else 'root',
+ "open": True,
+ }
+ if not is_directory:
+ new_node['iconSkin'] = 'file'
+
+ return Response(new_node)
+
+ def patch(self, request, **kwargs):
+ playbook_id = kwargs.get('pk')
+ playbook = get_object_or_404(Playbook, id=playbook_id)
+ work_path = playbook.work_dir
+
+ file_key = request.data.get('key', '')
+ if os.path.dirname(file_key) == 'root':
+ file_key = os.path.basename(file_key)
+
+ new_name = request.data.get('new_name', '')
+ content = request.data.get('content', '')
+ is_directory = request.data.get('is_directory', False)
+
+ if not file_key or file_key == 'root':
+ return Response(status=400)
+ file_path = os.path.join(work_path, file_key)
+
+ if new_name:
+ new_file_path = os.path.join(os.path.dirname(file_path), new_name)
+ os.rename(file_path, new_file_path)
+ file_path = new_file_path
+
+ if not is_directory and content:
+ with open(file_path, 'w') as f:
+ f.write(content)
+ return Response({'msg': 'ok'})
+
+ def delete(self, request, **kwargs):
+ not_delete_allowed = ['root', 'main.yml']
+ playbook_id = kwargs.get('pk')
+ playbook = get_object_or_404(Playbook, id=playbook_id)
+ work_path = playbook.work_dir
+ file_key = request.query_params.get('key', '')
+ if not file_key:
+ return Response(status=400)
+ if file_key in not_delete_allowed:
+ return Response(status=400)
+ file_path = os.path.join(work_path, file_key)
+ if os.path.isdir(file_path):
+ shutil.rmtree(file_path)
+ else:
+ os.remove(file_path)
+ return Response({'msg': 'ok'})
+
+ @staticmethod
+ def generate_tree(playbook, root_path, expand_key=None):
+ nodes = [{
+ "name": playbook.name,
+ "title": playbook.name,
+ "id": 'root',
+ "isParent": True,
+ "open": True,
+ "pId": '',
+ "temp": False
+ }]
+ for path, dirs, files in os.walk(root_path):
+ dirs.sort()
+ files.sort()
+
+ relative_path = os.path.relpath(path, root_path)
+ for d in dirs:
+ node = {
+ "name": d,
+ "title": d,
+ "id": os.path.join(relative_path, d) if not os.path.join(relative_path, d).startswith(
+ '.') else d,
+ "isParent": True,
+ "open": False,
+ "pId": relative_path if not relative_path.startswith('.') else 'root',
+ "temp": False
+ }
+ if expand_key == node['id']:
+ node['open'] = True
+ nodes.append(node)
+ for f in files:
+ node = {
+ "name": f,
+ "title": f,
+ "iconSkin": 'file',
+ "id": os.path.join(relative_path, f) if not os.path.join(relative_path, f).startswith(
+ '.') else f,
+ "isParent": False,
+ "open": False,
+ "pId": relative_path if not relative_path.startswith('.') else 'root',
+ "temp": False
+ }
+ nodes.append(node)
+ return nodes
diff --git a/apps/ops/const.py b/apps/ops/const.py
index 8838c31c5..fb394b6bf 100644
--- a/apps/ops/const.py
+++ b/apps/ops/const.py
@@ -29,6 +29,11 @@ DEFAULT_PASSWORD_RULES = {
}
+class CreateMethods(models.TextChoices):
+ blank = 'blank', _('Blank')
+ vcs = 'vcs', _('VCS')
+
+
class Types(models.TextChoices):
adhoc = 'adhoc', _('Adhoc')
playbook = 'playbook', _('Playbook')
diff --git a/apps/ops/migrations/0025_auto_20230117_1130.py b/apps/ops/migrations/0025_auto_20230117_1130.py
new file mode 100644
index 000000000..b6d3881e7
--- /dev/null
+++ b/apps/ops/migrations/0025_auto_20230117_1130.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.14 on 2023-01-17 03:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ops', '0024_alter_celerytask_date_last_publish'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='playbook',
+ name='create_method',
+ field=models.CharField(choices=[('blank', 'Blank'), ('upload', 'Upload'), ('vcs', 'VCS')], default='blank', max_length=128, verbose_name='CreateMethod'),
+ ),
+ migrations.AddField(
+ model_name='playbook',
+ name='vcs_url',
+ field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='VCS URL'),
+ ),
+ ]
diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py
index f92968762..0e649532e 100644
--- a/apps/ops/models/playbook.py
+++ b/apps/ops/models/playbook.py
@@ -5,6 +5,7 @@ from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
+from ops.const import CreateMethods
from ops.exception import PlaybookNoValidEntry
from orgs.mixins.models import JMSOrgBaseModel
@@ -15,12 +16,20 @@ class Playbook(JMSOrgBaseModel):
path = models.FileField(upload_to='playbooks/')
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True)
+ create_method = models.CharField(max_length=128, choices=CreateMethods.choices, default=CreateMethods.blank,
+ verbose_name=_('CreateMethod'))
+ vcs_url = models.CharField(max_length=1024, default='', verbose_name=_('VCS URL'), null=True, blank=True)
@property
def entry(self):
- work_dir = os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__())
+ work_dir = self.work_dir
valid_entry = ('main.yml', 'main.yaml', 'main')
for f in os.listdir(work_dir):
if f in valid_entry:
return os.path.join(work_dir, f)
raise PlaybookNoValidEntry
+
+ @property
+ def work_dir(self):
+ work_dir = os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__())
+ return work_dir
diff --git a/apps/ops/serializers/playbook.py b/apps/ops/serializers/playbook.py
index a69bab3c9..3429766d5 100644
--- a/apps/ops/serializers/playbook.py
+++ b/apps/ops/serializers/playbook.py
@@ -27,5 +27,5 @@ class PlaybookSerializer(BulkOrgResourceModelSerializer):
model = Playbook
read_only_fields = ["id", "date_created", "date_updated"]
fields = read_only_fields + [
- "id", 'path', "name", "comment", "creator",
+ "id", 'path', "name", "comment", "creator", 'create_method', 'vcs_url',
]
diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py
index b0a4ccb14..e637d84fb 100644
--- a/apps/ops/urls/api_urls.py
+++ b/apps/ops/urls/api_urls.py
@@ -23,6 +23,7 @@ router.register(r'tasks', api.CeleryTaskViewSet, 'task')
router.register(r'task-executions', api.CeleryTaskExecutionViewSet, 'task-executions')
urlpatterns = [
+ path('playbook//file/', api.PlaybookFileBrowserAPIView.as_view(), name='playbook-file'),
path('variables/help/', api.JobRunVariableHelpAPIView.as_view(), name='variable-help'),
path('job-execution/asset-detail/', api.JobAssetDetail.as_view(), name='asset-detail'),
path('job-execution/task-detail//', api.JobExecutionTaskDetail.as_view(), name='task-detail'),
From 853f9c422d22ad5088001cbcaac8d2827d76703e Mon Sep 17 00:00:00 2001
From: ibuler
Date: Wed, 1 Feb 2023 18:45:51 +0800
Subject: [PATCH 38/92] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20i18n?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/assets/serializers/asset/common.py | 2 +-
apps/locale/zh/LC_MESSAGES/django.po | 2 +-
apps/users/serializers/user.py | 13 ++++---------
3 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py
index 7f39e2378..82a163d84 100644
--- a/apps/assets/serializers/asset/common.py
+++ b/apps/assets/serializers/asset/common.py
@@ -6,7 +6,7 @@ from django.db.transaction import atomic
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
-from accounts.models import Account, AccountTemplate
+from accounts.models import Account
from accounts.serializers import AccountSerializerCreateValidateMixin
from common.serializers import WritableNestedModelSerializer, SecretReadableMixin, CommonModelSerializer
from common.serializers.fields import LabeledChoiceField
diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index bdbc5b7d0..1f16f7a8f 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/zh/LC_MESSAGES/django.po
@@ -6381,7 +6381,7 @@ msgstr "登录被阻塞"
#: users/serializers/user.py:93
msgid "Can public key authentication"
-msgstr "能否公钥认证"
+msgstr "公钥认证"
#: users/serializers/user.py:162
msgid "Avatar url"
diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py
index 7e2c6ea33..d1cd63fc5 100644
--- a/apps/users/serializers/user.py
+++ b/apps/users/serializers/user.py
@@ -96,11 +96,8 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
read_only=True
)
password = EncryptedField(
- label=_("Password"),
- required=False,
- allow_blank=True,
- allow_null=True,
- max_length=1024,
+ label=_("Password"), required=False, allow_blank=True,
+ allow_null=True, max_length=1024,
)
custom_m2m_fields = {
"system_roles": [BuiltinRole.system_user],
@@ -129,10 +126,8 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
]
# 包含不太常用的字段,可以没有
fields_verbose = fields_small + [
- "mfa_force_enabled",
- "is_first_login",
- "date_password_last_updated",
- "avatar_url",
+ "mfa_force_enabled", "is_first_login",
+ "date_password_last_updated", "avatar_url",
]
# 外键的字段
fields_fk = []
From 758d6ae81ba67690c84c059f88c0e942940f636e Mon Sep 17 00:00:00 2001
From: fit2bot <68588906+fit2bot@users.noreply.github.com>
Date: Wed, 1 Feb 2023 19:02:41 +0800
Subject: [PATCH 39/92] perf: gateway print (#9398)
Co-authored-by: feng <1304903146@qq.com>
---
apps/assets/automations/ping_gateway/manager.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/assets/automations/ping_gateway/manager.py b/apps/assets/automations/ping_gateway/manager.py
index 858717665..d03e5d22d 100644
--- a/apps/assets/automations/ping_gateway/manager.py
+++ b/apps/assets/automations/ping_gateway/manager.py
@@ -33,7 +33,7 @@ class PingGatewayManager:
err = _('No account')
return False, err
- logger.debug('Test account: {}'.format(account))
+ print('Test account: {}'.format(account))
try:
proxy.connect(
gateway.address,
@@ -91,7 +91,7 @@ class PingGatewayManager:
@staticmethod
def on_host_success(gateway, account):
- logger.info('\033[32m {} -> {}\033[0m\n'.format(gateway, account))
+ print('\033[32m {} -> {}\033[0m\n'.format(gateway, account))
gateway.set_connectivity(Connectivity.OK)
if not account:
return
@@ -99,7 +99,7 @@ class PingGatewayManager:
@staticmethod
def on_host_error(gateway, account, error):
- logger.info('\033[31m {} -> {} 原因: {} \033[0m\n'.format(gateway, account, error))
+ print('\033[31m {} -> {} 原因: {} \033[0m\n'.format(gateway, account, error))
gateway.set_connectivity(Connectivity.ERR)
if not account:
return
@@ -107,7 +107,7 @@ class PingGatewayManager:
@staticmethod
def before_runner_start():
- logger.info(">>> 开始执行测试网关可连接性任务")
+ print(">>> 开始执行测试网关可连接性任务")
def get_accounts(self, gateway):
account = gateway.select_account
From 308077155951ce9d0fcf67a985fd91bd08ea9920 Mon Sep 17 00:00:00 2001
From: jiangweidong <80373698+Hi-JWD@users.noreply.github.com>
Date: Thu, 2 Feb 2023 10:32:48 +0800
Subject: [PATCH 40/92] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=E6=B2=A1?=
=?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=88=B0=E8=AF=81=E4=B9=A6=E7=9A=84mongodb?=
=?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84=E6=97=A0=E6=B3=95=E6=89=A7=E8=A1=8C?=
=?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E4=BB=BB=E5=8A=A1=20(#9399)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/assets/automations/base/manager.py | 4 ++--
apps/ops/ansible/inventory.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py
index f9d06bb73..6247c85d7 100644
--- a/apps/assets/automations/base/manager.py
+++ b/apps/assets/automations/base/manager.py
@@ -73,7 +73,7 @@ class BasePlaybookManager:
if not path_dir:
return host
- specific = host.get('jms_asset', {}).get('specific', {})
+ specific = host.get('jms_asset', {}).get('secret_info', {})
cert_fields = ('ca_cert', 'client_key', 'client_cert')
filtered = list(filter(lambda x: specific.get(x), cert_fields))
if not filtered:
@@ -87,7 +87,7 @@ class BasePlaybookManager:
result = self.write_cert_to_file(
os.path.join(cert_dir, f), specific.get(f)
)
- host['jms_asset']['specific'][f] = result
+ host['jms_asset']['secret_info'][f] = result
return host
def host_callback(self, host, automation=None, **kwargs):
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index 0a3ce43af..a340b510d 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -105,7 +105,7 @@ class JMSInventory:
'id': str(asset.id), 'name': asset.name, 'address': asset.address,
'type': asset.type, 'category': asset.category,
'protocol': asset.protocol, 'port': asset.port,
- 'spec_info': asset.spec_info,
+ 'spec_info': asset.spec_info, 'secret_info': asset.secret_info,
'protocols': [{'name': p.name, 'port': p.port} for p in protocols],
},
'jms_account': {
From 7a5195e91eee2d328783f2b0e49dcc6ab314ce41 Mon Sep 17 00:00:00 2001
From: jiangweidong <80373698+Hi-JWD@users.noreply.github.com>
Date: Thu, 2 Feb 2023 10:33:07 +0800
Subject: [PATCH 41/92] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=E9=83=A8?=
=?UTF-8?q?=E7=BD=B2=E5=90=8E=EF=BC=8COracle=E6=97=A0=E6=B3=95=E6=89=A7?=
=?UTF-8?q?=E8=A1=8C=E8=87=AA=E5=8A=A8=E5=8C=96=E4=BB=BB=E5=8A=A1=E9=97=AE?=
=?UTF-8?q?=E9=A2=98=20(#9400)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/common/management/commands/services/services/celery_base.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/common/management/commands/services/services/celery_base.py b/apps/common/management/commands/services/services/celery_base.py
index 7d69139b2..8d197d4f8 100644
--- a/apps/common/management/commands/services/services/celery_base.py
+++ b/apps/common/management/commands/services/services/celery_base.py
@@ -18,6 +18,7 @@ class CeleryBaseService(BaseService):
os.environ.setdefault('ANSIBLE_FORCE_COLOR', 'True')
os.environ.setdefault('ANSIBLE_CONFIG', ansible_config_path)
os.environ.setdefault('ANSIBLE_LIBRARY', ansible_modules_path)
+ os.environ.setdefault('PYTHONPATH', settings.APPS_DIR)
if os.getuid() == 0:
os.environ.setdefault('C_FORCE_ROOT', '1')
From d68ed57eb918c0c7fe490f5271ea5a5d5ad6de04 Mon Sep 17 00:00:00 2001
From: Aaron3S
Date: Thu, 2 Feb 2023 11:34:47 +0800
Subject: [PATCH 42/92] =?UTF-8?q?=E4=BC=98=E5=8C=96=20playbook=20ide?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/ops/api/playbook.py | 27 ++++++++++++++++-----------
1 file changed, 16 insertions(+), 11 deletions(-)
diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py
index c51868c24..36c1c4994 100644
--- a/apps/ops/api/playbook.py
+++ b/apps/ops/api/playbook.py
@@ -88,20 +88,26 @@ class PlaybookFileBrowserAPIView(APIView):
is_directory = request.data.get('is_directory', False)
content = request.data.get('content', '')
+ name = request.data.get('name', '')
+
+ def find_new_name(p, is_file=False):
+ if not p:
+ if is_file:
+ p = 'new_file.yml'
+ else:
+ p = 'new_dir'
+ np = os.path.join(full_path, p)
+ n = 0
+ while os.path.exists(np):
+ n += 1
+ np = os.path.join(full_path, '{}({})'.format(p, n))
+ return np
if is_directory:
- new_file_path = os.path.join(full_path, 'new_dir')
- i = 0
- while os.path.exists(new_file_path):
- i += 1
- new_file_path = os.path.join(full_path, 'new_dir({})'.format(i))
+ new_file_path = find_new_name(name)
os.makedirs(new_file_path)
else:
- new_file_path = os.path.join(full_path, 'new_file.yml')
- i = 0
- while os.path.exists(new_file_path):
- i += 1
- new_file_path = os.path.join(full_path, 'new_file({}).yml'.format(i))
+ new_file_path = find_new_name(name, True)
with open(new_file_path, 'w') as f:
f.write(content)
@@ -118,7 +124,6 @@ class PlaybookFileBrowserAPIView(APIView):
}
if not is_directory:
new_node['iconSkin'] = 'file'
-
return Response(new_node)
def patch(self, request, **kwargs):
From 0aa681620fcf09de38ec08227219c76a06e801ee Mon Sep 17 00:00:00 2001
From: feng <1304903146@qq.com>
Date: Thu, 2 Feb 2023 10:41:03 +0800
Subject: [PATCH 43/92] perf: asset tree search
---
apps/assets/api/tree.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/assets/api/tree.py b/apps/assets/api/tree.py
index 346775069..9c9ffa8c9 100644
--- a/apps/assets/api/tree.py
+++ b/apps/assets/api/tree.py
@@ -119,7 +119,7 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
query_all = self.request.query_params.get("all", "0") == "all"
include_assets = self.request.query_params.get('assets', '0') == '1'
if not self.instance or not include_assets:
- return []
+ return Asset.objects.none()
if query_all:
assets = self.instance.get_all_assets_for_tree()
else:
From 5fcd83b587cd020346c830bbc5146b6f02062050 Mon Sep 17 00:00:00 2001
From: Bai
Date: Thu, 2 Feb 2023 14:37:21 +0800
Subject: [PATCH 44/92] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?=
=?UTF-8?q?=E5=8F=96=20swagger=20api=20=E6=97=B6=EF=BC=8CAssetSerializer?=
=?UTF-8?q?=20category,=20type=20=E5=AD=97=E6=AE=B5=E6=89=8B=E5=8A=A8?=
=?UTF-8?q?=E8=AE=BE=E7=BD=AE=20=5Fchoices=20=E5=B1=9E=E6=80=A7=E6=97=B6?=
=?UTF-8?q?=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/acls/serializers/login_acl.py | 4 ++--
apps/assets/serializers/asset/common.py | 4 ++--
apps/perms/serializers/permission.py | 4 +---
apps/tickets/serializers/ticket/ticket.py | 4 ++--
4 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/apps/acls/serializers/login_acl.py b/apps/acls/serializers/login_acl.py
index 99b1f1077..59b9cf0ad 100644
--- a/apps/acls/serializers/login_acl.py
+++ b/apps/acls/serializers/login_acl.py
@@ -52,10 +52,10 @@ class LoginACLSerializer(BulkModelSerializer):
action = self.fields.get("action")
if not action:
return
- choices = action._choices
+ choices = action.choices
if not has_valid_xpack_license():
choices.pop(LoginACL.ActionChoices.review, None)
- action._choices = choices
+ action.choices = choices
def get_rules_serializer(self):
return RuleSerializer()
diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py
index 82a163d84..e51c4558f 100644
--- a/apps/assets/serializers/asset/common.py
+++ b/apps/assets/serializers/asset/common.py
@@ -139,9 +139,9 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
return
category = request.path.strip('/').split('/')[-1].rstrip('s')
field_category = self.fields.get('category')
- field_category._choices = Category.filter_choices(category)
+ field_category.choices = Category.filter_choices(category)
field_type = self.fields.get('type')
- field_type._choices = AllTypes.filter_choices(category)
+ field_type.choices = AllTypes.filter_choices(category)
@classmethod
def setup_eager_loading(cls, queryset):
diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py
index 63ba3a765..fef9929b8 100644
--- a/apps/perms/serializers/permission.py
+++ b/apps/perms/serializers/permission.py
@@ -70,9 +70,7 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
actions = self.fields.get("actions")
if not actions:
return
- choices = actions._choices
- actions._choices = choices
- actions.default = list(choices.keys())
+ actions.default = list(actions.choices.keys())
@classmethod
def setup_eager_loading(cls, queryset):
diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py
index 334031e5e..4a36ccf4f 100644
--- a/apps/tickets/serializers/ticket/ticket.py
+++ b/apps/tickets/serializers/ticket/ticket.py
@@ -39,9 +39,9 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
tp = self.fields.get('type')
if not tp:
return
- choices = tp._choices
+ choices = tp.choices
choices.pop(TicketType.general, None)
- tp._choices = choices
+ tp.choices = choices
@classmethod
def setup_eager_loading(cls, queryset):
From d78725f7c5ac51db0ceb43166302b50e1d62b1c2 Mon Sep 17 00:00:00 2001
From: fit2bot <68588906+fit2bot@users.noreply.github.com>
Date: Thu, 2 Feb 2023 14:52:44 +0800
Subject: [PATCH 45/92] perf: choice validate (#9404)
Co-authored-by: feng <1304903146@qq.com>
---
apps/common/serializers/fields.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/common/serializers/fields.py b/apps/common/serializers/fields.py
index 96ff7c103..84d4d5927 100644
--- a/apps/common/serializers/fields.py
+++ b/apps/common/serializers/fields.py
@@ -66,7 +66,7 @@ class LabeledChoiceField(ChoiceField):
def to_internal_value(self, data):
if isinstance(data, dict):
- return data.get("value")
+ data = data.get("value")
return super(LabeledChoiceField, self).to_internal_value(data)
From 6429b56a54282f0a1ddb77535aa65da63522eb80 Mon Sep 17 00:00:00 2001
From: fit2bot <68588906+fit2bot@users.noreply.github.com>
Date: Thu, 2 Feb 2023 15:01:56 +0800
Subject: [PATCH 46/92] =?UTF-8?q?fix:=20=E8=B5=84=E4=BA=A7=E5=85=8B?=
=?UTF-8?q?=E9=9A=86=E8=B4=A6=E5=8F=B7bug=20(#9407)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: feng <1304903146@qq.com>
---
apps/accounts/serializers/account/account.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py
index cc13aced8..36dff55a9 100644
--- a/apps/accounts/serializers/account/account.py
+++ b/apps/accounts/serializers/account/account.py
@@ -17,8 +17,8 @@ class AccountSerializerCreateValidateMixin:
replace_attrs: callable
def to_internal_value(self, data):
+ self.id = data.pop('id', None)
ret = super().to_internal_value(data)
- self.id = ret.pop('id', None)
self.push_now = ret.pop('push_now', False)
self.template = ret.pop('template', False)
return ret
From 88edc9191cc0292933cf510b1a10a895888f485e Mon Sep 17 00:00:00 2001
From: Bai
Date: Thu, 2 Feb 2023 14:53:18 +0800
Subject: [PATCH 47/92] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20ACL=20?=
=?UTF-8?q?=E6=8E=92=E5=BA=8F=20priority,=20date=5Fupdated,=20name?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/acls/migrations/0008_commandgroup_comment.py | 6 +++---
apps/acls/models/base.py | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/acls/migrations/0008_commandgroup_comment.py b/apps/acls/migrations/0008_commandgroup_comment.py
index 82e44912c..8937d2a93 100644
--- a/apps/acls/migrations/0008_commandgroup_comment.py
+++ b/apps/acls/migrations/0008_commandgroup_comment.py
@@ -20,14 +20,14 @@ class Migration(migrations.Migration):
),
migrations.AlterModelOptions(
name='commandfilteracl',
- options={'ordering': ('priority', 'name'), 'verbose_name': 'Command acl'},
+ options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Command acl'},
),
migrations.AlterModelOptions(
name='loginacl',
- options={'ordering': ('priority', 'name'), 'verbose_name': 'Login acl'},
+ options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Login acl'},
),
migrations.AlterModelOptions(
name='loginassetacl',
- options={'ordering': ('priority', 'name'), 'verbose_name': 'Login asset acl'},
+ options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Login asset acl'},
),
]
diff --git a/apps/acls/models/base.py b/apps/acls/models/base.py
index 7ba48b3c6..53a566048 100644
--- a/apps/acls/models/base.py
+++ b/apps/acls/models/base.py
@@ -82,7 +82,7 @@ class BaseACL(JMSBaseModel):
objects = ACLManager.from_queryset(BaseACLQuerySet)()
class Meta:
- ordering = ('priority', 'name')
+ ordering = ('priority', 'date_updated', 'name')
abstract = True
def is_action(self, action):
From 1c55bde8c8fc567b1086db148eefc52e6cf7f7a6 Mon Sep 17 00:00:00 2001
From: Eric
Date: Thu, 2 Feb 2023 15:03:48 +0800
Subject: [PATCH 48/92] =?UTF-8?q?perf:=20=E5=8F=91=E5=B8=83=E6=9C=BA?=
=?UTF-8?q?=E9=83=A8=E7=BD=B2=E6=96=B0=E5=A2=9E=20CORE=5FHOST=20=E9=85=8D?=
=?UTF-8?q?=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/terminal/automations/deploy_applet_host/__init__.py | 5 +++--
apps/terminal/serializers/applet_host.py | 3 +++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/apps/terminal/automations/deploy_applet_host/__init__.py b/apps/terminal/automations/deploy_applet_host/__init__.py
index c5e903f35..00f63463b 100644
--- a/apps/terminal/automations/deploy_applet_host/__init__.py
+++ b/apps/terminal/automations/deploy_applet_host/__init__.py
@@ -53,14 +53,15 @@ class DeployAppletHostManager:
if not download_host:
download_host = site_url
options = self.deployment.host.deploy_options
- site_url = site_url.rstrip("/")
+ core_host = options.get("CORE_HOST", site_url)
+ core_host = core_host.rstrip("/")
download_host = download_host.rstrip("/")
def handler(plays):
for play in plays:
play["vars"].update(options)
play["vars"]["APPLET_DOWNLOAD_HOST"] = download_host
- play["vars"]["CORE_HOST"] = site_url
+ play["vars"]["CORE_HOST"] = core_host
play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token
play["vars"]["HOST_ID"] = host_id
play["vars"]["HOST_NAME"] = self.deployment.host.name
diff --git a/apps/terminal/serializers/applet_host.py b/apps/terminal/serializers/applet_host.py
index 9ec4b8104..cf7895a71 100644
--- a/apps/terminal/serializers/applet_host.py
+++ b/apps/terminal/serializers/applet_host.py
@@ -1,3 +1,4 @@
+from django.conf import settings
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
@@ -27,6 +28,8 @@ class DeployOptionsSerializer(serializers.Serializer):
(1, _("Disabled")),
(0, _("Enabled")),
)
+
+ CORE_HOST = serializers.CharField(default=settings.SITE_URL, label=_('API Server'), max_length=1024)
RDS_Licensing = serializers.BooleanField(default=False, label=_("RDS Licensing"))
RDS_LicenseServer = serializers.CharField(default='127.0.0.1', label=_('RDS License Server'), max_length=1024)
RDS_LicensingMode = serializers.ChoiceField(choices=LICENSE_MODE_CHOICES, default=4, label=_('RDS Licensing Mode'))
From 3341d55d1b37183808e0e9535fd41f6d2346881d Mon Sep 17 00:00:00 2001
From: Eric
Date: Thu, 2 Feb 2023 15:26:19 +0800
Subject: [PATCH 49/92] =?UTF-8?q?perf:=20APPLET=5FDOWNLOAD=5FHOST=20?=
=?UTF-8?q?=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/terminal/automations/deploy_applet_host/__init__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/terminal/automations/deploy_applet_host/__init__.py b/apps/terminal/automations/deploy_applet_host/__init__.py
index 00f63463b..164340098 100644
--- a/apps/terminal/automations/deploy_applet_host/__init__.py
+++ b/apps/terminal/automations/deploy_applet_host/__init__.py
@@ -50,11 +50,11 @@ class DeployAppletHostManager:
host_id = str(self.deployment.host.id)
if not site_url:
site_url = "http://localhost:8080"
- if not download_host:
- download_host = site_url
options = self.deployment.host.deploy_options
core_host = options.get("CORE_HOST", site_url)
core_host = core_host.rstrip("/")
+ if not download_host:
+ download_host = core_host
download_host = download_host.rstrip("/")
def handler(plays):
From e3e727f97260de3794f6db0f5cbbb81885bc7eb0 Mon Sep 17 00:00:00 2001
From: Aaron3S
Date: Thu, 2 Feb 2023 15:55:37 +0800
Subject: [PATCH 50/92] =?UTF-8?q?perf:=20=E9=BB=98=E8=AE=A4=E5=B1=95?=
=?UTF-8?q?=E5=BC=80=E7=BC=96=E8=BE=91=E5=99=A8=E6=89=80=E6=9C=89=E7=9B=AE?=
=?UTF-8?q?=E5=BD=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/ops/api/playbook.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py
index 36c1c4994..322129b8b 100644
--- a/apps/ops/api/playbook.py
+++ b/apps/ops/api/playbook.py
@@ -193,7 +193,7 @@ class PlaybookFileBrowserAPIView(APIView):
"id": os.path.join(relative_path, d) if not os.path.join(relative_path, d).startswith(
'.') else d,
"isParent": True,
- "open": False,
+ "open": True,
"pId": relative_path if not relative_path.startswith('.') else 'root',
"temp": False
}
From 64e48712a578c17f54f51389919806906fe67fb1 Mon Sep 17 00:00:00 2001
From: fit2bot <68588906+fit2bot@users.noreply.github.com>
Date: Thu, 2 Feb 2023 16:16:38 +0800
Subject: [PATCH 51/92] perf: gateway migrate (#9412)
Co-authored-by: feng <1304903146@qq.com>
---
apps/assets/migrations/0100_auto_20220711_1413.py | 3 +--
apps/assets/migrations/0101_auto_20220811_1511.py | 1 -
apps/assets/migrations/0103_auto_20220902_1021.py | 14 ++++++++++++++
3 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/apps/assets/migrations/0100_auto_20220711_1413.py b/apps/assets/migrations/0100_auto_20220711_1413.py
index 16e75cbae..2412c6df0 100644
--- a/apps/assets/migrations/0100_auto_20220711_1413.py
+++ b/apps/assets/migrations/0100_auto_20220711_1413.py
@@ -1,8 +1,7 @@
# Generated by Django 3.2.12 on 2022-07-11 06:13
import time
-from django.db import migrations, models
-from assets.models import Platform
+from django.db import migrations
def migrate_asset_accounts(apps, schema_editor):
diff --git a/apps/assets/migrations/0101_auto_20220811_1511.py b/apps/assets/migrations/0101_auto_20220811_1511.py
index 9ca39774e..0287a8414 100644
--- a/apps/assets/migrations/0101_auto_20220811_1511.py
+++ b/apps/assets/migrations/0101_auto_20220811_1511.py
@@ -1,5 +1,4 @@
# Generated by Django 3.2.14 on 2022-08-11 07:11
-import assets.models.platform
import django.db.models
from django.db import migrations, models
diff --git a/apps/assets/migrations/0103_auto_20220902_1021.py b/apps/assets/migrations/0103_auto_20220902_1021.py
index 8d324fa12..dfc731736 100644
--- a/apps/assets/migrations/0103_auto_20220902_1021.py
+++ b/apps/assets/migrations/0103_auto_20220902_1021.py
@@ -18,6 +18,8 @@ def _create_account_obj(secret, secret_type, gateway, asset, account_model):
def migrate_gateway_to_asset(apps, schema_editor):
db_alias = schema_editor.connection.alias
+ node_model = apps.get_model('assets', 'Node')
+ org_model = apps.get_model('orgs', 'Organization')
gateway_model = apps.get_model('assets', 'Gateway')
platform_model = apps.get_model('assets', 'Platform')
gateway_platform = platform_model.objects.using(db_alias).get(name=GATEWAY_NAME)
@@ -28,6 +30,16 @@ def migrate_gateway_to_asset(apps, schema_editor):
asset_model = apps.get_model('assets', 'Asset')
protocol_model = apps.get_model('assets', 'Protocol')
gateways = gateway_model.objects.all()
+
+ org_ids = gateways.order_by('org_id').values_list('org_id', flat=True).distinct()
+ node_dict = {}
+ for org_id in org_ids:
+ org = org_model.objects.using(db_alias).filter(id=org_id).first()
+ node = node_model.objects.using(db_alias).filter(
+ org_id=org_id, value=org.name, full_value=f'/{org.name}'
+ ).first()
+ node_dict[org_id] = node
+
for gateway in gateways:
comment = gateway.comment if gateway.comment else ''
data = {
@@ -40,6 +52,8 @@ def migrate_gateway_to_asset(apps, schema_editor):
'platform': gateway_platform,
}
asset = asset_model.objects.using(db_alias).create(**data)
+ node = node_dict.get(str(gateway.org_id))
+ asset.nodes.set([node])
asset_dict[gateway.id] = asset
protocol_model.objects.using(db_alias).create(name='ssh', port=gateway.port, asset=asset)
hosts = [host_model(asset_ptr=asset) for asset in asset_dict.values()]
From be151523f4c329762ad9eb8cc48bd0dee5771f75 Mon Sep 17 00:00:00 2001
From: fit2bot <68588906+fit2bot@users.noreply.github.com>
Date: Thu, 2 Feb 2023 16:40:00 +0800
Subject: [PATCH 52/92] perf: user filter (#9413)
Co-authored-by: feng <1304903146@qq.com>
---
apps/users/filters.py | 24 +++++++++++-------------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/apps/users/filters.py b/apps/users/filters.py
index 8359f8d2e..434faf847 100644
--- a/apps/users/filters.py
+++ b/apps/users/filters.py
@@ -6,12 +6,8 @@ from rbac.models import Role
class UserFilter(BaseFilterSet):
- system_roles = filters.ModelChoiceFilter(
- queryset=Role.objects.filter(scope='system'), method='filter_system_roles'
- )
- org_roles = filters.ModelChoiceFilter(
- queryset=Role.objects.filter(scope='org'), method='filter_org_roles'
- )
+ system_roles = filters.CharFilter(method='filter_system_roles')
+ org_roles = filters.CharFilter(method='filter_org_roles')
class Meta:
model = User
@@ -22,16 +18,18 @@ class UserFilter(BaseFilterSet):
@staticmethod
def filter_system_roles(queryset, name, value):
- queryset = queryset.prefetch_related('role_bindings')\
- .filter(role_bindings__role_id=value.id)\
- .distinct()
+ queryset = queryset.prefetch_related('role_bindings') \
+ .filter(
+ role_bindings__role__name=value,
+ role_bindings__role__scope='system'
+ ).distinct()
return queryset
@staticmethod
def filter_org_roles(queryset, name, value):
queryset = queryset.prefetch_related('role_bindings') \
- .filter(role_bindings__role_id=value.id) \
- .distinct()
+ .filter(
+ role_bindings__role__name=value,
+ role_bindings__role__scope='org'
+ ).distinct()
return queryset
-
-
From a045eb99361a458eccb8b5436a9b3e012b8708f1 Mon Sep 17 00:00:00 2001
From: Bai
Date: Thu, 2 Feb 2023 16:06:15 +0800
Subject: [PATCH 53/92] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=A7=92?=
=?UTF-8?q?=E8=89=B2=E5=88=97=E8=A1=A8=E6=8E=92=E5=BA=8F=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/ops/migrations/0025_auto_20230117_1130.py | 2 +-
apps/rbac/api/role.py | 1 -
apps/rbac/migrations/0012_alter_role_options.py | 17 +++++++++++++++++
apps/rbac/models/role.py | 1 +
4 files changed, 19 insertions(+), 2 deletions(-)
create mode 100644 apps/rbac/migrations/0012_alter_role_options.py
diff --git a/apps/ops/migrations/0025_auto_20230117_1130.py b/apps/ops/migrations/0025_auto_20230117_1130.py
index b6d3881e7..7a5aa33b8 100644
--- a/apps/ops/migrations/0025_auto_20230117_1130.py
+++ b/apps/ops/migrations/0025_auto_20230117_1130.py
@@ -13,7 +13,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='playbook',
name='create_method',
- field=models.CharField(choices=[('blank', 'Blank'), ('upload', 'Upload'), ('vcs', 'VCS')], default='blank', max_length=128, verbose_name='CreateMethod'),
+ field=models.CharField(choices=[('blank', 'Blank'), ('vcs', 'VCS')], default='blank', max_length=128, verbose_name='CreateMethod'),
),
migrations.AddField(
model_name='playbook',
diff --git a/apps/rbac/api/role.py b/apps/rbac/api/role.py
index 0a4d52706..255299516 100644
--- a/apps/rbac/api/role.py
+++ b/apps/rbac/api/role.py
@@ -4,7 +4,6 @@ from rest_framework.exceptions import PermissionDenied
from rest_framework.decorators import action
from common.api import JMSModelViewSet
-from common.api import PaginatedResponseMixin
from ..filters import RoleFilter
from ..serializers import RoleSerializer, RoleUserSerializer
from ..models import Role, SystemRole, OrgRole
diff --git a/apps/rbac/migrations/0012_alter_role_options.py b/apps/rbac/migrations/0012_alter_role_options.py
new file mode 100644
index 000000000..ba98b699e
--- /dev/null
+++ b/apps/rbac/migrations/0012_alter_role_options.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.2.16 on 2023-02-02 08:03
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('rbac', '0011_remove_redundant_permission'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='role',
+ options={'ordering': ('scope', 'name'), 'verbose_name': 'Role'},
+ ),
+ ]
diff --git a/apps/rbac/models/role.py b/apps/rbac/models/role.py
index 1eff7c15c..e5a18b5e6 100644
--- a/apps/rbac/models/role.py
+++ b/apps/rbac/models/role.py
@@ -43,6 +43,7 @@ class Role(JMSBaseModel):
class Meta:
unique_together = [('name', 'scope')]
+ ordering = ('scope', 'name')
verbose_name = _('Role')
def __str__(self):
From 2d514c0db07d5836f96d4bb6d1905a3010f855c8 Mon Sep 17 00:00:00 2001
From: Bai
Date: Thu, 2 Feb 2023 16:09:50 +0800
Subject: [PATCH 54/92] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=A7=92?=
=?UTF-8?q?=E8=89=B2=E5=88=97=E8=A1=A8=E6=8E=92=E5=BA=8F=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/rbac/migrations/0012_alter_role_options.py | 2 +-
apps/rbac/models/role.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/rbac/migrations/0012_alter_role_options.py b/apps/rbac/migrations/0012_alter_role_options.py
index ba98b699e..6c7ce1525 100644
--- a/apps/rbac/migrations/0012_alter_role_options.py
+++ b/apps/rbac/migrations/0012_alter_role_options.py
@@ -12,6 +12,6 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='role',
- options={'ordering': ('scope', 'name'), 'verbose_name': 'Role'},
+ options={'ordering': ('-builtin', 'scope', 'name'), 'verbose_name': 'Role'},
),
]
diff --git a/apps/rbac/models/role.py b/apps/rbac/models/role.py
index e5a18b5e6..796b1ab63 100644
--- a/apps/rbac/models/role.py
+++ b/apps/rbac/models/role.py
@@ -43,7 +43,7 @@ class Role(JMSBaseModel):
class Meta:
unique_together = [('name', 'scope')]
- ordering = ('scope', 'name')
+ ordering = ('-builtin', 'scope', 'name')
verbose_name = _('Role')
def __str__(self):
From c3d01591e755988422587e21013f96af14f3895f Mon Sep 17 00:00:00 2001
From: Bai
Date: Thu, 2 Feb 2023 16:28:54 +0800
Subject: [PATCH 55/92] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=A7=92?=
=?UTF-8?q?=E8=89=B2=E5=88=97=E8=A1=A8viewset=E6=8E=92=E5=BA=8F=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/rbac/api/role.py | 4 ++--
apps/rbac/models/role.py | 1 -
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/apps/rbac/api/role.py b/apps/rbac/api/role.py
index 255299516..dd0220692 100644
--- a/apps/rbac/api/role.py
+++ b/apps/rbac/api/role.py
@@ -17,6 +17,7 @@ __all__ = [
class RoleViewSet(JMSModelViewSet):
queryset = Role.objects.all()
+ ordering = ('-builtin', 'scope', 'name')
serializer_classes = {
'default': RoleSerializer,
'users': RoleUserSerializer,
@@ -61,8 +62,7 @@ class RoleViewSet(JMSModelViewSet):
return super().perform_update(serializer)
def get_queryset(self):
- queryset = super().get_queryset() \
- .annotate(permissions_amount=Count('permissions'))
+ queryset = super().get_queryset().annotate(permissions_amount=Count('permissions'))
return queryset
@action(methods=['GET'], detail=True)
diff --git a/apps/rbac/models/role.py b/apps/rbac/models/role.py
index 796b1ab63..1eff7c15c 100644
--- a/apps/rbac/models/role.py
+++ b/apps/rbac/models/role.py
@@ -43,7 +43,6 @@ class Role(JMSBaseModel):
class Meta:
unique_together = [('name', 'scope')]
- ordering = ('-builtin', 'scope', 'name')
verbose_name = _('Role')
def __str__(self):
From 4c9d16b4c1e3f23fdfe7297293c7680a17829f03 Mon Sep 17 00:00:00 2001
From: Bai
Date: Thu, 2 Feb 2023 16:41:41 +0800
Subject: [PATCH 56/92] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=A7=92?=
=?UTF-8?q?=E8=89=B2=E5=88=97=E8=A1=A8viewset=E6=8E=92=E5=BA=8F=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/rbac/migrations/0012_alter_role_options.py | 17 -----------------
1 file changed, 17 deletions(-)
delete mode 100644 apps/rbac/migrations/0012_alter_role_options.py
diff --git a/apps/rbac/migrations/0012_alter_role_options.py b/apps/rbac/migrations/0012_alter_role_options.py
deleted file mode 100644
index 6c7ce1525..000000000
--- a/apps/rbac/migrations/0012_alter_role_options.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Generated by Django 3.2.16 on 2023-02-02 08:03
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('rbac', '0011_remove_redundant_permission'),
- ]
-
- operations = [
- migrations.AlterModelOptions(
- name='role',
- options={'ordering': ('-builtin', 'scope', 'name'), 'verbose_name': 'Role'},
- ),
- ]
From 1083f5f6f24c2473653e815f00d81a37eaf28ff2 Mon Sep 17 00:00:00 2001
From: Aaron3S
Date: Thu, 2 Feb 2023 15:57:06 +0800
Subject: [PATCH 57/92] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20task=20?=
=?UTF-8?q?=E7=9A=84=20verbose=5Fname?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/audits/tasks.py | 5 +++--
apps/authentication/tasks.py | 5 +++--
apps/common/utils/verify_code.py | 4 ++--
apps/perms/tasks.py | 5 +++--
apps/terminal/tasks.py | 13 +++++++------
apps/users/tasks.py | 18 +++++++++---------
6 files changed, 27 insertions(+), 23 deletions(-)
diff --git a/apps/audits/tasks.py b/apps/audits/tasks.py
index 5b58c5fd2..1dc507c25 100644
--- a/apps/audits/tasks.py
+++ b/apps/audits/tasks.py
@@ -9,6 +9,7 @@ from ops.celery.decorator import (
)
from .models import UserLoginLog, OperateLog, FTPLog
from common.utils import get_log_keep_day
+from django.utils.translation import gettext_lazy as _
def clean_login_log_period():
@@ -32,8 +33,8 @@ def clean_ftp_log_period():
FTPLog.objects.filter(date_start__lt=expired_day).delete()
-@register_as_period_task(interval=3600*24)
-@shared_task
+@register_as_period_task(interval=3600 * 24)
+@shared_task(verbose_name=_('Clean audits log'))
def clean_audits_log_period():
clean_login_log_period()
clean_operation_log_period()
diff --git a/apps/authentication/tasks.py b/apps/authentication/tasks.py
index 08472e931..978e4ace0 100644
--- a/apps/authentication/tasks.py
+++ b/apps/authentication/tasks.py
@@ -5,9 +5,10 @@ from celery import shared_task
from ops.celery.decorator import register_as_period_task
from django.contrib.sessions.models import Session
from django.utils import timezone
+from django.utils.translation import gettext_lazy as _
-@register_as_period_task(interval=3600*24)
-@shared_task
+@register_as_period_task(interval=3600 * 24)
+@shared_task(verbose_name=_('Clean expired session'))
def clean_django_sessions():
Session.objects.filter(expire_date__lt=timezone.now()).delete()
diff --git a/apps/common/utils/verify_code.py b/apps/common/utils/verify_code.py
index 85589de69..0dbf810fd 100644
--- a/apps/common/utils/verify_code.py
+++ b/apps/common/utils/verify_code.py
@@ -8,12 +8,12 @@ from common.sdk.sms.endpoint import SMS
from common.exceptions import JMSException
from common.utils.random import random_string
from common.utils import get_logger
-
+from django.utils.translation import gettext_lazy as _
logger = get_logger(__file__)
-@shared_task
+@shared_task(verbose_name=_('Send email'))
def send_async(sender):
sender.gen_and_send()
diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py
index 57b9f48bd..cd4e7bd86 100644
--- a/apps/perms/tasks.py
+++ b/apps/perms/tasks.py
@@ -19,12 +19,13 @@ from perms.notifications import (
PermedAssetsWillExpireUserMsg,
AssetPermsWillExpireForOrgAdminMsg,
)
+from django.utils.translation import gettext_lazy as _
logger = get_logger(__file__)
@register_as_period_task(interval=settings.PERM_EXPIRED_CHECK_PERIODIC)
-@shared_task()
+@shared_task(verbose_name=_('Check asset permission expired'))
@atomic()
@tmp_to_root_org()
def check_asset_permission_expired():
@@ -36,7 +37,7 @@ def check_asset_permission_expired():
@register_as_period_task(crontab=CRONTAB_AT_AM_TEN)
-@shared_task()
+@shared_task(verbose_name=_('Send asset permission expired notification'))
@atomic()
@tmp_to_root_org()
def check_asset_permission_will_expired():
diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py
index 4e67d1fc7..63b449320 100644
--- a/apps/terminal/tasks.py
+++ b/apps/terminal/tasks.py
@@ -21,13 +21,14 @@ from .models import (
Status, Session, Command, Task, AppletHostDeployment
)
from .utils import find_session_replay_local
+from django.utils.translation import gettext_lazy as _
CACHE_REFRESH_INTERVAL = 10
RUNNING = False
logger = get_task_logger(__name__)
-@shared_task
+@shared_task(verbose_name=_('Periodic delete terminal status'))
@register_as_period_task(interval=3600)
@after_app_ready_start
@after_app_shutdown_clean_periodic
@@ -36,7 +37,7 @@ def delete_terminal_status_period():
Status.objects.filter(date_created__lt=yesterday).delete()
-@shared_task
+@shared_task(verbose_name=_('Clean orphan session'))
@register_as_period_task(interval=600)
@after_app_ready_start
@after_app_shutdown_clean_periodic
@@ -55,7 +56,7 @@ def clean_orphan_session():
session.save()
-@shared_task
+@shared_task(verbose_name=_('Periodic clean expired session'))
@register_as_period_task(interval=3600 * 24)
@after_app_ready_start
@after_app_shutdown_clean_periodic
@@ -81,7 +82,7 @@ def clean_expired_session_period():
logger.info("Clean session replay done")
-@shared_task
+@shared_task(verbose_name=_('Upload session replay to external storage'))
def upload_session_replay_to_external_storage(session_id):
logger.info(f'Start upload session to external storage: {session_id}')
session = Session.objects.filter(id=session_id).first()
@@ -108,14 +109,14 @@ def upload_session_replay_to_external_storage(session_id):
return
-@shared_task
+@shared_task(verbose_name=_('Run applet host deployment'))
def run_applet_host_deployment(did):
with tmp_to_builtin_org(system=1):
deployment = AppletHostDeployment.objects.get(id=did)
deployment.start()
-@shared_task
+@shared_task(verbose_name=_('Install applet'))
def run_applet_host_deployment_install_applet(did, applet_id):
with tmp_to_builtin_org(system=1):
deployment = AppletHostDeployment.objects.get(id=did)
diff --git a/apps/users/tasks.py b/apps/users/tasks.py
index cddf0d2ec..2e19678ad 100644
--- a/apps/users/tasks.py
+++ b/apps/users/tasks.py
@@ -16,12 +16,12 @@ from .models import User
from users.notifications import UserExpirationReminderMsg
from settings.utils import LDAPServerUtil, LDAPImportUtil
from common.const.crontab import CRONTAB_AT_AM_TEN, CRONTAB_AT_PM_TWO
-
+from django.utils.translation import gettext_lazy as _
logger = get_logger(__file__)
-@shared_task
+@shared_task(verbose_name=_('Check password expired'))
def check_password_expired():
users = User.get_nature_users().filter(source=User.Source.local)
for user in users:
@@ -35,7 +35,7 @@ def check_password_expired():
PasswordExpirationReminderMsg(user).publish_async()
-@shared_task
+@shared_task(verbose_name=_('Periodic check password expired'))
@after_app_ready_start
def check_password_expired_periodic():
tasks = {
@@ -49,11 +49,11 @@ def check_password_expired_periodic():
create_or_update_celery_periodic_tasks(tasks)
-@shared_task
+@shared_task(verbose_name=_('Check user expired'))
def check_user_expired():
date_expired_lt = timezone.now() + timezone.timedelta(days=User.DATE_EXPIRED_WARNING_DAYS)
- users = User.get_nature_users()\
- .filter(source=User.Source.local)\
+ users = User.get_nature_users() \
+ .filter(source=User.Source.local) \
.filter(date_expired__lt=date_expired_lt)
for user in users:
@@ -66,7 +66,7 @@ def check_user_expired():
UserExpirationReminderMsg(user).publish_async()
-@shared_task
+@shared_task(verbose_name=_('Periodic check user expired'))
@after_app_ready_start
def check_user_expired_periodic():
tasks = {
@@ -80,7 +80,7 @@ def check_user_expired_periodic():
create_or_update_celery_periodic_tasks(tasks)
-@shared_task
+@shared_task(verbose_name=_('Import ldap user'))
def import_ldap_user():
logger.info("Start import ldap user task")
util_server = LDAPServerUtil()
@@ -101,7 +101,7 @@ def import_ldap_user():
logger.info('Imported {} users successfully'.format(len(users)))
-@shared_task
+@shared_task(verbose_name=_('Periodic import ldap user'))
@after_app_ready_start
def import_ldap_user_periodic():
if not settings.AUTH_LDAP:
From d0bd35d88c9e1b3c7a37febeef94cae2a18948df Mon Sep 17 00:00:00 2001
From: Bai
Date: Thu, 2 Feb 2023 17:05:05 +0800
Subject: [PATCH 58/92] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=B5=8B?=
=?UTF-8?q?=E8=AF=95ldap=20attr=20map=E6=8A=A5=E9=94=99=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/settings/serializers/auth/ldap.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/settings/serializers/auth/ldap.py b/apps/settings/serializers/auth/ldap.py
index a6cc22455..472627e8a 100644
--- a/apps/settings/serializers/auth/ldap.py
+++ b/apps/settings/serializers/auth/ldap.py
@@ -15,7 +15,7 @@ class LDAPTestConfigSerializer(serializers.Serializer):
AUTH_LDAP_BIND_PASSWORD = EncryptedField(required=False, allow_blank=True)
AUTH_LDAP_SEARCH_OU = serializers.CharField()
AUTH_LDAP_SEARCH_FILTER = serializers.CharField()
- AUTH_LDAP_USER_ATTR_MAP = serializers.CharField()
+ AUTH_LDAP_USER_ATTR_MAP = serializers.JSONField()
AUTH_LDAP_START_TLS = serializers.BooleanField(required=False)
AUTH_LDAP = serializers.BooleanField(required=False)
From 77486f07732b31fcdd78d418f1827d237d281875 Mon Sep 17 00:00:00 2001
From: ibuler
Date: Thu, 2 Feb 2023 18:09:31 +0800
Subject: [PATCH 59/92] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=94=A8?=
=?UTF-8?q?=E6=88=B7=E8=BF=87=E6=BB=A4=E8=A7=92=E8=89=B2=E7=9A=84=E9=94=99?=
=?UTF-8?q?=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/static/img/login_image.png | Bin 488955 -> 89631 bytes
apps/users/filters.py | 45 ++++++++++++++++++++------------
2 files changed, 29 insertions(+), 16 deletions(-)
diff --git a/apps/static/img/login_image.png b/apps/static/img/login_image.png
index 1cb86eb5f9b208a2ed0ed96a2020edb0edc8fa8d..8188757be026e7996b06384db0841e340ff07016 100644
GIT binary patch
literal 89631
zcmb?>WlS7Q)Gp2#nG)*$|x6V->*|U8?^&HjE$)#r5RxzwJr@sRQ3jV%}GVSTXO2Fw1%q1
ze&_d{?yer~+f22o<-`Wg^)&3S6m0+Pd%CEc9Zgsr6ZszU(xHphpZ&7*DPy=(%Ep9S
zTg5*&jX^?4JJ=I%zFZ*0#X31Ex}xwdSNMMK_w{JNg9^?F3JM5C0Ves;W8o-s-oES?
zm27nn5P85$+(piL)FR8uxrNv9l0Yl+MoTOHAR{v~A))@Cj1CHV?$eBTnj-Ok%>3`S
zfEPp9_X{A35tzG-7IO@Bjlg`B-`sLHLJsxYreU+QT5_lp^%_{in`+i6dTtT~R%8cW}f
z;qp*VIu(}yg`seVP57**@|kp7sh^wJ^n44i*PFvaBgSpwh(ru#3IV
zD$YhDhpP!*@e2H<|70P&m1%BS`ZRBD{jVzy-oLgw$vd-G`!X|>4OwJ-I}cI7wTW~N
zPEL*9%=PP#7?kn6KKlBAwut%JT>QYOU)xION}fQyA4pRo^<7#@sjj4zSpILZpL;U2
ze5Ov=$D+WIxIMH^A;IgyKC#m6V<{x+;OW_I@kc3R{T)4#e8n)E<`>8HwIK|RgRgv9
znVzhR#(2`LO{|B%{!A;SUdUm@mzI7^lLDQYqv@fAa~f{*+r@=Vtek$LtpvYlqJ1Ap
zlHdzbyk!qe4ELdn8DR=P;%^+ZScfhXSm;h)rbS+R8~!kGXsWZ~J<-O(K>N&sl-{bw
z*tO>>&=1w=!?)KGE>84^ftE=VhMebI?L_rOk-b5cr1Fr(Hjcd(#panJ3a#VXmrZh5
zft8?azPVn(TwkQ!OQ&<4Q9ApDX44V>L$H#t!*(J*)Bqj!@7#e}uPC
z>B9UcVRt>{M~f2-rJ}^xp>53x>>xR9{^!_lbVPCuHaG1ZjobW#C6sdaN$XmHTl~ro
z(j%sV*BMt}{D~QTw0-8cD!~`dK3Q1h9~+g`xtk?b)IB!~IwR?p8S9PxThGStY8P}O
z>Al}>15dZ+!&oC{KXaocDHlE`0k$Ywu4_$+&Sk@>j1ZF5){%C9c`8y#+bC>r9KD
zfl92n%y#8dKsUYN@!O>ewD%MY&ZEkhfi(XtTQHlH8=B(fb7H@jAFaiNs5rM?FnxvM
z9?!EK3Oelx1eP&iv&Ws4NYq90$(~jjG`3JbFwP!#NP-GA8ynG@Sl#RWY`#sny7hlf
zy7#ed!ZCwR^G#)u$Aqcbhd(t{U8w@CX#8?8o7k&$--d+ivilf-4u}GO
zJqe(FUs9ndyv*OVo{_i9MW<-~Fzf?Gvf^&q*QPa4sdPl+%ZYu)!+4FiQU@}&OT4Ld
zV<6UOuZabB`Ws7PmEVkh`PujvgxigOD7%@)m~d{!&P)~*T9nR2I(Tfsq8&)_`u;{g
z;4}!yWnl3>57DOs(Om4mUKy^#uOG(nPy>}_lVS3NK-P2eZt}>@a7Hzogv5tXHOl?`
zAGf3)H0-qhY!|3bjE~=|Dz;&+c>o?M|uHT(ZDICwd_`yttJNG4DegXHma!GIWB51O4!b)2tr|%jB$9
zuHs#>B$;DS>G=7B2+(ROIeGMpYPpEBN$68;M+=xttx^CwI*?A@jRRXS#FGRKE%}TD
zl@)Ry7JZnDN-|{0ImKm6&lM&s0f{+sRLG?f;GxChnoUQVzAM?^vSiAw>DnIeN?pwH
z{|F-dgnZ=0BgSFl&tCRY2APO%uX{NFF!0b$aq@X0O_|I)I%z5EH!;zIeyog
zLsM`Itb17qQa=5JG_*Zwm3uw3iR&kT7M?5}2JFV(VR=3D_F5O{VF0SwQ13SdKUFQc
z5~*$Uyj{jME7yG_z%$`4#T=;B4?H5z=e#8IR0HvJkzr6T1ehYlvSm#q*Bk>e`q*m)
z+L)&x;eSzG$q{7{?fSuqOvH5VoPg^XOiw+`8fKr}20X)0Vk5$Y4bfy*by*D*j0K*H
z39&k>07ip;xPdDN3PFngAsjQ%p8?tPQDi9pJvWws+j+$-%JY%QwO3lh^Qzyg8154G
zOAC+bAo=?8C|>=S@I^a=#=K?gh;}kFPSO}I$MpO>0qsU|Msk2}`(StDa!)+s6c1>n
z)&CzugxLEQryN99?ns0_=gd(hnl%9$QT_WHiov~IwRC&BD$xG*hO;~K#4>-;a9GrD
zKKu%EzYAY|cZ~RB*V|v?igh-n;j;_T9MzyRkF>cgV%^9dMDa|sNPC%kYLDZeX~*SX
z4p}qqUgdOAk_4wWjxCYYf$-D2!aVfbp&9iPT*m5MYNd(=8vhV^od0$hkUvCVGak%-
z;pv!tl^Y`6C)fWJvlv?w?n_Sf()okpRVzDE-~eQEXWxW)>FpTV?F~1|eiV0K5a#J`
zFuSSRQLJMbd7lFOZ)NK5{0qG^)<0`!GMlR>?j9JQ9_$(F7@DUvU@}yEAM_ayqFDU&
zvp&-_T;ssTw>zokVmfy_a?9_}bdm(WcXh{(YF0|~FaU$D0j!mstsO+6CC_&0DL6av
zWIeZ0B`6&d6ttrAF6okVki?D7fbn9k#_EjP$9m{Kb0x^dufs@RWl5R4i)vY-JZzl2
z=J9Qxt7e5)la}OCe2-aOOsT-wn9v7$h6LZO{N?`X^55I;itE0E?E@)Ijrsu!rERJd
zwArn`@7q2xj%1gJh`0GkCd!EJt)dURd#1lLnj<;RFYM~9gMP)*B|*@4c4xFxyn`z5Xerj)4<)Yk
zo$|`-ess$x){UDCEI*2xz1po&?mdbZBaVsItLzJll2}}fwXp|1!z_arEPanY@7acV
zmL72cMtd~Tz@Zd#?$qwKrozhDjO88VKV<@}*FG*N%H~aHCy-}$c^9*$dYe4(_stLM
zg{2Tk1^c>d++6myAsBd@MeJB9Chsfl5#Qi^UhMfn4?3qkbH22`c&lfaf~nr$`Eq-_
zYoyZbuBRCl-~gjy_ttvSIDdm;5A>W%JhsHzMOjU2P1Ew3*g89A9;1Rgx)%h
zTLl#^=u9~IEEGQ?7M*i!=B9O4m)CCm3-bJ3@oYCyY5MnNm=lgZ_6YMw&Xm5uKtKQn
zTA;-L_i(26!yiXRAwyh3&cXXJ{TFp9=_l
z6I&r7!=WIyJzJVK(=FRMp9c*m?RMNE(e7^j#rOwez|h>OKFhM?<>#n~4^(BR=j{)>
znqE6+8I5&2toJ=XyD%V@0TgP+B3t66xa0xiv*kMViF@YZ5cpJam;ppC5y;JOk-FHx
z^*JMf44i=*Lobz#OH8@Z*8GPEzgYA=s+^!Y{2RYJor5~6h`j1G7Up^eZLZLNXf7(;c8q?ZVr8t>%s2TrgVQ#oZ=M-y=_mh@c}v6z_C5$G1K7=#cNr3%WH|5Q!6>NChbJnV1Bo~gmIz3rD(gmE
z2dU*Y|IJg@LJa*iL^R@xGWM90wU>9ix12-Z@9C&IbkQcir0}X
zQGZaXl71*F69SAOM=0h)8TB|7=)Sb{JKF2x!+96#C!hgq#x;8QpHW;RR&CyGYhu#qq
z@Quv2nFu(t|9qQ$LoZEAsFvX8k7Imu`tT&Ck{!NUBGjuJKv(opFk3>BPBh=Mo{R_r
zc#kAH2&)D^h#wZ7o}Mm_jg1{!Sn=ZQ(4FjHk?*)`wh2p8jGEkua09BH2qItOb|y&g
zrjf$eK(auS>#k?@Mk3^IWYi+(({qD>j1=)ukFE@Zwl!Z|&kyY@kgE7mN)llgP~iqU
zM_alM9K7;b+!_i>2B$&-IBu*Qf~t6XP3Cj*Ku|XmoYcMgt1~*dCKwJR)=P%mBd{&j
z`jlZnnXOU$@2+W8+r09F7ZDk~7h6@5s!9e=o93x>D
z7;udEo1U`wsvHvXWy5VC1SS21v%kT}J$XMz*wmtb0JyT#hd+f{NhfDdE1uf#S%w*V
z($4n6Bp|1Q`yrnp0;r9to6Rhr!9Lu^Y~sjG&i=Es{A#UYga-%F@pY5j7}Z2noB%Ap
zwD_CusAuD{oY}yhkG=vWo3wkPD~Pq`fyMhtlX>1(RQyEldSrHcHq@t1iw;|Xbbe>#
z`x$2p=ywFhzL3*l%Vd{djLul@>M9Vq=or0yZ42==0-Te>Y-1{ZmHv^ATrnpDM;!f&
z8}wgK9N4GzTk466rN){EpKB-3nA+uq;nyC$>8v3TRRAxd(+!Q%LmK8Fv-wYV27bQ*|SUE@Wo`u)mRly8oO
zx8s6b3{AqO2#1h?OZVh=tk!LRWQ>jy2fVz7V;3jsm&9vmbCT~Kvocl98};?pq&Pn_
ze~e_#zr^4nrL-~}tTaIpB#($0UYlDGY)_;A4!SN}G4(-Pob`3xsf23tb^@Who;;HH
z(*5Nf^oBgWJG6z44rRZ9GKzkQsI$b9V&BmNQI86IUC9c##ee9
z#RTeTNcvE!b@tfrVl5wa$&qThK#91oQVpIhiaW8gJqpl96(usy6-pU=IeeM_5E$r*
zaR3YKNP3MFVBCItGX33H!C3oZ7KFO3iGx4)mIsCj-DeG%kx8+~F`0->OO`5|CGPx$KS;2N8_1=iLk9qse#^Sm-)E3ju2+$=+gD`4u0N%`*IUND2aLq*)u{UCN4
z^+B8rT`3Sbbz)W1gCY=qLoWWT3a{@IZxETu?DPH9K!j&4|JvssP@^jyRZSxo8fSe7
zjgS4}uBpdNx)v~yK%7rrqNOs$f7-3%-7VWpssdT{bgJd-cxa<$BEIg_Jv(gZSOCUFqv`An_k`E?XWv41Y`_#
z>>}&*S;>Y+&GUy+558z!(|;%Pa~){WN#{o--Y4K^?Db~(%U-txR*$3Ycz;y{Hj-=~{%MtY8W}ddp??##Zl_D#oZ89_zKi0ejh99j1~?f)e3nZ#~s-X51ZgoR=MMIIHHmt
z5M*N-v{v<9xNKvDC2uFYWOZbP6aU>ALK&jdHln@^#4g_O5NVGrVr?SzOD*tN|vk75LEAH1q
zG{ii<1H`8B>`xwj+43;A5_y~&BRz0>r=9vsg(tZh{FHjBDQ#+rb
z&{qj91KpMN8)<57S!HZbZ$=GK9aI_BMsrxTk%cp3@NDsj^ULbmk{g;Id45%?PO27s
zB*~6FeDECF&ZAYlBnQu;TrqedpMPzJzVJYuWW9c2T9tPgTnu9CE|5)G^2}2CJz5wB
zPrT_zROACvTF%k4kovZved(22w!IsdG|8u0u#pHO35~bpXW($gZscy`2Ib{CFHFa{
zqX4hcWvU_iRdw1vKSRHg-y@{6r0i}l&GPGww8*3^L3{
zY8Wq&o&A_%l+&ehH|a4-5OZ%uf7;uO4a~UzSz{AOmqk<|vWL#;|87uTeqYk)a&s91
zsPGZ#a)WAAn~-{~e*6$3Y9SdxQ~Hcraj0XQ@{gNt>H3wF
z&|r?bXdGIQ-8DnKNwLX$hm~Rdttuy6JT2VdAeY30wht~zKcs)mgjyTp*F;4`LCLlR
z3hBz;&@VsE>4$P0Y+N;%kW)3Xcg9JT(es333i5(Tf`nBh5Hc7|0MU4#Uqd@Cv`QSD
zCXM07atTV{O2)0%z7gCbedy}*9|X(RaV9VGWX?noxC2JzJ1w5|N+!`vqFBnY!*P2u
zLb%mjNP7agt~#FI|L!O5C4t-2)%fqIQu@-giMF0-PMtmvIJ>#%FMC-{GNnxoWa>ir
zq1GX>;wW$TtKzfd?Bn1*>l~GL<++QexI1~#TL7V4pT!cw8WB;S_b?ph69*!*pE8X}
zu_ru~&6Yln$1EK#hqmo(UwFpSRS>wuy!xZ1U}16$##itFJ_j|cmBZ9neqXEX-?$;b
zLeOl)-|)2qwRh#9{^K?p2VQz5ntdZ2g~faR)zuFeO)l==f=H_-(t_PcemYT=?rB9u
zlF1=^2h%qtN)^E1#O`J(YXRI)O9g7%ubcfj#}dsy<+D;Mb>#cK(Jsb)Tq-v>S?Ee$
z?tSetlgJszgLmWf)e|MxB)~$O{7Jajsr`c_V75rIa4FYLo7C73bXSha{{pp0YIQ5{
z@S=x0l97!VjH$5%OOMN5Z*xE1WWPMToX^>}nWRE``ZJ)?ROj?BpIk{~HZ$RIv!}tf@>{0Q!IFQ$4dp8S?WEcV-%@grh=1*Do&x
z!f#vfS^}no)bn6yEml;;T8S6mM!$bw)*5OluqDx;#_0K3NK-vgBH!PqsLQ!|ioeN)&~Y`8T#
zW(I2gT0^>Q<9M-cZMBgpABenmm|Zx`BouXjlq?)KyX2^`6P{;<5;(}_m%B|4U7pCg
zwvj$6TPG`p8~SG)M7Y=)@#r9BY7ZX-0Q3>|E|CC}h<%tx((AyR;pfl?UHwS8r)&cW;=NN!a|f!7Hef
z1+$n<*e;!^bjsarbb=|27cM)@1!McZoqDc3tKbZDv=hdH4brWi#OSPdyY8k?dwdA+
z09l1NYdlr94;x>jyV4!nwF|w1TzDe2I^I`$ZfCe~2A3s8Z^FK(G(1}^&7_?8d_7TY
z7*~ry3%=(lgO~x8Xp&x`dKC1
zpo^SecMa8IXtdxzsEzs8Sm2O={L0O93eY)Dd|pF`XJp&^YU)JSywW&yf&Cq{LDW?o
zhzZL>?9J@rJK|jBOL9}@q$$D+pOdeJ9u-4Ga2Di_NG3PESia5&^yW0ly}XM=o7y{q
zM26u>B{Mx29p=||hoK8kwE
z7Q+xZ;qjhNOeIi*(PcGydD~hfOI{Ww&ht;E_S&|9``X9HB+bnAcdwlNnT6;A2or$-
z@a5pDkKATe>qC)9cKYWf{ctOzT9L^59hSXBe(j9INc-0oQ&f)&>%nRy+Yf|A$IeX1
z)DXO{|2@zmHGGBWXMeNCEDfce%W>s*yxULzr6deEiJ+<`521f5mw7gGa2=+=sDoyQ4;D7`_+bX{8StaM89hTe=G$cPG2@3&U3_3WqYBd@dHzr
zVoRjno<;nn%uyKce5zKcmS=)Rb+0W`r2+^+JqyApyztD;=7;LKm07h|4sQ;uuHjqTOz=?UN#Y8@aD)LD-1=C4xJ)M|-*`C)7dZDM1>_C*X%-lS}qIeBz%
zm{fn6|J(+dOeD5LV3clh^8W$WcVRa}3l>ThRZkVoF7yXI;I?$1okV^coJXPrLTCSh
zFl9dm%jjgFfy1_!Y5V)1{7+b;P#|}y?kXQEYYkAJ=qRwl
z_Y^oh&zvLoH%Bm}66N!j^dtn;tR}6fU!f2x(|3n~Fo+Z?ffqy4B9E!J5@hd}SA!!0
zrgCev>0z)F~r1qZusW;0d=D-w=OG;kC|CIvY5h&HH33DMV3pf_wh8>OP{=
z$EIhp&U+hPJV_J+P)Hc*@VZ6r2Y+|Xa7lUxFYu{x2W48erdrQ;9lL$q_JK789%!<2mYs%HIO(ytxO@
zcjlX!=oc~@M7F-$mSSW`!0#~4qIzlvwZpaAE9oPZ6+n*bz9+sTny*T;pX&V*&wu7M
z(-l@c_vM0D+k6Sb52tb|J*Z6)L2$swsDP1{Bgnx95^LSW1q+VvZQsY;ZPgGc5fg1t
zxZ?f4zW^{ZG{hC2l-HaE@iy(sPsEB65k@{~)C@`&_6)%qHH{>V!V=B~m=uGOvPH9e
z#KT*)msP{eEGr2!qVT}m=YG@m
zIc{GzdeaJU-o#+$^M{)LW2?3qp6PPA?`0QQ@34PXCMl3q?=DO00J
z!ge6>f@{j|zq^g~SuXg)1Y!EEgoHed%W}rJDI?Z$^%9qCe)pL@vbIbf
zV&$^|R~Cc}wRdbjNYzJa&<+(-cHSG;M7|>}ksQj$jScZ+6pH%UGROo6I{NtliL`4?
z2B%*yu@~kG!}^52*4){7j9(9A5^8r}hy+v((m03>K-qPBds`SaB;Y=baU3RwX3>p6
z;Fx#lT%-iXZ9g(9YBNqjNJF(>!y4e@biv=28^67^$t>BHN}oo4GtO-JkZL20-4BTVFa_%S6H
z5;9`Kq;Wg4;6-{zvx%KT7y$|gfPT
zA7W}vVNpxv$V(i{Y#z2(F8}jE`TP^P>@{|}2>A{XH2i@$vRbkKo1jyycr(3TVx%*m
zvlJ^y6PJnZ5Di#J32h=G`~{3vlgeLK4-LB=@RFg7oFT39z)^av#4@ItH)(~tz&>%z
zW99^?`g8{o(qm$vdU|^L{rg8MVSV-^2E0*yw0gZBq_#{e4$d=euMf|y(zm5GzT6BP
z3N_c4p+kN8%iW1L{-JFx68dtxDIMu#m5I_e@THhVQuzKCWNpIiQh$W(HBwE3=*NPSK^B)k^b1muvf8&6Q%=r@Ev-g#e&0WCZjQ*ZxY`Hta
ziDyWkWD=c7GOhhiMO?cxxI-K}uA!Z>sf5v@IDl-g#n>^-{0ROh7uzMCln$!#0RPs}
z;&{|&R`vKA0@u{|1r^OBVc<)Zu^qm5p_S^*9`~=QG~uqrmKN^70?`qg{hExna)}l9
z7Ymzr6D%5+6gzxpuovm%@br0)!fBU&im#q5q?M$T$Evf>?qVY6b(?TV)MGt;E=LpV
zw&7c~{W6U{4S~1oqpkJ7+kmVANWY?YWvyxmBHEv*w+K-hC=o7k=i-o)$gMDx!3n~9
zVnIXd|L3&5F%~t4FtGYh2^B7Y@L;QguIzGjzdBDiedNSH)VR4k33&TdH^IE_pnB*d
z%8Yk)t-+hUS)>r$7k%+3DQ~NKZ#=ooh*7
zUowGjTP23$^6iCg&TBMK4mJ^NP%fkACyi7MNnYr1cd5Ui)0>e`Pu~(R9L_q%-;19C
zr0DOg7g>#}R+fZO>27N0r^JnKOMd*(Y`(pmoyqGTX&GM6O&dH1nV53C(NS4_w7B~^
z=fwfr;=V15Q4i{x9&9c9)TYs=d>e`@YJkiKX~-j*$+t2fAjcgr9pyUs7U153OUEQ@
zauWdIO}|z{=~wXJ(0;R?I7h6ZZ+3pcmtNQ~49WmP6$HvmLtiXUm(r+v>RD|8$bQFw
zztv%psLqt~q0Ijk>iRvo{
z)^i1MP>XSGnMPr6nwEi6*CK4>iN{~R2Cgy0sBNm0Rl~1R;Q@djr~y?VVvdJT#`jjT
z{%>L(N=J4QMH*UkX^6h*6vOf9KMd9S)O-7)zj{yOm+|-&0_LQKuv@53g^7qPPpr?hYeZgwUK0dWg+USX|58O_UTKFrt_}}r
z07P-5JhJVj$QW}10s=BNNEWxv$vPvMp=A;-pn1#r_2UtL@9WS0_yGnKu{~XVPdpeR
z{ORNOU(o?w7)wcvso)P-zsudYf6#|@4gC?vXh_))_P}rHASI$%{Cof}R5x2G^C(UK
z+~z(r0lt1gGE+%ep7>`+$aeuR_UX89bVTVcifkPD$s(Z$!!xRWxl%}CZ0-fsurS$!
z33wg;`$BCaUj_7o&Jl=xEas3s*;2qU{+;5P(D$phv8cfhX?UT_6?brT+0C+rG|s@$
zMpNEqEMaRBX3x++wf9N6=wyT?^!`}nTjtOrVOW7rQAu)7LNJR?q3fC6l<$f@e@FAx
z1Q9RqC!ZJGSHztsd(SbA+7dNB>f&B7=9$Z(1hV%at9d%qeIOg&Movgg`56L9HTQ0Z
z2hQcguT3y1=ad^zM+dcvXAFN2WD%kc;aRuzOWsCG{!`6&B5k8X4uO|_#mls#Vn>Fm
zts6b|S7tPb%f7vAZK>&Hi3SFYNBMbpH=gLJcvB0#6=J1f_m$~Rg~gRZSL1nwoW1{J
z?yv%$g`D83^e6z8M*&uU&~Gm4S|XGi-NT&veXP-Ib%TZ&`c0t>NUjF^{4UmyR%Wj$2J+o}tkfdIhm*u#QjMt}G{7gFAo)Jfti
zxSOWuO8xeXjg}L9YSc2O&)-m|8>;&19yQ3e
z2YnDL$^WhI7#YcIs7+*OGofC*Fd+_NsLQ3(1`C$aR~l11!g5(HZBrq3;Tw^f+k_9;
z<%UJJbdb!(-(DeY1N^6DBDa^Wjh|f6q@?8sLQUezYd&l&6<-P!l0(N(V^bOpzbx2w?4oqM<%VpN#dJ~5}0C1YaF>nYKG(p3Rv?nmUMH!a;3YEIWc=5
zTBPw+=ucWGR6}eM;&ZojgIgP}&8GS1_&xo}zTM2A95=WRYbl8h$3jy$gk+`(-Q(zz
zW@(12j$(hwxT#@#d(+M+iG&w)QDohn4wx;EKVMu<0^$28mxA&PIrv)J(TS{t@?JRd
z33rQGaCsecwrbPi7P%l9{Ci1XINoOxqZlj+s-K-%3iw`396o1*?SzzAl#E9Gjv=(m
zVcDK8yg#_l)pJQm<>(%}xAHV56YFp%BXHI!Ma72pw0qD0R=wUeC9y5#`~)JDi;JPf
zc6!y14Z9j)7w)VCjFP@{WK9ag1_v_=uBjm%j;9f*ykv1X1iWu`
zyDu!sIQh`M)#aos|zRpnAXl6Nv>ps15SQ3=e{DO~a96?w;3|hATL9)8`|<
z4OZN2{nLGhuT7`Tc=@0)z&K{K+(#!;$=B|c1*$f~y-j`}g0I&aSl$9o6Ybst&M|7)
z2O*M9LB%~9??Ge@q$UI4IDDGOz*yDvyu_Jb5hFdt{4xTDa7ya{7PamIy)XHHbAzbi
zEmTT}Rd05AReP@q?|X{orVYAs6^he?_713pGrDH0rz~lH!2aee_p*N>$aCu
zbil?zjZET83c(*EYt)K=gIR1Cj%uPQ!OlT>t$;7Je`_YM7s~UG+~}A+@N^CzIP$qW
zzR-9e=*?#hiG_q3ySI3iL9AMDxpTcIyyztMYSl==?+_Jp^$Ef;(&uNfH5l2dAo?Ni
zsfMV$Qv=q#(puyw|EsKedhs#KjFsdohu^`UIE<|;?^Q`06DKGBnn#~j?N%Pz9Bw4y
znNP;N%Y1mtPEk-h-&%Ye@kr&O9r^HKo|wx3u@??$)-wOY8-|&j9~6}yatQ8eOu;~L
zL<65O^}sBa^Bc-JHVX5;Sxd3}S2J^EmrtS^u$n~n$#&9G+rf6xOptFQ%}wk?Yji~!
z4u^rSt;qI=jgPYr9#cD8SP?FZGjQAX?RPG|6(CKqTffW8T4(p$^SQaxl_t>_HhNk{
zXUy-@6P}!Hn#k}77l_k4$i|h}sUvr#h!}u3ed!J1e#3+jGMQW&{`{LRo?1hTPBC3R
zjBIDXP0K`IRWsezE@&!&B+U=Oahmx4Swr0{vdmXlrCg}drQF0bJ9z)oEZY=|Z$xcI#blGj
zr^D*3?4h}qmX^kwj+5Qs00;H0^mAayr3=^f=X@fjF4fs#({pOZO0~m1j$Ab|v-d*o
zEss}@uMVnNGxW1^nlRa+(!7nh5j@+{Gkfr!qO#)jT$MJN_Rqo(u85#(ujA<>Gfoae
zmN6T{*jtTgNb{Tht4X=*%{+(DeDVBs`Wf;+E04YPKevnxKRalXgw^X!iMxK`GOKj5
zW)X(O89G-~-5S$MTY6Ez=9oiVR2G-oT5SV}qV2B4p6+&OWwuUhsfJ(0ooW%Pla7Jo
zm|wL$X_Fr;&4~Pj9${745D~rnoXCe3#1@#0un35Ji`}Nuw&8&9!(7q!gybDTEi@T}lwaas-c`k3WeldRTD!%Nw_qB*&9
z#j(xLBw^xIhT*0hqP@fmIhohZ+HBoUx*6LBd$XLo2ISo&XeQZK4N0VoXa6OvL}^+b
z4gyLXe-~vk5&`JCmVvjx5Uo?--?cKeva6o~dwZcARfJ1q&xY#nnXi|Cnc(}s}txCq#y(+MML-DK9m)H3gUI?+h+d(Y;hvpJ%8RD?gkiu`P)Exi5}a@=fl
zA-7FF(DU`jNq(~qB#R@>AHnhTI{sAWm>{bgh^Ro~_sW&t;(NY)6DbJj?pp4W_D!Ul
zuiVhWKgGg(k~rM?6n<-Z@W)pEeSb;W7ALz3hoK}5vE}+ArepGU7H4-nq36r&n;C$F
z6n;36$~^8|NbpY@1P!c4J@K1fI+tXyjKAjP-(`3oKj%)EUf>32bk#3EjHA;*wL)71
zk>-SXiwHs&>d&UPh1YMRYF$Lmyu5HYDnJ95EfD&|IFkyTXR4J|+JU5kzC>S%pt9%=
zSy(~(e02NA8*3TAP(n=`eAYi~;femVH!?kH|<_N1M_Rb-k&NHcL%y$K7Q9UPp6ts4XSyD&^O
zw{+hDrY8d@^G(B~U>}VK;DQ%=$(pvc9c$jEA3;t9+-(an0XT8V`+A#p>(l#PFXUHw
zG6wRcnDDv?IcG;Oc#$P8w}s^eJqefyCNiS^hqIZ7jkT2+*}Slwu|~uv5}BPbXYJPx
z1*!v38}0S&0TVO6JU}HX;f0T4*#7-IPuEm$MJC0$>H8Si`=#*>K#)B3)@f`f!}9(y
z6ccT-^!|C5atbB!?p!L!9!UK2Rk`}ZWF$YZCF5pXR`)FlG5n%t<4xYz#{y%bb@Y?#
zE>QrWS4SpSGRRE+V2w?%^*4Gkgos>`X!%RkkY2;Wp79)KWL12xZJ-g#TX#$l0zsLT
zHqw9sYYtWOpQX>fs1qazQv_6Ys7;McSvM_kkdM*Rmu@eHD)x6}xMUAw=S`q2-A8BX
zcR()MZX>XDx2;Kr%2<1y>R|{?nC?-l4ooj)2cPT?>9p^*O;qXg4h^NdkYQy&jeitr
zN+}(RjiHDAi;@s%u8wu9r=G62k(>`|6+%9bE_UY8*KmDyqCRqr+Rq}`{LWdh@jD5%
z(@4@_vN`1s
z;JD)>6Fz+aP6N|iOy>pt^f%;SQlEbQ%V&OCW^7;BeiZq>hb;(%fSx1F>hDGdP>uUG
ze;=AfTAjs*)@>bU9vHa4S3n3gEXqZ0iqGGxG;1qMVU<$b2Ld1q-%q#ED_>&oikQ1U
zHrvg>jC*v;*m2eo@;1%f+0rK&-|aT64hi}4vI5{BkZsVbRk9i;#5nb1GJC(|(q+#p
zRwuf0TJ){wN8k%i-+YIwWSn3yw_Ol07N-*c(5O3sl&gnTdGBV_s&J)WCnP#$c*g;rK~&IhkkF
z-t&C1Oa9$)BixNGneF4Fyp=CO9Fh@DA|6(Lqxx;0Q%}7s>OSysZF^G@67$H1AvL`t
z>W4DR=yBDf@O`S#=SKhrUN?HK|7YxckE-9n#VI-ZeWeVw6Baf$?Lc2
zrlQ^!0ZYin##EwwXgTC0@Qkf0_1s^7nR;}AkHOc-}u~~DHxg#|8pRC@ADl@DPSO#tO=-j(@U8mJeieH52Ik?q;O_hDwcIv_;kxA3YkVWZ7q~X;tK9nd;-q!w1{96=
zCeHY;It2$R^6or)Dq=8f9O)SWUqS`@@gQy9ZNvGdmxbrL*LBpYmFwxd*|ALZ2G_FL
zQ77iV=^?MTi&D51cAKP5k2cV)s9l0XbSDEb)XL>N8yJ{jnm{gw;p8vI!q7+agsQWf
zqZ%AN<=$sOfqqAN>UW@Li;whWS20(tE;f&;I&~~lev}{R=`8E>XU4WEA~VRAHi`q*
zVUlm~0+92Mx6lWVDXuDT9sFGHo;ZjCP^_!uYd83mi^GPgYbgr2rf*hrXQW)G+Y%14
z7iwroAFn9mV>_sB&5vWe=irl$iD!pbB##CQVMZBrR8(L$?>dp9Ijz8P-f`htB@FSW8YKiKRD-Gz4{OM
zbS5O}>W&qhM~GEy@cZ#*8Doh$U}=--t}Ml)<%47X2YZjF46YDnA6WGJZk+`c3amDx
ztwH8Yapk>8(>RWTG6;TbLoVnH)0TzqEj>OFA6QRpipi`8&~*5Sliw5*eD9+D&h7bN
zmK(df=p!(P-l;gel>F+TPZtw%BG6@6!`TKXKRWnCLfD5}Gc-|p^y*yo~F|;^|1pmv?I%+h~2CXA4s^eh&-&&qT
zwx5cI8@Iu9iLs*Vc@w(Ra!}V}$?@QpJ5xJn%sAERy$JztH{E4?lNKj5I5FDJCMi|u
zrsf^MJ5tiOE+F{6e6BFg2|Fm0V3z*U@^h-oEF_?E=-&Kz0!&8X_vwjK)p%U3`s=LN
zAu3%oa2tI^tbmZvsu=}yo<@!%h2Wstz#8r%RQ?(#`2D4vpH{|plMamQ^57nvJQ-)C
zO4_{b<*pj`AYIvJGo<8vq*3jCx_x~E8AmEh8Hhl8^3=ACoOime~&w3(t_
zJ6{6GK-{rq3d
zOQJZ+Q{$>|disO?^IrgqKNkRIxHC^?1*z1DkTfQ8rl?(k%98t3Nb1mE7AA;1P$X&6
zRj0Z5q_uF~bFOP?fDkjBNVb!|;1l5nnsL*1NOUpTNkz1jzd@A7lB2i1OqUMs?7=
z5Bu@fhlmf8ktN5khGrjI#u#|LGqwMv3WdGrGNeuQm(s2yUA5O+m&5hvZ;R9Ce^rjs
z_K8GLkgks&Hxfbsb;&oqLOdMfA+M=wqNeV@2_~YPzjUoM39yv#8T)KQ5DFzu$+x)n
ziSN`=ae(z4BxB9z5B|lC+;86)44aSLX9x4lJ0(a((m#p36Tn9WyUw>{rA}q=*WP&S;71d0@566*q-kW
z%&POgsv(^1DfbU0(z__EzbaBMk2Ieij482!`}d{mFljq`j=S_uvB1$5BVU8fF6#~e
z0xFpTG8m{rj3A7Gs_1xC(Et~((OZI+0v)QK37GFyd&%NfzuM#d%S{OZbb`VoWAViq
zsutk=H0xMv8_-~VSwMCt8Y#=f42
z#czv3XZJ0p#VwHwpNi#(s4E`b-lcjG6K(2S;@h{yKKV7DW#JHd{J29AVfBK0Hk!tm
zpuoweda{uHS|R`#@CqcnZHxR|$X>CKnCz>k|AG>ioaU%lV-jbbo2jTHLP#e#QaWpg
z3}nPZC;C4CfIxr0IT(%WjqvK5X%E!ZV!>UU%LuYs-Axb=96KNcd)mT4R_^YGL)?o9
z!yoxW_<#8O@E49p*(<#gLl#3dJGMT}TMB!j6qAww03ZNKL_t)z;7sgqdvz7$rXe#;
zwB9;!U4cljvrM_Z$?2K6fM`;Ksj)X6qBoym_y9uIU9F2NEC|Rd+-`)%0+XKC0a@9$
zU7zNy2MzDCw*J*1qIFvKOBv;Ih-yKaD}{fdRbjgYsr5d<$0&^$4)M$l%s7|?tI3=J
zVTrZB^C^Tt{8%v}5JD#NT{17m0)Ih_qhy2VkbEe3%sVnseiq|Jl0)vj*aE_H??ex05&zbiBrsjJ!uHXAvg^%Ji6sgzh?
z7949#h#(w{$b2{1%^`y92oCrTPYd`RCgfNU!i=bh94AAsevvtOKq<4H(mzLOUakq9
zmBexq!{``sg4;JJLEXj1giGw)DNMV^e4zOtb%lH+amT=Xtl@B&Wm!rUe>PJJA?rvr
z>B-i8eB6|&CU&YUxLU7I!n_!?U`8WhTFCto@c>>mkBF*CT5|o#Naq`
z%?^Y@$ebexUefci?JPlWfTLiS4vfsvgTPV5j|D##$jFgG2sTb9J{J4qUW%;!ad9_l
zz4wNVWW}OR0wQ9rDToBABa3?*-;69GKy(+`8BrQC_9mnT(4H5MSBM9O4*b!pt#-
z4>*QvQ50+N#*rgSay9E60C^e}Re!Qb4}9zM!@L-g58ZlmrLei-)@XQi;~desx@n5O
z#i_vKxEIch3n>w$M&%9
zX}B4LhQuQb4>J)$D(P6!_UiueA(s0H<1*b{=?(Xe*t}k0w={saWMKZ#|t?i5}WH
z?mXqTTUoZjYpSb_@RcA)c|{%s0D&u&RvP*@uP?veAW(4l*dZd*xv=Qdg_z+L
zCLi01V9~jk#aIZGVt%v=0&Yr=@^<}V^D3MTItv&HQbvSkbbH_tWDo%}LgT8yW1GZ=
z=6FnppQRBob4UP>*ou?G;O9C94?>S(A^peWlaCV1%dmuYc-nw0mG@A(^vgWfmk6mZ
zq*pfGvH>{@9<++vr}d{;wfos6D2%desK5
zsCgFiAuIk!k(Hym(t!{66dfD+Q!o*A7NtXn7~(jSx1w~MGXBLHKh!fv4EV?ED>sl5
z-JbjR_tXuf(PK@$;A{`#o2**hGLgy&fnCfSfe~hAkbg>>jdfc}BfT5*jm3L!QrENA%kDq86D+ed)|#S4DAWlSC)`NhJDcr97uSecjl
z9;ET9jTfY|c#Uw7Wb*BGHG}T^=m$Q3-E
z*IV%DPZ{^`39ALH8*3gl2LjshvNz<<2&t8>E)B?8@aXguIG7NRXGC2c+0akKkwKBU
z_%y_21yENcvA~2#^U*&&ZWT}jA$^q=9vh^y-Zc{JVnrZnkO(HNRth)w3B#N$SZ8MkT#xh=lmmgc-rTau8M<6Zq)TdG>i
zM=KJg883^s?*SJ&OAm8frYd6kNuDNQlBAibq51GInRo1`h51>9yME7zHxu8N
z7eM~2&Z;_)GvU#x^|__FtB4{8g$?mcSm>vv7UurhsI90V(DCJ_wB|ScHkoy+#E_at
zZxLGm>%U+nx%YNf@RVdeQ|Nl=tJF~ml-DDH{@%m_e
zx=?{~w0OEV_uiS=8EQWG{N`R&1dER_94Iiszquau1vnIt9QP-H2r0yelyl%Xi7kvv
z;#putYBrUQ@T&!hi}+Z}ygXF%y6%K}bBy?gR9vkrOASd;HPyf|JYD+!W}YS~O0fq0
zD~b>;J7}nkDTcGytG_1wH*{83Y4ReWD>bw$)s-hwXpA`==0(O1hI>(EoF@Z
zX}6o_)O;xJygb0$k&`|Z*U|atd)?8nS5#S$G${s|jD<mlE){picU1JspmsURV69Ky+rIbLk>0Xzt&5eG>`f+URBa@y47evxrUFYnMb
zm#z-NRXoU@ANo$$^Ew45a>SJ4{kfV1DWn+Kyuy-b66>{NLs;0sxl{lVv0EuTrm6o8
z6NcZTIQ~j(i|D&Lr3At!<~mrOg-)f5ka50cO(fiB%iDGA{A~gBTUn8pf=Awb;EM1J
z1=J-m4nY&bA<~e+EI}neAE;IM6a>tCNNlJwt9RY2vLO7icNfy-dA`5M!Q(<8mCb+$P(X{>=lD+7*?5aO37Q%l+Gt>DNZeenXGs*WjJltG{Yos`GHiA?S}vWDd3EVK
zTVl@T>!rNw5^(=*0rbY;!P?ZS{9&)WZnhk!Ay`DjvxF7l33iNKt+~D;U_QiLS`Lby
z86AVXMW^?(1@Om)s;e;Y7>nF>2E~j>2P+%t?eSBkj^SAmw?fcZTxTrRHC7A66lNMPJ8kR+5u(h=}*{v^h;)cbm^#{;dOLki}z6A+^){K@a$+T~a
z@l5Wro5u$NX2oi&A71W%2O_mPpJmf}i&G%*!&$bify0&n83~0YY{)pn2>K9?
z63h&WInd{5K2&esD-ZK(21KbgS{bP|SXLyR8TTdEStAMLI>uEshOkV&Gf(WovaNthl;jK*@)ByBdW!S-vn!8#?14Y05cliusWA0oV{G
zy)@7}$mA7$y0``Y2*;5hL_t86Rzz1AE{gEDkWL-fVZXn>EMg;bBWn!#mKA6z*@qgX
zequ;e*pT&m&aKxKSSUpDGVOVOOe
zh{JAzJ!H5yV6iKvu0B1U8PJh=5#wQ7kwWjmI6#h(>nQRgs&XiEj3LtNdl9ZpXJB5(
z%!U9)PP`J{xI5S6lARS<5mOIo><16RxMh>dGfu0B9jJxwa+ynq8WReRyPM5ucy+Ti
zx9Iw%^$rt<^;S!=hYpq3w<6$-EJ+~Je&{kQ9(Q#HkqBY68DT7^g-mtxp}<40#WJd^
zLEl@5nygclMq{)fK^g=p*EjgKgHP
zyR=CU`yTFQP)8-vjljrobagl1T{fq;cf;Ys{Ia4)b>R)SSdH1j+
zKb-PW1Vl0yVrv>J%Y;aY)!;e9rK+ynr-xn%
z=hwrtD=dO>%Twv5(i0`St~sP8^nqjvwOZ|cz{9pxWaJ?7YQJMmNPulpA#!$hx0?_X+SX;#%gw_={gL3-#jHLY##Q?>
zp;FI?!(h_I)_kqRp@4@aF=E+fg=JafrmGT&1c5W9(j-X(Kk>qp+H|~A3Q5yQ!N-L(=-j5$`$53t03z#fJRI)N%h_SAo)!7A6MuZi
zvSV0fIeBfJ-vC5dDdpNoP3z`Q{_FI@%JI>%>$$t-B>kU-*a$vr@duW628m7xDuo
z>}0mv49|v}-7i@5lJp|DbwtC5)NdAtBiG(Or!aNZ%4w>WL1euGL~8X`ONXh(AC^@F
z%B{(yd+97=4|jXNSrpOH$t;L=-WFc>7Z0b$`!BoQZoAzkFpGvR3JbpANf3gtLZuZ3
zk?*Ishr!_E$3h|$|xy`K`JM=5?!dkPS_zS+8l
zCSE^<8Inn@HdvAW#orr#>(2v-YCbY-w6w=pgotWB6c1vTDl9m1>kIzK;=Ic&c4b-X
z^QXth(}u;b`euzKc!Ltg!{hz#d@`BNplK&`UZX3HS8*&*LeT*als|w%{1lOrr0M3+
z>-78i6rn%~2gxyWAczkjk;CJsX{i+<&F8F$n^R@jX?J2`8n07;{0zBw%pz0M#X
z;^ogXm37E4ay&AKZx(dtp9DgT
zVJr$9z{X`0k_!apzOKkuS3w61R95%{AVL@LM+p28L@C1uu5iA2SoAu%YAnemN5!Ud
z4xtbcZr#)sx9wJ*70I`>+?zT$2-YYzYp^fzd4S#65VdcrcJSR$^**0yy%x7DI?Y
zArv8hwmaigQoO;6=%vKTf==4~#?>scLj(b>Z1yC?t-d6VyyTtyo0U#X_J=~WK&HFM
zZa7A+79clBv>d4F&}-q5C+ON2t|NfV2dZTc(SNk-YzW!;>e1zX%Ft!z1AkgiGk}NI
z>tn}_ArkDj6@X~l!|PKQ+bsH&vP`y1bv7NMuk@Tj)}aHFf~dHnHXVG4;}EBgKacT&
z5kf%%h3`JR^PM2EA+`7~M+qg8_i88$4t6KMPe6obMW!`YgvDZ6V$c)Q7nT0>uN;I<
zAd;j{^7-z3Ss3(94ivbrKNvt33m@eO;V`*UfV66_KrwuUDi%b19lvNr^Q}-biH>vV
zLMFXjibc@@4@>-7KApOu&bL5E2Z&q|+o5F#V32)DWodisbxF6$vhnG1m+%J}Y0JO?
zK#1XDcYFXy$m3WTH?bRs?izod=qtt{K6&%mah$~L_j*N6gg~T>5E9EGAlbimPVC-^
zSbmM-$>JA++qqV
z7g{%0Odv=QF{sIY)FmO>7p&I2EkpM>A0w5jv1&yY^oFcfx|A@<38i&s(kKl}gK>=_)q5
zDCBi-+kK4^2zkr)SYquNhnTy^W$OmUYr|h!%D1=?3$lTo0X%@^#q@%AUEhxxI-)iH
z2oQq#BMdQt6b%qdt$KC<{
zAT|Ww?zf*tr+EesS(<>w6pR^#-(Ves_gFN}i2pz5X=W0LoL3=9KA_mhv9kUs1LVW;
zS{xp&E4Lu1U1QA~s-VC
zWY#%CwtLIka}$E`f$#g@&vJ8J2OZRnF#%^vwiUYJ)6pN)o3{7p6^hYoDn}mY7$VuH
zA%A7o#iA8*+sLqV{iAw(x!vMR`mQ;Mh1wM_T$Wa-T}fnL*n_aRGiv6r0v~PBezdDm
ztS;ZfhRuh~kERvrbBvI~=i~Ag%7f?{6Y4;?!pc25PoM)p!q^9V;0qi8vZC;SxD|Z`
zkK-9W1Q;`FK17S|3mk%@fRv7>L)DD7G=q!YaWheHQ
zwOREVpFyhBmuDn1BFonDBF2WnC!&s{fF`L-nH!K2LOK%4*MwhfmazFEY{;fdB?oYO
z0eB#)uaMBTh3b9<)^giiZj$f0eNF?*6%f+rJ{?P(-Iy$Ay}6-#OqfHL+A{U%(1Rq)
zMG7550P8345H0};9b;gq=aQaKo*ZSG)`Mj#E
zjVD8R&BGK@hmVhsSeCo!azp=r&V&dCgcq8iif5NZd6U1P3z(0B%TP_=pyER6*rT+D
ztt)jRY(5efEB=B*^7;}xiP-L9Mj+IE=*BpTpeAuOe$pi}{+R+q_hL_i3(+U^fgi(o
z7G(69`*wdDdEPwZ$24vBLrw8ys+uIzz~v_)UR|?$cYpT2Zwaw1fkKL9(x7{e2TYT$
zLEU@S&-35nM(e&Y+P3
zCO@49q2>b#xy!nWdnqq9X*Y*>yIf{Pb9#*?);uxcDy9g5WTBZvxkRJ{0^XxLPUnF-
zp(6|ilf;^(Mz;+Mnt)F{{PGMl_zp*%sH!>|P4O8`?jVlUODxCATX2P32%!+_<3A1gyY(Hy;v4C4#A*Rk%6SL-N-8;SZtUA+-u$Ylf@1xP4FiZjtL
z0PG;ZI1_7o|8S8-Ao4E+w4T4ys#LGY^lt!cJWnQSP)u^@7_&_4nTN+OO}a;GpF119
z6DosY1s}@50uYg=nYfZ>$4cm<4fcy
zc^F^|CCoa(n1PtDT@*2vX^Btom09pl#&3=?P<{#U_%$$tM^IqIqFnoYpqpDUHF%T@
zK{YYnpDt7+>IxyTCagK1UlfBCbAQ@L5a~n#)izshJ|@hHV1F&mZ
z!6eI4h;VHP)m;BH-`iG``#{xpkW19@LTERaS2>HIS{5q@(F$Z7ORVuO;~u#UT1&@b85a+>K{205!boBg208$3Y(Dmb$KqI-vQW%3^h>gr1?GJh1
zW&|SOK?(}_BQ%ig#&Wej>#*wp03ZNKL_t(UOFshxr&^loyuGZ4L)A+4_)XnazWxGr
z(AkVMlaR^&|qe9XyL2*vyrBeZDOH>yb+9_8tNdW0M`AKY`4
zQKt^|$65aKw=eEClq9|(U(@Nni)<1hpRf_VgA^gf;a%%+`k
zh$u>VJ}`8pxc({)7(9r^QW-=5BS0aei46#V%EfHU2}Dd7-rq9}?VkRRq4=(2@utHd
zeeT2y1`xdjk$z9xzv?GUKG9c%L^>XnPX1C9Sl$Ei?_dS^fGC%|;T?y!e}m3CR{m7$
zNSB)YbBjvUQBVjvS?yldS7ngsDKj5cc-To|n$`+c{^l*{Vn2^Q@NEJU6bXYs9^Icp
zAj)+i7zOLxhC`(ck0I-f2WR50SC-(?Crn=fhl~h9MhQTKAOx}@r8w3f3J*5&VN2Ij
zH8q&myss3Ex!yLclkVrG~>
zlGNJ}2Tw?bw`g6t6=|VTZ%D04zl1k0?1w-j)qMVOnlLZxt(0Q5m)LwTbyYl2`baW6
zI~X?=d|(3U%4ZYE!(g@!!JYe%MUPWBQT_-uTJCf-EF{*DGYIMZ^;ffu+Yq1-MP)(@
zdhC5Ac3&SfhC_1PpNI}%`Bi^P;t3Tn`ra@|J2=kpTpq*2a}oA-j(K}
zF-fPBrm2tbe)rconpjhR`c5K}n-GS`b0#5#Na3mJy_
zy^7g9NUk8A(dsJ2R+4}q9@C$rQgstEyzqZ}`*$h`+;QLo+y~CuVm%R`LU<+kfF(wx
zWX_btF0AK*%VC6Y0hv&6Wsr(E^oE%)K!gavRMxYU>V1~x*vX32p5S(Cwsu!MfYZC>
zUc%FM;aFDJnl6+T6n(b5VmrdP@a3hc$AG~Rd;=pA5g`P~A|_|@jc!44h1Q?o)|FH0
z+VAk@LOvt~X^M1~BLA$361sY=qQ5+1QLY+aO|f9NKX>lRd;lQ2u4#++>W5`)lQfKQ
z#1>dUI&wx1@xhH<*M9Ug%_OqH~)#%4lulM4rzX=WjQ4&+X$;m+h*+h5-Z;Q!DsVm^a
zyY=?}2oGNQXsQFOcKc=-Uuza2$yO>ZkC;g%HlDD)Eij?z>O8BIq>zvMB-XU^`*OUN
zVb3>ll;Tzs2(Q8j;UMiej1o6-5fRrNj{%;LA;LGS2twvbyw6-Znvh916A_0!03q}F
z#u$t8s5#ZL9g6vs3;`{8v+Dh6l|)5D8xo@;n7?_YZd@8`N-(N
z%K-~Q$*eR%YZ4R%#QPJDr@B{c=>7FeOzt0KJS1ygse!avj-Xv0jMwrL*x0A7GdCYo
z9$g6wGChK>EL=^ySMc$0pM-(dS-i)gTZXfZ&Nz4x$Jng}_mm@&j3B-O`H0;Z9>@MT
zMisFQLxO{@Z5)Xp3lL!*on!>{gA=kn@&VkOklck75CONYSE~`b8->E!%HYl`p4$_h
zE^|~6%sm*+p>Ckc`_b$FN4>_2$wv~hQQH#%a_!Omx
zEV4@T=!)Vi3b5QUwPX4I0nCWQ9J<8FJV{VOCKf3O5YiO_LY4&x*=JFr1+;dA3nT@NNKEJ?alA*+p;Gw)Ca|<<%Y0on5D_R?&HO@Z}$6-)bb=u2tjg%
zxC`Z@IsnPXK$6R4lR$jdeKWk;E-76VBxKG3j=!0DfOs%AD89lg06=ErbhB9tII<;n
zQLX+khPufuYCUY>AB?8i-dq%cmGkLW9Plc*T*3Nf@fw^l_xvj
z)2kYhm)=W9{Z%NsI;|TvIT;H9;Xlg;&J~7npqyy`6RgPZI~7E(d}KB~54U)xxm~+>
zNRzEPuY-8YtBHK{O_KxiD0GC}M@#Ey)t)%s0pPM;z*_~s6g&`Ig
z5Cp{c1Gq(-&0LDH@XXA0l%x}p$D&%ugjGW@9)kz0s&A1I5RvcS#1V$yUYRe-b>IK?baViAO$I(PSh>=H0KBkntdVD$1aN&Io9}j$B
zIt^O!s(XVE2jZ&~Zg@vJ(#(7$91g+*f~&C)0uuPM8IJmG3l5}VuTunM$^nrLI<;gO
znGL~vw?xRZ(y5bM|LW_?dUmNkxKlPyDM(2+1c+@r)LjM&u;{2)#p#E~-Va?}jSr#=
zlMjAt*eqF2h+vp?V@>_XN93XX%ZI)4k-?^%;?3WQPG03aNbzhb{?TC}=)lKEImDvy
z%BSx4wu%oLlS(e93%bTl6nFepO;46(+lw<35c1}d`C#!?OgG@e#qbI);KTEh5G6zg
zWS%AwD2PRPFhsaR&pe2M$=>;KzMsz>PRKPMFBFZv#cS~L)zfoN;r*-r6Dibz=x~e8
z0k&Zd`gG9eqNBF_?o01+r1t;2cyn5#eh?y*=HN#n(mNnTl91|m{drHyeZzdTWvfx~
z9`XWhIhCbVTY4e98sm{jz=hdoZ*UhmEV3Ufwm7afz-kj97kUHRsQJsi8jwZ^qH&gM
zgD4Y5OGLAFv_}8TW>}P
zmyqWwAc)B5&F(Dk=9Hn7`wAlJ2>=Bd4!)S=AQrj`Hvjdh*MF^ZJ1GbVI|fnQrJDgh
zByHVT9rRgG%6%icYDx*Ll35pZ!n$Vn!pS+!CYleSA5290rfkoz%NAR1(yj}z5D+dQ
z43Um^-dm~&Ysni*$blpoC7cd-Oz~B~{0EZ{d`*nPO(O?{1SHwAQlZ6yEDi@65WEv4
z_7ULNlZISb>-)9X*jx1>6#5ZMBZP%wx*Tg&JV>*-1M|^m
zfG`c=pKybIJ%T-FCmyIAh}dI79^@n_2@_o5F)63894BIQWZoR*!xixrCU*mft^$g#
za5$X(d#4g0R7nQ`Sx+qs@vz9H8+k26G^b!w7ER*^&OiIb
zj(4wOQNpz?oyT4T1+K>0-Y>%?yz=v
zzL~=z1zINKV@iDB2DnNndzWvDYsW<;K0gp!vMi2@?g+|
zpYep&tGXEll}d=P>9RX7y7a?-n&Xkbmn395F(3Cnb@*x>y_&-k
z0TxRjr&)Y;x(G88hEy2o9LS7Vn8zYQfDNEyg#KLQc6sfb!YeP}|GFL0k3tR$2*Dkp
z_K-{nh=^g4OP?6|P)J5erXRQ+#p$vg4%eqb=VA>W%&m(a#`pHO9~QLa2vc>-#SxQO
zRQ?#!k9v>m!0b8lp}Vg%D0R5CQHwoxBEv=vF>3i3t}wm={XjluH@{*1gAe8YTRa}r
zY{*We+U;JumkacRRX)Te%txN>?VA&`KRxj1O7E0C`kg3&wAo=fo%ABp!4xFKcVVLG
z2duAK*o)i+v`CJv)xOZG!llJAg@SPfyJ4T<0<;9*f5i#~K$Zeky?
zKo+X>{9~TW5Xpu##d>qK{$)|?{xqC^HonN?nTAzqTHi?`e_rr$%;I1JJ|pZQw7R#MgEm3%m3!%kCP&;2JcEK;;mFFR`=~ni0oFLEI!6Ugzz{o
zQ9E6%?cTT5I?|R&Vl7Vgghtl;jq*o
zcdv%S{vlSAgl+r;$1RxYo=1o<6T&>Kkv!&s2q833qtl_L)N@Fh0U;@(HJ`uApGa-F
zYm+}7YY3B!WrOgo9l69j}@G
z5PVoa8O8EvsrB5u5|ngGa7aFtEYT$mGAJ#9#>|IeJSG#KLl-6lzqvT7e?b9Wjq)HW
zAhH3;n@TipL<%*M#UV!o#K?Aa#JCF(0!HLuKBlZLk>t5!llh?7l@K3nn6xifDN>#=
zj1QTwdl=|+!O7CyLV&mmA|(sLoUD=wd2Lo9mBitdOa0CQ@iBFmrnGM}+xE8yFe>=|
zJ|AfgSZX`wUo^k^%i`v?cZW9C>+KE3CPd+X8}E^@+n>*eG!gcrM6hKooV;k~cWKIf
zt9R9GLVC3|Sqzdv!?yOv{qsaU#<}_^B3a%AzI`S<_#T9AueQb{Hz24~y4yG?!oB2P
zWf9-_0*K(?#S!KUeLsxOshKx^INW?t?wq%pCWkn#6k)X~l7%KjpL$as?sQ3`yQNV<
zWI>Kxpz}k-dcVF(-65|}*8rh1U+r4W31uu&tYY4urPK33f$r`0uy?)7F$_1SaD86=
zSYDpT<8LeH8W+1#Zr)ys@hrPYEl^C{*;zOMEyb>!5P7D!j9j1D9&-3s)^i4|DTm4&f
zh`pY3@5&JKCwW}ZU4nlF@}tU!O
z@Q~7k^f?*f=-9K^e?V5GzirhSkYZU5UzO8m?V+5H)9ECw4GZIKcbBY324e(Q6vkPa
zB;vOS5Zvshu}NML+sNNi}g-XG920JEz~A>Cm=+m=!wgCfMsawQB88W+p0hya9ydk`>04qDe*?U}=XK`n-fIDA&$
z$A>BAeYR~OL6`-JUEVQ-gplLmRwtHDbcGI_=Wze@%2Y@xlJ!#?f=#(K@vL1*d|0Io
zB1z<<_@F?nX*U%VTFt|-WlNPt%Oj819gpEUrtBc+LtPjOd^k?JLU&dU3(uEl2c*fu
z`6#u1$B+MqHbdzOt5@i(
zp&j7Bh0p!IShkkL-*Y?|AhDStA{!7RT6cS1y23%qX($cjRZU1+gn2hHh;)N#5P9S^
z$0LWtxYt!a;Mpcm4@8JGE$cQNLeqTB@@cJ#kNT3uIA(<)MGe+3jc&yAdACSc!G0eC
zarC6;!yz@zZ8?38_QNsPM}w{)coD*K0-c7w0?RvVu_!9x?>>N!4@QKn2iG4&NUbU9
zDe|KxS6Gzu4hDTieAGfBNsuFc*^4!|8aE@1?;H33?YG}NWDTe|ws_$;idgN6133`m^$AbH{UO*QXEF4NUe)>HrO?)D{AQJZQUqv+eJ~8`b+iKW5mWRk
zDeQ;%hJ2)Z-eRE7+8Z>{LFnsD*4x8fL5TK#mGF1U@v+{OqB^d*TD?*`h=fOx2IA)IRl=}0Zvibo-aemWG)h^K{*Wb$1AIIlr&trl#sm2w
z?YaYPXW&ow4iI9!ds8k4qxG?fU}BIvSKPfSB?{}xO-c=gxm4!{#OC5-T5CRJ5yE7M
zOLtAaqgHGy)S_A3s!{0jKJ@;JvJV|+!^ILJr!ccEJ499xkvJLllQq@5N|4M78zNyM
zBE0Q(7-d;Br;DLC6_ww59st#IUu$~*NtfuL=R*;~2)X=S8QS}F6C&Sufd~$#FqF(k
zV=ur%liqmlvyQ@O=J%B%#ln7o
z8wO4mU+c|WEVl66PQcL$nEk+yC=Rml#0fz}`dYFIE4y;|EX6t(D+K!y1628-#$3o0
zBAp>J{&X+-SMX#c{wGh25WZihd6?YQ+&YMlxnUev^$S6Fx3XegUEXHZwwMbCY)eIK
zYWjnBqnNgN8RvX#h>w_p=RxSg@7uFDAl7>|<*MFQ3$LN<_Re^S*j1+^fzp^ORPyNq
zRju<>`k-rr$GW^U$d7U9Mxc;~HNN!oMV<_kZakYstvUmO*>;We$4kFoSP(W{6c2Q?
zHbhArY=plBfWYrAIP^6;lM#_u9jJJqJ_kPH2>r6cg19V_rOW%Nm;o6x>oI3zfY@_S
zXgvrLqO6I2gAKvNVY-KZv?@NVn-}s}jjq-04wr?gBdbz}zc4|eVK|t*Fx3ttsznhji1p8SJZ0}80usRcc1VzrAe@iyFS~9&L%8@4{Bl!n
z4H+#7kq^N`x-X&8Y09M!Dy|mk4+?nvSg+UrTY3^5(@L$PlnR+VU#OsWsZpCPyAdKu
z*q#N$B7`N16fp!$c;IbYC=l?k;Mn4l16-36g(pzv0U)uV8y-|zvNDZeLE3
zLf@eJhy$M$U7-CCK9(*+!q*#ugZ0Vd+YqREiWCR}!d6T@{QXtkfqPR%JwL{m&o`~q
zv2F_Fs@L<~FNT5n4|ld{?>;|I485=*F|A@DK>X?bmu`2rs~vd#F6N`93X!|_7z`)j
zF;Fc?r?J$eS7|C#bjV=$2zW3+z{~RLBfR-AliY})hc!V!umFqTU>c-r1vrsva3{4%
ziIoWy!O~u&5HI(!nEgl&xJe32NCLDTgh%_-&JPqL81f=UM0{75hw;L%IRHGe$e(d9
zN*Wh0Eyfj}#sv7dcbNg1RT;GR!9kuAvU&4fJQS}5yHr?FK#gYj+8Q4iTd6dDM_G>2d#s1e`=WDI5u5Vb6w!&J^|xm%9+F5-hD$mCNHAb)5Z
zF~_goR{b|6u*3!35ds2Bga!g+PKBuqk$AlYi~!NZ?Y32CQZ8d%BZ@1zn2n3|ct#X>
z5JP0Kzyb?`1k&ZrQ^+7qy1$=Vt=Y%B4Uvx^YyM2a-gEMv{tq|2euzkQH2r
zC}H<3C?tuFxHtmSAnUcPblX?&4SeK`8@iz>_cAt0Y(}Id`G1%X$sewr2023@7AUNK
z+h`}?@h#;Qo$)5jQCWkkYL^y-}11qR~IkVwT4mv(KDiSm{;Hfu8?Gv&uiKE1nfat
zt$?de%vkRY2zi0AR*j&M=`6pxH(_+#_`bh6K4|w$%w4`vlKmUz8@+qxa^9an{T9a-dFlM&uk9}^U&eJwEubzuGqg#b<`~(qZq$n36F#ZDYKv75r
zM-mk=G+yb4@UUP(Id$vY{Qw@NiW#?Zm0#B=sa);|(Pm@>>IxCU5iEj+0LK)sO-5m1
zkxs1hARNSMgbR;Tgv27~*}Xm7*7I__UYQ|Y%%Q6?cX+CLwUrU$Wq|DVnnNI?VIib!
z!z0BW%78FFn$3*HqHi||9s(a-zpiuW%A-GU9z=MMhbM`UpHRq2E^+=0n!gNrVrRr{
zoj=<3j)Fcg{SSMwOr3R^7b5f)MiRZ>>vUe|bUHnw)x>~FBJe>&_!7L}=|bM#LXP`W
z`XF4CbhuYLlG`8x;>y-n8Jq52>`6C+SoBWdfh=fSHoMDd~`l!p6+DFxp
z-An<5zR?KaJOoXcV4dL)$RA3H^u#Y2Ax7QeQz3}_miLrE$lslGk~_(B6}#zSbD$}c
z7?}nVneGbo6B@f0;N_?f*yspk;6#QFo}M*!R1OvcOaN=}t4m0aM-|Gpbc&F|yd}NCQ>tMFC(p3%&~2=>7z}
zxj(=nvJiJSz>vGmp4syRDx9nw2)1=2n78(WmZelvIl`?85Ke(GJoW?+cv?e#cR4p1q@E4$%HY*w
zIQ;ya%_vrF0wmP*f%u?BI_%E}@|O`mBk%kc0Yr1i2dOOD@p*NcT+7UUM5o}N{^fK<
zH1-i8e%Dtd0z2LXUZ(o}JGnaIqz{(-MW7?3xiD>*Swe3dn@NB4-0zQyrSs2%gerS~
zh7Z%3mkfm^?YV|WiA{f=$#&cPXJ6~=i;m`#-R0ON9s<232z2y-IAVgD$
znh~e;RXc-^ItQJV
zSG)X1jQ|;-?daACA_E_W5)GzT6M>P=kPCe9kYi|vnq#xzV!6aIi;st0G@@`;w)Z_M
zYH`k1n_#&}A&3}xkdjfcXrbc_i)i*Jsi$*MEZ1yJubi1&EzJk2t8WfpO`uSb6NCyI
zQ{oWB2qT0n2x=@}wT1X3v7nb?fuj{Y6k1-56NaAjA@jeWFgIRoSKfW9$YiBz6g4^!
zAX`L-f(C+vF6dM!x6=khmFTkfpd(lWFrqY;2@k-BuXE?tpcC<94;-LsJz<{ELWtF2
zc-g4G_c}d&N7XvB08L1Are-|?fS{my9MD>K01M(nFi0&|lu=kIm6iu@J|PbS!+NQi
z7P`%2jO_WHBr=ejUh%GB8|9Tv%HR2!dc~pyV!9A3zFV>hq>=-X62n4&{fnRA<5O7>7Fn^{eK6nw?;w|7vBsn(Ls&GxL15%<
z0r>deU(^=3-g{;vx{6>)c_lnLhOoBUfoKo1hKdpz7>6ZSZ9BY6wIgbU0ND@mNnqq~
z7~qUSFdvPe0T1yJ`sqO?oJ(&HfIxzl3}Q7~sfi_0WT_tY^TFQtm(kIf6ju`_x!X=sd*f#$^Ubl-YA0<=W_xzz0qL3+r5!Lp3=I>ZNUNo|9-46{rR~ODW#j
zGLymrt7LvEWg=K9B1xNb`f|pD*w9G1kn>4GuXCI#`#}Qhn-~PcSER1SY(da~(BljK
zmWR@#OB1310vLIOaeP}YSX2`hseRB{t>Za0&q8T#ym-a*ppaK8y!$3oAa(}i6#`iM
zSBa0cbRmooD&vh~9oH(y6#A*7dN(JDF4xBiXbUU#Ssk&12TN5@^vm?2Rfq?wuN
zlcJEPvlR08i?_2TjrKo--)HoS{5%DK42Pt%(0jylvh0&u&M@r!6h*(&d;Sf7OlXE`
zQ<*N;vGY8Jqmx8Yf$Eebx0W|bE)*)>-Q8wFR9|`qAr(y`6-^=x4^~$4MLv7j63cb7
z=|WOh!hC3K==|XHD8pCC9~i?Tb;T0vYrGK%@s&pxPa#XJMmSUeiMxxHK_1S(w4IAm
z;>s3=%ZP$NP(Y)B07qkfWr~r|D{f^OLwmh7xC;mVTex6>^-RcvF`ukbT16p
z7T>1iWKp~xPjQ|@45Gi85(p4NIbIEkupWqqHXwuuUs`E#GIewpxeq?ZX83suH4!l@
z31X&VtrX#bR^+jXeprQDd2=4tftPj0g#7(%mtRJyjLKaLB2uz2@tnR2KoAi>7=8?4
zkDS6IOIw$ZT(zHC?QiwtX<#B_NW%a)MS!D%>ZkU7YXnQeOIo(v&(2QWUj5?oY>%QF
zejH)M({m%li;mG{_~D%iLQZ#@+;-PY2Rm500)l`f)Th|j@yBPP5m9syAu9?%Civ`V
z3(|C|bwbXQOHU%pwAWV6*!J8xW4ssS4r{@YjjdPt-%|yQdlOF%rAZN>AHb
zuXNzvw-yP$mg%}(Vn7DyXT=}DhkAYfs^4<9kA&wN<`p2}NC8qcawRw(K(n;dK)tV}
zcjej7v-085y;(o#He6N%5zduo#zJHnUE)#ThlYcxupdu3SqFF=L4<=39esS$jq51Xl^o?pm#QIp(09@oniHjg6MCbm*a`-;Agy4MnR58LO
z5T^q2PIp}+6g3clLnAVwhhSt1h)6u=72Y`??utldk~5r!c8%ysv*
zU0b*EnsuH&%%af#&^S&J5WJ$`BmRJIt)DE*1v!_*)#*v34$MDWB)^zyT
z>D?+oezADO{^LK7fL!QYd3L1GyR!b(ktjr>VLv!|EX~PX&V>Awz*oYTA}mo|6I2iXr{Kt9mF661tENULK(q{_jG>tBI`6;u@>
znh)(W)obWgUuI|NU&0%kAYF_~^;bz^8}WcS+YO=p?RJkxwgpa79TRtbFEU
z(D7r6Ka!Up7rPxeMD6aZ+?tRa!_X*RVU|1|PG4ZqbC!$q!wa;wE>R!-MF8Ttvs2!Q
zb`zQM6%Ll;Q+WqUKW?SaJ}oD=?X_h?+Yk|f&?nsB{z-KZQ3TP9^w$tV0wU;WY27#w
z#-dEGa_NUih>;-0O;aTU2z2opL?Be>K|%P*E#7sE?zZvD3L(em3u8c7Le>&u@%X^=
z9|$^NeN3M|tHx7nKCA^{Jd`(fkD1__=`-OfVf~y9!7#*10sY;-l*^$q+~f5)9XV%v
zU)#7IRyXyVQ8nV)Ih&El0D=LCpDiy{=@mXxw;RrGZr*6x9OW2v6-j-|OX`3?&Gr-q
z;qlz>>@xl8OYMhe!t*OJ4{~Tnr0vk@-Ga{WdL|H&rsOV{ZZgJ-}#dl*x
zG$=SO$Z|~h&{6t2S3qA#`!Sgx1RxrZ|A2V_X*tx2!?H-UQm-6$$Tq7swZ#EQU_ub6
zJ3<%Zz2>8C8Wro$C%r633y8`l5Sxb(oTuZ_D0M{RM%sXwlb1p+7gV*eobR)%6?Fz%
z_2~Ek2I$%A*%|f|@j485+}z&0&XvU&YSmrT^|>inK7Q1845ikO_b+GAa|vG04ok^+
zU7}IQV`4wLZHivul3Kbo{(>^1Hu``zX%5#@8fLF};yJoZW2pTap#*ot)Q
ziYcxY-Z#`)V;jQT(ut08$@C~fBbG#jVvztDp-u==B|#gq$hRrl6tA@)!okv6-Wb#u
zX!jW55oPKt&amU68N?>T#pc8MS7<_-4_rp9QTU4v5!F8{g4+oT^g>;d|y0fwk
z_#3W#IPR5ku2R#znrPT$Yk$!+*Pr;dS@D~$KYbUgwmTD)CNz^
zLcwLG>__y4Mg$p&f*1=Qu|L?}Z#zB}KMw)9D6q!qCq$&+(X{zpfu;P2kMKwBi2qGY
z@zP!_Pc|%Szv}AhOavm$hbRzw$0{JFEC^RVdOEW!{VO*B0nh65YxA|)Rw|W;m8=e8
z1tdd6)D`p2zw2p#qW+_m-I0FJhpGXRBEuM0(!Q1H67dHpH5B!*?W^>(C>jaiNupOl
zKx!q(!h$yv&K*Q@XdoGSYo#H?UnS~mhuu`TJ1NfRf7!vZCBiDFuoK`?Ma7)3J)r*S)ro*jeuHYlD$aLoJo>RqFPmPQ(|jF9AJbc;^TL^=fJZ
zbhxS(knrfJMi`!y_eDZ(kPYivC_2i&N$rLg2Oqy}HOc9Zx~0M^;bhsn3`ArLmUpBO
za(7Tl*A_%qKGFj|z6~Cby4vqP_nQp@QmGAA>|iBGM}lxb5E7WJu|u#klT65uHt1Nz
z6FhW7bhH-X4f|HeM;Ym0JpL&`75yFkV&QCQM1Yd(fZ9g_yOYo{6?#Eo9O!neri+I<
zQ29F#}2`CtR0_=uQ~n&Cs8|4QwrUUs3CwcS4f9wSIyjQ|g6K3+gQZnxw!
zzD!q87++)8m3qKUp&UTb1NcyH6VZJQ0eJ*KmZcM-zTIvcHgKXK!->8T(qg#b{j8XV
zzd+JAAAI~uWaJmwD`%Y0^N#nLc)94j;N?3^yrXr(T%2x1f66awi3dAa->J-ALE1_M
zADg|p2oQzHS7!Q4kdHkY5nn782%ujplM?R@>;i;90DiUsh>yjvpSEN?EH)vuGN-7V
zU>LZ4C0RJS0xd9#h&&=Ej(mw(lM_7pL0;TPZ!Q#Uq)E0i>nLNl1LdFuf(4K!+eEl3
z&&Gi0T-NDvK~2bZ-|No0FOat~c$j#+{k^HxHhSrK8zyQaf0bq2Q$mRN0hY~)4+BV0
z)qv!$ZVSiEVLDJfV9)u<&4$3pjLdrJs2KIxApO@XL4Ma{;@RJ?iRu3jXP;>bjyCH{J0xM!w
zNWLPq9CZO4P6bi)pV%525OA`#yHR%*zNmkGq`a&3Xf5Tc
zyQ8oB+QXj1k1neDL@2}%F&o%<6@Yv7!H!?me^72OY%K9}fN)i?%OmE>cC=a52UnaI
z&nXGbF4C0CUBBO$b9og+NdFk?admaPR45!P8;=x3zlmc)4o9XaOsBT1JRDR$|F(=I
zDmDi`Iy-powy02BsqcE76Uu2G$<<)W00c0s!6rVYjM>zQSo>d
zm|&!ereoqZFp3h0$Y52Qytx+%sj!1pR}PkrMb3_#!y3bJI}BUBHCvC-Qb_cKy`A#g
zc0E4W%VZTrxRe^zd?I5+0_-Y@;T-}Om2_$Vi25>o35dZ11$??-MV9J<4N|b!yq&iq
zzY}3R%wj>^{ObP!hCMkfm}S-fgMiYV1!
zSLXbew<}>z8(o4ic4EgM1O^l&1PF18AaF3{60oe`|Np6-<2wapv9q;geQdVsy^5oz
zr)PS4jLU>fBnXmw{fmxoit*r3x^?-nPT`@aQ^^@T6v47t;lt7B6&7-Yz|r)gM9oK(
zM=LV%p&-&^8@xPvLL+q2<%o})vUd;rHo&7L;4#fics#Z!x_TfV+x5l?#rAL5FcCtw
z-N?gAi<%Ay5EnOroiN-KORS*PLfx^>;5~t)m`C&(5Jo!wnUMDjArJn)+1ur}&)>g%
z;`JFXuMWw&Twj?LmfQm=_^}=?o8FCesjqFTx)RY0(qEzqtidtgvKwx@&GDOExr)8s
za(Q>aTL9Zsws?=JBMTV;ko#y40MSPfn)en$sDB%AI}7S8*}=ktAWtklEO@NM*3|1#
zFbTD^?vpnm TUTrA@yo)E{+%XmSj#Mf(h=Ug-^GS*tfRX0gtYGhE0})kk6*9{@li`N^{yPts1oc%8zd5rX=vU>
zV;gVU5`#d26^5Z?5c!k`Eg+0`HrUuwyLniO2ss5GbBT7XoH6%hn{SvV9CALSpnM2kXo)x*{m
zPCejMg{ce>(iVuM2yyXf1+1}`dem+Gpp5;B7jKne^3qH(PT74S4ywve?Wv^|NT#&>%5&n4Ei)rRj&ugZ9)yiJ&RTkl^~s;0_{igq2pT2Gq~S{gNo2&C
ziL_ITzFjIzy%9!Xka`cP#0m)?HbDFiW?c<=`q|*C9`r{~=$4j~QP%E9FS8Z+1N8jY
z&tJ2>l|M>$K(EHg-QBrCv1DuY2c4&QL4Z=IlJ%7V{9PM55W-oYE9^ef4W@
zqr_Of710fEcr=|xfcN4Th;^~DA%-&43iVVaRxqCM0+n50MTzQQF&PziaBEf3zhZo-
z+NyrFjrG^D)$6tUG4*z#zJlt?ms2}GJG#|sSDuz?ujznJd(+?$AD!mogrk>{$F4-&
zA_RN@e*lCCAfXKq)DZ1f5DdwfOlI~mk5$ozxS`rDwyqZ3ev92oe)yw%=pWcgFB!9t
zE3pkmomHR_%g$T+oeS%sV^&vKUTJM^)>rA4VKI{U*yWl*5Fz`6!?csKqRCb
zNw24;Q&+TnSfVHVMjr`_Emv6dATmTyt^+uJJrdtZk&Ze?v3!z
z6SnPDLH{?7#4ABR&)@&-5ZXsa=|=y`>SNjFU2Bf6?TP1RlkaCmR?jN0Vl$jUw|56v
zUAeoOOR4N56s5ab4!`486(|k|O(He!mNq#~$Ma-Ox#e;Py6kT#z*JWMQy(J>A~);2OD4tJYm2=RShbwLiV>4
zKsc6xrXE;S0jpqgu#513mj#fZ74(J*ALL}$$IUT(zZTnvp{z(b|2Jk`|FeU^4+5lg
zX9V${3H8!75-GHL)ik|S4F5R~cmG+$hi>nlln4QB4o@}U17NYkukgvOZK;7%R+GYQ
z?$J&})#aQ{fB=V9dg}CYWpLW($by&C@ZKXi6d8|6LoVJ{R9pSB)%j{=
zMgPbXIz%Fm^Yau^GGy8N=t*%mU8s8JZ#^fHOwO
zEnT-P0pge?)=vP4e?pT_Q{Qf0(5~_u{Ae8bYZ-oh{^{R-s<7~%{umw~2p(1Tt2^a0
zDQ=}3h%h}^TAVywc^NX~TMH49#kPCb_O>%TT
zuOYRuv)|`AHhmhR^LwvrEPQl5pccg-ibiIO*TM*7HMb?Y2@hNjna^LZL^v##OUwUm
zCpZ-4ha8}L#2*ztsZ)E=5e`&dsrGKYs-SVL$Gh9Nf_4kU2dVQoL;AoMix1Z30v_!+
zb$B*}$qP<`Ccq7R}R!@Fvv-KzwUkn$SL5-aHS;V-kIPSX-V=%VRj
zmb|#`KWCkKA~niRN$kWXa~yW`6*LxGyq~swt)+1M$`k;A?ne!5~t!
z)AHb}fewX@Im-{@gpSB=!2e=1h_zw3ybckogJtXwR^-8unhbEjFM>XP(C2csUN1Cw
z3nkWH$3DS>WnUHpJeWU-FOD_T=JN0+CAE+5y{Jj9Dqsdg2v0z0jPIZ=0nfBIac2tP
zg6!Q5^J5B<9jmR)U`iqFazu`|&3i{lAO?fH5kM+#?pt<5|F&zPw=dNGhZ;okg-Tbv
zGkovX%2aU_V)2KzW?ix?K0L2uQ=t!SbD|oaZ$`?_y;I*YTo@jx3diqbJ|~L^X}_UXvFBwHtmwTe%6bR3%dEG3Le*9Z_9!DwZ)NL|pum(W54VtNbcY
z001BWNkl84=Dks3elA{;_*~iTnN#dm~5m&-8%JNk8E0e<(kKY0lc5
zCm)?`kWvyg={H{cKzSkhSQX|J{7VVhUE&~p)X=;*>K>=l>2b3ePQDJX<7;>hy@5+Dc>T)|3a
z>4^U?YF$rF4cUJp+P_hCKUfjLf7K_7sRet*(eFVG?FIBGT(g@_-+a)m1&7xJ$DHE{R&
z+ryVH|0_V0gV$I`zcUAgm%Jv1-Mu7(O-k3iir*i4L#GiA;`oBSWLVrj$8FhtI%0H4
zd{imUnE-+xZUl%h@R24&A^7aW6MnDZVT`gX3LxlYM5A!pbwx%rND-kwq#l8P?ukgS
zE_k)G74;D|^GFDnS(X410Vh@t>Een&_q>LRZz#UB^@{1Etm(t#kh&p0TFf5IAMl(K
ze{_&O`Y1kf6~x<%H=M0Y{oGKP5^3-eQXh*B_h7dQJBA353POWWgdT?+!7bS7jj~0a%XtZj&076kRKvOP+kOx{`T_Waz^k-l416yDC8Y?V=H=cs4>zUtA&y1
zAL6csDQz^0O28H&fe4rqNE8wtQlu(ooTx?M|Np7|?&EtUvDF>jMMc{>JKZVAbI*O8
zLk1~vSJIS(h#s#qjo^Y68^+$<8t%LV4=ByFr7YKkr_cjRT
z^MGnBbTNs%8T>QEs85(=dg%BwRpbHa(ENdcoS4jcA$0o)m)*C5j=wjXcrn;&tv{3z
z^>?S;&qat+EAE##))>83={#D&Am(gcDs-Dh!l{5q-Z~pqwXc-DsKp@Yf3=qx8SojK
zCZN&jcY@dY(-E3SU?IJ|{ds@$`beUEg3840msrFFzXOIy0Jm0Yx|cMCZ*bHniz}p(
zfIiJ>S%f|RECphx++vx<`uwI!p_@L7YB}DG`jmNqJAKOLq|6=+2~8j8=9Ex=w4avZ
zg-PrI>Kv;phDUPG;17Sc
ze_4Vn%F<#F1rLM>#?_7?ls2Txb4)KPr<)hNfl6oX;kxz7@Hlz*DzD=>mTO$io~1X+
zIU(l3t#y=MF0sN*-%Udpqn8e(j<&ncRDYnmPlmKkknr&f21vUbz0UXP&wKH}C-FcE
zLHA0$W}=8Q8OXs~v^dfQkOUjNILv%`-A%Lx325_-#YYn|
zYBSu}h4tD#;UFe>kP)@O4liAK*~X{76^QsA;bYd@Y4O1TVTG;%f|1T}G=&5FwG22n
zM(E-Dwys*Arp^D&pirBd*y~uea)eGrjiuZbE6l6c(|)l1248(Q?xQ%Iv*7Nd*@JUH
zvYDh1F0mLP&++Ty{^j=Gcsu}wz&b55$XszqpP>TY$Xc(XRp~SXD+f#K_4*E}G(v9ygCzWWib-B4nV(
zAySSEq!@AC?0{#wFoKilK(XWFFn%(5oal=YzCMI4jJo#7#l;jDH{P(BET@DB5_wR)
zt6xTSg#{JP*&+!JZH|TP!EX!?*c$-n5(6Ubj)up%!5>^`wGa3+T%hBK`eT7c2_s`L
zIFo)1qXb%46n8^cA21U{slJaqx*`ss(Hm1^%S!-YewVyWNPlEz?`mOa4+|dP)BqGV
zf*u+|c~n90D?WOS@0SiwkCtz->yWFg?AW;4_ri{Bw4DgjH9K&z}o>rpu`dop&ASR
zv*Npft$G3@k9xL_>_OLu@DiWnyMBm2s7vD?Xih!cnVX1g`$NV*kqfncM39Z}jW{GW
z(1Pfop}F|K?Y1Q*+Opg-Fd?7BM|-!tBK9Bx$+!4pJJfDIhBnWkvx`R5Dxj7!oN<+1
znnVcf#JJNQW!vFmxqLz6h91IWujzx;c@ZB&8y{oIAmKWEmw&ISzdm1lNPv7mg&11B
za>=A*K5A-oySOqrL73Lww3{%f$ye=2k|
zsmPjB>@r+tP}>)0guH?^&_+B6kevVsOj~@vD
zdh4S(KJHCzj`to=OSzbH?(x)|X69Zz6Gc{?R!SnDE4+U1o*uSb_2
z-Ui_zK3=jY-e-p+AY)lz57HkM<<*!xe?nTbw*#I3<|q1v@HqL%NxE?}B?U;WYL&OR
ziDe$#I>s|E@h1JvsSG@D&em6wL5_v>60^o4{!l7JLWBqe03z`5>_4(fZyFF0GK5N*
z0o}op8KUxQm{Zk-uhjhxPD*AnOcnwMdVW`-gMiZQ#?<=de(xlZYbZBK0tHl{Y}Xka
zE_~ijC}$HX#8cp)Pl%6*xTH@s5=klnbO?A95OUrDN-^M}vB6*c-YvMeBKlYi$I9jt
z-F!OxeB3l&cjGLQ8O|PQbASgL?9RGA3)1krqJoSdBeuLR;)D#Y05EyqihY_W87juE3{P0?5V#8C|5oQ7A0?cKxz2VtT}
zAcT{cODq8pSoQVmuZ1=i$dGF&vdFq&Lb)FE`amKCi!M1H0e}F5utM))qCkg!B|$>2
zo_?g5WX{IE!3zCKwig$7HyiMPV+l@o9INdFJ*Md?K#VX%B7UMeD=GwGqBJB-b}
zm!{+_HkuZSHqD(A4ncyVJ7%jSCiAPgvSSr<7!qEbcF$Fqi}^@aQ<=|Fb9F7T?r3jk#5_nuox~s+D};SBmj;h%mqz19~ulA
zxoG3t5nc}p29W`^SX)8ah_|#JSc!rOgUbsj@O@Esd;=bqI)9_OQsVrriTRr`A77-+
z-w6<>xR9lZqpZ>!6}Bd(94X}!@{rJTw|a5$fM2Fy{@l~vnD
zha7xJh!`u2EIxoefH@Y6d~r3pG;<907CgjHr|Z>qnui)7bZ?G14-@1MLDauHKq1t<
zLj4{`8xtTE;{2-A+Z~m9S0%bwht=_gJRbZ?@$tcJUSlTWXcr(=Vl}GM*DQlx%fA|b
z=Zpbzt3%hs-{`E>3P*X-0E;)U-XsuW5%Gwrj|EN+y~oGh@zy2~3nAGtSiPm9{+vgJg$E!60n$4h
zm&vMbSiW;Zi`uJu@WX!MYZIo84$(-rU3AbfjS+xM#1B;gM>iG@`CDiwIfk%Ko
z0wNKJ*y#CPcAzkDQLL=m6~C$O(c$g)0Vf~uO5HB5@+)N}I;IWwnSh6K7agYfSIa2o
ze6*o_LD)=0vv#~iPe(a;QFqWOhORyHic@j{5qWS9R_!WS5OAGlGJq2@oY9Qh%fxq&n52EA5G=xaaV#asVp-AKz0lpO
zVFf-+@$si5wa{AHJvsT$0P?Q8LesmtRz66ty)hs({dp_TLC&UT7mL>g8nDbM>a3Cm
zscv9x5F8pJ+1{TCkv@wbCoDn)KIW@C8zFwz2Y~#;+_kPHk}Scs1r?e`S_EmK!3NY}
zI6a=D{qX^U;`_hUsk~HGR$m-<))^OZF0Q)sgD^5OBO{;RW|l>n?o!+4#`^(zQ6)xI15mBw*rG-y5YQ;&q0C!e`M1LGkJ0GzYTF%
zF}e~Jgjj;Ow!{+p=((doHkL#`jlTNSu&dwX#X7Q~CS@z>IUyqOQLBla+=5n@bC3og
z1Q6>%owXcHtvXN2=7XlfB7oRO_8M>I9hih@5%S~s_}hc5vRX>V?+*$f>V4K141%>*
zOH;^(`NP^3{m_MkyNM(Mc1$D5DT>;@-kWdJAKgc&F(NsH{~j`WiuIOtUzvZ$=6*o%
zSi{@hqY)niRa{|HfXyNq*h|JUZ0EKdj37cfz#w$vd`8y@Gl;tK+w;Jf5_wGMjy?*`
ztASAGbV_RIqN75R=l@glPl%770>rDb^g1HFBoE6~=q8;vpQ~s-a2kk7y88HEL}}g$
zOOZr$u=4YB^CTT&@u5BdL5tAnWjofx&GC)UBmV(_oF51v&(BkaNS_yOFOfpxje6+a
zckN|yBSON^srj#D%8uob67f*^U;yMu#C~NvHz&NZ{{Sb=hv4xD=y(+H7)~=Z-(oP&
zC3z9kH7rHpEu0a#OBf>o-Eba}c{iBy0&Wt-9mt~|vFR!vWzU7??xa+lqjmV&9LpI;
zQH6hGSy+-{v&lb6+WkC$cx4?ddFR$Enz3r-3+Xmi(JS2u0WVh5oF-H<ye`{Gh<2|TR$FzVxP<+h*w
z8EyWngF>+| |