diff --git a/apps/accounts/filters.py b/apps/accounts/filters.py
index df224b707..84dd82452 100644
--- a/apps/accounts/filters.py
+++ b/apps/accounts/filters.py
@@ -7,35 +7,43 @@ from django_filters import rest_framework as drf_filters
from assets.models import Node
from common.drf.filters import BaseFilterSet
from common.utils.timezone import local_zero_hour, local_now
-from .models import Account, GatheredAccount, ChangeSecretRecord
+from .models import Account, GatheredAccount, ChangeSecretRecord, AccountRisk
class AccountFilterSet(BaseFilterSet):
- ip = drf_filters.CharFilter(field_name='address', lookup_expr='exact')
- hostname = drf_filters.CharFilter(field_name='name', lookup_expr='exact')
- username = drf_filters.CharFilter(field_name="username", lookup_expr='exact')
- address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact')
- asset_id = drf_filters.CharFilter(field_name="asset", lookup_expr='exact')
- asset = drf_filters.CharFilter(field_name='asset', lookup_expr='exact')
- assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
- nodes = drf_filters.CharFilter(method='filter_nodes')
- node_id = drf_filters.CharFilter(method='filter_nodes')
- has_secret = drf_filters.BooleanFilter(method='filter_has_secret')
- platform = drf_filters.CharFilter(field_name='asset__platform_id', lookup_expr='exact')
- category = drf_filters.CharFilter(field_name='asset__platform__category', lookup_expr='exact')
- type = drf_filters.CharFilter(field_name='asset__platform__type', lookup_expr='exact')
- latest_discovery = drf_filters.BooleanFilter(method='filter_latest')
- latest_accessed = drf_filters.BooleanFilter(method='filter_latest')
- latest_updated = drf_filters.BooleanFilter(method='filter_latest')
- latest_secret_changed = drf_filters.BooleanFilter(method='filter_latest')
- latest_secret_change_failed = drf_filters.BooleanFilter(method='filter_latest')
- risk = drf_filters.CharFilter(method='filter_risk', )
- long_time_no_change_secret = drf_filters.BooleanFilter(method='filter_long_time')
- long_time_no_verified = drf_filters.BooleanFilter(method='filter_long_time')
+ ip = drf_filters.CharFilter(field_name="address", lookup_expr="exact")
+ hostname = drf_filters.CharFilter(field_name="name", lookup_expr="exact")
+ username = drf_filters.CharFilter(field_name="username", lookup_expr="exact")
+ address = drf_filters.CharFilter(field_name="asset__address", lookup_expr="exact")
+ asset_id = drf_filters.CharFilter(field_name="asset", lookup_expr="exact")
+ asset = drf_filters.CharFilter(field_name="asset", lookup_expr="exact")
+ assets = drf_filters.CharFilter(field_name="asset_id", lookup_expr="exact")
+ nodes = drf_filters.CharFilter(method="filter_nodes")
+ node_id = drf_filters.CharFilter(method="filter_nodes")
+ has_secret = drf_filters.BooleanFilter(method="filter_has_secret")
+ platform = drf_filters.CharFilter(
+ field_name="asset__platform_id", lookup_expr="exact"
+ )
+ category = drf_filters.CharFilter(
+ field_name="asset__platform__category", lookup_expr="exact"
+ )
+ type = drf_filters.CharFilter(
+ field_name="asset__platform__type", lookup_expr="exact"
+ )
+ latest_discovery = drf_filters.BooleanFilter(method="filter_latest")
+ latest_accessed = drf_filters.BooleanFilter(method="filter_latest")
+ latest_updated = drf_filters.BooleanFilter(method="filter_latest")
+ latest_secret_changed = drf_filters.BooleanFilter(method="filter_latest")
+ latest_secret_change_failed = drf_filters.BooleanFilter(method="filter_latest")
+ risk = drf_filters.CharFilter(
+ method="filter_risk",
+ )
+ long_time_no_change_secret = drf_filters.BooleanFilter(method="filter_long_time")
+ long_time_no_verified = drf_filters.BooleanFilter(method="filter_long_time")
@staticmethod
def filter_has_secret(queryset, name, has_secret):
- q = Q(_secret__isnull=True) | Q(_secret='')
+ q = Q(_secret__isnull=True) | Q(_secret="")
if has_secret:
return queryset.exclude(q)
else:
@@ -45,15 +53,15 @@ class AccountFilterSet(BaseFilterSet):
def filter_long_time(queryset, name, value):
date = timezone.now() - timezone.timedelta(days=30)
- if name == 'long_time_no_change_secret':
- field = 'date_change_secret'
- confirm_field = 'change_secret_status'
+ if name == "long_time_no_change_secret":
+ field = "date_change_secret"
+ confirm_field = "change_secret_status"
else:
- field = 'date_verified'
- confirm_field = 'connectivity'
+ field = "date_verified"
+ confirm_field = "connectivity"
- q = Q(**{f'{field}__lt': date}) | Q(**{f'{field}__isnull': True})
- confirm_q = {f'{confirm_field}': 'na'}
+ q = Q(**{f"{field}__lt": date}) | Q(**{f"{field}__isnull": True})
+ confirm_q = {f"{confirm_field}": "na"}
queryset = queryset.exclude(**confirm_q).filter(q)
return queryset
@@ -62,7 +70,11 @@ class AccountFilterSet(BaseFilterSet):
if not value:
return queryset
- return Account.get_risks(queryset, value)
+ risks = AccountRisk.objects.filter(risk=value)
+ usernames = risks.values_list('username', flat=True)
+ assets = risks.values_list('asset', flat=True)
+ queryset = queryset.filter(username__in=usernames, asset__in=assets)
+ return queryset
@staticmethod
def filter_latest(queryset, name, value):
@@ -72,17 +84,19 @@ class AccountFilterSet(BaseFilterSet):
date = timezone.now() - timezone.timedelta(days=7)
kwargs = {}
- if name == 'latest_discovery':
- kwargs.update({'date_created__gte': date, 'source': 'collected'})
- elif name == 'latest_accessed':
- kwargs.update({'date_last_login__gte': date})
- elif name == 'latest_updated':
- kwargs.update({'date_updated__gte': date})
- elif name == 'latest_secret_changed':
- kwargs.update({'date_change_secret__gt': date})
+ if name == "latest_discovery":
+ kwargs.update({"date_created__gte": date, "source": "collected"})
+ elif name == "latest_accessed":
+ kwargs.update({"date_last_login__gte": date})
+ elif name == "latest_updated":
+ kwargs.update({"date_updated__gte": date})
+ elif name == "latest_secret_changed":
+ kwargs.update({"date_change_secret__gt": date})
- if name == 'latest_secret_change_failed':
- queryset = queryset.filter(date_change_secret__gt=date).exclude(change_secret_status='ok')
+ if name == "latest_secret_change_failed":
+ queryset = queryset.filter(date_change_secret__gt=date).exclude(
+ change_secret_status="ok"
+ )
if kwargs:
queryset = queryset.filter(date_last_login__gte=date)
@@ -97,19 +111,22 @@ class AccountFilterSet(BaseFilterSet):
node_qs = Node.objects.none()
for node in nodes:
node_qs |= node.get_all_children(with_self=True)
- node_ids = list(node_qs.values_list('id', flat=True))
+ node_ids = list(node_qs.values_list("id", flat=True))
queryset = queryset.filter(asset__nodes__in=node_ids)
return queryset
class Meta:
model = Account
- fields = ['id', 'asset', 'source_id', 'secret_type', 'category', 'type']
+ fields = ["id", "asset", "source_id", "secret_type", "category", "type"]
class GatheredAccountFilterSet(BaseFilterSet):
- node_id = drf_filters.CharFilter(method='filter_nodes')
- asset_id = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
- asset_name = drf_filters.CharFilter(field_name='asset__name', lookup_expr='icontains')
+ node_id = drf_filters.CharFilter(method="filter_nodes")
+ asset_id = drf_filters.CharFilter(field_name="asset_id", lookup_expr="exact")
+ asset_name = drf_filters.CharFilter(
+ field_name="asset__name", lookup_expr="icontains"
+ )
+ status = drf_filters.CharFilter(field_name="status", lookup_expr="exact")
@staticmethod
def filter_nodes(queryset, name, value):
@@ -117,15 +134,20 @@ class GatheredAccountFilterSet(BaseFilterSet):
class Meta:
model = GatheredAccount
- fields = ['id', 'username']
+ fields = ["id", "username"]
class ChangeSecretRecordFilterSet(BaseFilterSet):
- asset_name = drf_filters.CharFilter(field_name='asset__name', lookup_expr='icontains')
- account_username = drf_filters.CharFilter(field_name='account__username', lookup_expr='icontains')
- execution_id = drf_filters.CharFilter(field_name='execution_id', lookup_expr='exact')
-
- days = drf_filters.NumberFilter(method='filter_days')
+ asset_name = drf_filters.CharFilter(
+ field_name="asset__name", lookup_expr="icontains"
+ )
+ account_username = drf_filters.CharFilter(
+ field_name="account__username", lookup_expr="icontains"
+ )
+ execution_id = drf_filters.CharFilter(
+ field_name="execution_id", lookup_expr="exact"
+ )
+ days = drf_filters.NumberFilter(method="filter_days")
@staticmethod
def filter_days(queryset, name, value):
@@ -138,4 +160,4 @@ class ChangeSecretRecordFilterSet(BaseFilterSet):
class Meta:
model = ChangeSecretRecord
- fields = ['id', 'status', 'asset_id', 'execution']
+ fields = ["id", "status", "asset_id", "execution"]
diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py
index 6ab538551..c23a2edd3 100644
--- a/apps/assets/serializers/asset/common.py
+++ b/apps/assets/serializers/asset/common.py
@@ -230,7 +230,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
.prefetch_related('platform', 'platform__automation') \
.annotate(category=F("platform__category")) \
.annotate(type=F("platform__type")) \
- .annotate(assets_amount=Count('accounts'))
+ .annotate(accounts_amount=Count('accounts'))
if queryset.model is Asset:
queryset = queryset.prefetch_related('labels__label', 'labels')
else:
diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py
index 2eb5a44b2..7573d17a5 100644
--- a/apps/assets/serializers/platform.py
+++ b/apps/assets/serializers/platform.py
@@ -6,7 +6,8 @@ from rest_framework.validators import UniqueValidator
from assets.models import Asset
from common.serializers import (
WritableNestedModelSerializer, type_field_map, MethodSerializer,
- DictSerializer, create_serializer_class, ResourceLabelsMixin
+ DictSerializer, create_serializer_class, ResourceLabelsMixin,
+ CommonSerializerMixin
)
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
from common.utils import lazyproperty
@@ -158,7 +159,7 @@ class PlatformCustomField(serializers.Serializer):
choices = serializers.ListField(default=list, label=_("Choices"), required=False)
-class PlatformSerializer(ResourceLabelsMixin, WritableNestedModelSerializer):
+class PlatformSerializer(ResourceLabelsMixin, CommonSerializerMixin, WritableNestedModelSerializer):
id = serializers.IntegerField(
label='ID', required=False,
validators=[UniqueValidator(queryset=Platform.objects.all())]
diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html
index 47bc3fb8b..88899c044 100644
--- a/apps/authentication/templates/authentication/login.html
+++ b/apps/authentication/templates/authentication/login.html
@@ -402,6 +402,14 @@
+ {% if demo_mode %}
+
{% if auth_methods %}
diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py
index e6cb1b261..4166145ee 100644
--- a/apps/common/drf/filters.py
+++ b/apps/common/drf/filters.py
@@ -4,6 +4,7 @@ import base64
import json
import logging
from collections import defaultdict
+from django.utils import timezone
from django.core.cache import cache
from django.core.exceptions import ImproperlyConfigured
@@ -18,18 +19,26 @@ from rest_framework.filters import OrderingFilter
from common import const
from common.db.fields import RelatedManager
-logger = logging.getLogger('jumpserver.common')
+logger = logging.getLogger("jumpserver.common")
__all__ = [
- "DatetimeRangeFilterBackend", "IDSpmFilterBackend",
- 'IDInFilterBackend', "CustomFilterBackend",
- "BaseFilterSet", 'IDNotFilterBackend',
- 'NotOrRelFilterBackend', 'LabelFilterBackend',
- 'RewriteOrderingFilter', 'AttrRulesFilterBackend'
+ "DatetimeRangeFilterBackend",
+ "IDSpmFilterBackend",
+ "IDInFilterBackend",
+ "CustomFilterBackend",
+ "BaseFilterSet",
+ "IDNotFilterBackend",
+ "NotOrRelFilterBackend",
+ "LabelFilterBackend",
+ "RewriteOrderingFilter",
+ "AttrRulesFilterBackend",
]
class BaseFilterSet(drf_filters.FilterSet):
+ days = drf_filters.NumberFilter(method="filter_days")
+ days__lt = drf_filters.NumberFilter(method="filter_days")
+
def do_nothing(self, queryset, name, value):
return queryset
@@ -38,6 +47,22 @@ class BaseFilterSet(drf_filters.FilterSet):
return self.form.cleaned_data[k]
return default
+ @staticmethod
+ def filter_days(queryset, name, value):
+ try:
+ value = int(value)
+ except ValueError:
+ return queryset.none()
+
+ if name == 'days':
+ arg = 'date_created__gte'
+ else:
+ arg = 'date_created__lt'
+
+ date = timezone.now() - timezone.timedelta(days=value)
+ kwargs = {arg: date}
+ return queryset.filter(**kwargs)
+
class DatetimeRangeFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
@@ -50,18 +75,20 @@ class DatetimeRangeFilterBackend(filters.BaseFilterBackend):
for v in date_range_keyword:
ret.append(
coreapi.Field(
- name=v, location='query', required=False, type='string',
+ name=v,
+ location="query",
+ required=False,
+ type="string",
schema=coreschema.String(
- title=v,
- description='%s %s' % (attr, v)
- )
+ title=v, description="%s %s" % (attr, v)
+ ),
)
)
return ret
def _get_date_range_filter_fields(self, view):
- if not hasattr(view, 'date_range_filter_fields'):
+ if not hasattr(view, "date_range_filter_fields"):
return {}
try:
return dict(view.date_range_filter_fields)
@@ -75,7 +102,9 @@ class DatetimeRangeFilterBackend(filters.BaseFilterBackend):
('db column', ('query param date from', 'query param date to'))
]
```
- """.format(view.name)
+ """.format(
+ view.name
+ )
logger.error(msg)
raise ImproperlyConfigured(msg)
@@ -110,14 +139,17 @@ class IDSpmFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
- name='spm', location='query', required=False,
- type='string', example='',
- description='Pre post objects id get spm id, then using filter'
+ name="spm",
+ location="query",
+ required=False,
+ type="string",
+ example="",
+ description="Pre post objects id get spm id, then using filter",
)
]
def filter_queryset(self, request, queryset, view):
- spm = request.query_params.get('spm')
+ spm = request.query_params.get("spm")
if not spm:
return queryset
cache_key = const.KEY_CACHE_RESOURCE_IDS.format(spm)
@@ -127,7 +159,7 @@ class IDSpmFilterBackend(filters.BaseFilterBackend):
return queryset.none()
if isinstance(resource_ids, str):
resource_ids = [resource_ids]
- if hasattr(view, 'filter_spm_queryset'):
+ if hasattr(view, "filter_spm_queryset"):
queryset = view.filter_spm_queryset(resource_ids, queryset)
else:
queryset = queryset.filter(id__in=resource_ids)
@@ -138,17 +170,20 @@ class IDInFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
- name='ids', location='query', required=False,
- type='string', example='/api/v1/users/users?ids=1,2,3',
- description='Filter by id set'
+ name="ids",
+ location="query",
+ required=False,
+ type="string",
+ example="/api/v1/users/users?ids=1,2,3",
+ description="Filter by id set",
)
]
def filter_queryset(self, request, queryset, view):
- ids = request.query_params.get('ids')
+ ids = request.query_params.get("ids")
if not ids:
return queryset
- id_list = [i.strip() for i in ids.split(',')]
+ id_list = [i.strip() for i in ids.split(",")]
queryset = queryset.filter(id__in=id_list)
return queryset
@@ -157,17 +192,20 @@ class IDNotFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
- name='id!', location='query', required=False,
- type='string', example='/api/v1/users/users?id!=1,2,3',
- description='Exclude by id set'
+ name="id!",
+ location="query",
+ required=False,
+ type="string",
+ example="/api/v1/users/users?id!=1,2,3",
+ description="Exclude by id set",
)
]
def filter_queryset(self, request, queryset, view):
- ids = request.query_params.get('id!')
+ ids = request.query_params.get("id!")
if not ids:
return queryset
- id_list = [i.strip() for i in ids.split(',')]
+ id_list = [i.strip() for i in ids.split(",")]
queryset = queryset.exclude(id__in=id_list)
return queryset
@@ -176,26 +214,30 @@ class LabelFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
- name='label', location='query', required=False,
- type='string', example='/api/v1/users/users?label=abc',
- description='Filter by label'
+ name="label",
+ location="query",
+ required=False,
+ type="string",
+ example="/api/v1/users/users?label=abc",
+ description="Filter by label",
)
]
@staticmethod
def parse_labels(labels_id):
from labels.models import Label
- label_ids = [i.strip() for i in labels_id.split(',')]
+
+ label_ids = [i.strip() for i in labels_id.split(",")]
cleaned = []
args = []
for label_id in label_ids:
kwargs = {}
- if ':' in label_id:
- k, v = label_id.split(':', 1)
- kwargs['name'] = k.strip()
- if v != '*':
- kwargs['value'] = v.strip()
+ if ":" in label_id:
+ k, v = label_id.split(":", 1)
+ kwargs["name"] = k.strip()
+ if v != "*":
+ kwargs["value"] = v.strip()
args.append(kwargs)
else:
cleaned.append(label_id)
@@ -209,14 +251,14 @@ class LabelFilterBackend(filters.BaseFilterBackend):
return cleaned
def filter_queryset(self, request, queryset, view):
- labels_id = request.query_params.get('labels')
+ labels_id = request.query_params.get("labels")
if not labels_id:
return queryset
- if not hasattr(queryset, 'model'):
+ if not hasattr(queryset, "model"):
return queryset
- if not hasattr(queryset.model, 'label_model'):
+ if not hasattr(queryset.model, "label_model"):
return queryset
model = queryset.model.label_model()
@@ -225,7 +267,8 @@ class LabelFilterBackend(filters.BaseFilterBackend):
model_name = model._meta.model_name
full_resources = labeled_resource_cls.objects.filter(
- res_type__app_label=app_label, res_type__model=model_name,
+ res_type__app_label=app_label,
+ res_type__model=model_name,
)
labels = self.parse_labels(labels_id)
grouped = defaultdict(set)
@@ -234,8 +277,10 @@ class LabelFilterBackend(filters.BaseFilterBackend):
matched_ids = set()
for name, label_ids in grouped.items():
- resources = model.filter_resources_by_labels(full_resources, label_ids, rel='any')
- res_ids = resources.values_list('res_id', flat=True)
+ resources = model.filter_resources_by_labels(
+ full_resources, label_ids, rel="any"
+ )
+ res_ids = resources.values_list("res_id", flat=True)
if not matched_ids:
matched_ids = set(res_ids)
else:
@@ -249,16 +294,14 @@ class CustomFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
fields = []
defaults = dict(
- location='query', required=False,
- type='string', example='',
- description=''
+ location="query", required=False, type="string", example="", description=""
)
- if not hasattr(view, 'custom_filter_fields'):
+ if not hasattr(view, "custom_filter_fields"):
return []
for field in view.custom_filter_fields:
if isinstance(field, str):
- defaults['name'] = field
+ defaults["name"] = field
elif isinstance(field, dict):
defaults.update(field)
else:
@@ -270,7 +313,7 @@ class CustomFilterBackend(filters.BaseFilterBackend):
return queryset
-def current_user_filter(user_field='user'):
+def current_user_filter(user_field="user"):
class CurrentUserFilter(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(**{user_field: request.user})
@@ -290,27 +333,30 @@ class AttrRulesFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
- name='attr_rules', location='query', required=False,
- type='string', example='/api/v1/users/users?attr_rules=jsonbase64',
- description='Filter by json like {"type": "attrs", "attrs": []} to base64'
+ name="attr_rules",
+ location="query",
+ required=False,
+ type="string",
+ example="/api/v1/users/users?attr_rules=jsonbase64",
+ description='Filter by json like {"type": "attrs", "attrs": []} to base64',
)
]
def filter_queryset(self, request, queryset, view):
- attr_rules = request.query_params.get('attr_rules')
+ attr_rules = request.query_params.get("attr_rules")
if not attr_rules:
return queryset
try:
- attr_rules = base64.b64decode(attr_rules.encode('utf-8'))
+ attr_rules = base64.b64decode(attr_rules.encode("utf-8"))
except Exception:
- raise ValidationError({'attr_rules': 'attr_rules should be base64'})
+ raise ValidationError({"attr_rules": "attr_rules should be base64"})
try:
attr_rules = json.loads(attr_rules)
except Exception:
- raise ValidationError({'attr_rules': 'attr_rules should be json'})
+ raise ValidationError({"attr_rules": "attr_rules should be json"})
- logger.debug('attr_rules: %s', attr_rules)
+ logger.debug("attr_rules: %s", attr_rules)
qs = RelatedManager.get_to_filter_qs(attr_rules, queryset.model)
for q in qs:
queryset = queryset.filter(q)
@@ -321,33 +367,38 @@ class NotOrRelFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
- name='_rel', location='query', required=False,
- type='string', example='/api/v1/users/users?name=abc&username=def&_rel=union',
- description='Filter by rel, or not, default is and'
+ name="_rel",
+ location="query",
+ required=False,
+ type="string",
+ example="/api/v1/users/users?name=abc&username=def&_rel=union",
+ description="Filter by rel, or not, default is and",
)
]
def filter_queryset(self, request, queryset, view):
- _rel = request.query_params.get('_rel')
- if not _rel or _rel not in ('or', 'not'):
+ _rel = request.query_params.get("_rel")
+ if not _rel or _rel not in ("or", "not"):
return queryset
- if _rel == 'not':
+ if _rel == "not":
queryset.query.where.negated = True
- elif _rel == 'or':
- queryset.query.where.connector = 'OR'
+ elif _rel == "or":
+ queryset.query.where.connector = "OR"
queryset._result_cache = None
return queryset
class RewriteOrderingFilter(OrderingFilter):
- default_ordering_if_has = ('name', )
+ default_ordering_if_has = ("name",)
def get_default_ordering(self, view):
ordering = super().get_default_ordering(view)
# 如果 view.ordering = [] 表示不排序, 这样可以节约性能 (比如: 用户授权的资产)
if ordering is not None:
return ordering
- ordering_fields = getattr(view, 'ordering_fields', self.ordering_fields)
+ ordering_fields = getattr(view, "ordering_fields", self.ordering_fields)
if ordering_fields:
- ordering = tuple([f for f in ordering_fields if f in self.default_ordering_if_has])
+ ordering = tuple(
+ [f for f in ordering_fields if f in self.default_ordering_if_has]
+ )
return ordering
diff --git a/apps/i18n/core/en/LC_MESSAGES/django.po b/apps/i18n/core/en/LC_MESSAGES/django.po
index 21cb5e138..e710c0366 100644
--- a/apps/i18n/core/en/LC_MESSAGES/django.po
+++ b/apps/i18n/core/en/LC_MESSAGES/django.po
@@ -5254,7 +5254,7 @@ msgstr ""
#: ops/models/job.py:148
msgid "Timeout (Seconds)"
-msgstr ""
+msgstr "Timeout (Sec)"
#: ops/models/job.py:153
msgid "Use Parameter Define"
@@ -5334,8 +5334,8 @@ msgid "Next execution time"
msgstr ""
#: ops/serializers/job.py:15
-msgid "Execute after saving"
-msgstr "Execute after saving"
+msgid "Run on save"
+msgstr "Run on save"
#: ops/serializers/job.py:72
msgid "Job type"
diff --git a/apps/i18n/core/ja/LC_MESSAGES/django.po b/apps/i18n/core/ja/LC_MESSAGES/django.po
index 4f1f5bbb5..5402c293f 100644
--- a/apps/i18n/core/ja/LC_MESSAGES/django.po
+++ b/apps/i18n/core/ja/LC_MESSAGES/django.po
@@ -5527,7 +5527,7 @@ msgid "Next execution time"
msgstr "最後の実行"
#: ops/serializers/job.py:15
-msgid "Execute after saving"
+msgid "Run on save"
msgstr "保存後に実行"
#: ops/serializers/job.py:72
diff --git a/apps/i18n/core/zh/LC_MESSAGES/django.po b/apps/i18n/core/zh/LC_MESSAGES/django.po
index 9a0f97747..ce8ab1310 100644
--- a/apps/i18n/core/zh/LC_MESSAGES/django.po
+++ b/apps/i18n/core/zh/LC_MESSAGES/django.po
@@ -5478,7 +5478,7 @@ msgid "Next execution time"
msgstr "下次执行时间"
#: ops/serializers/job.py:15
-msgid "Execute after saving"
+msgid "Run on save"
msgstr "保存后执行"
#: ops/serializers/job.py:72
diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py
index 0327be5c8..77a40bae0 100644
--- a/apps/ops/serializers/job.py
+++ b/apps/ops/serializers/job.py
@@ -12,7 +12,7 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer
class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
- run_after_save = serializers.BooleanField(label=_("Execute after saving"), default=False, required=False)
+ run_after_save = serializers.BooleanField(label=_("Run on save"), default=False, required=False)
nodes = serializers.ListField(required=False, child=serializers.CharField())
date_last_run = serializers.DateTimeField(label=_('Date last run'), read_only=True)
name = serializers.CharField(label=_('Name'), max_length=128, allow_blank=True, required=False)