mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-06-30 16:52:05 +00:00
commit
84070a558e
@ -1,3 +1,4 @@
|
||||
from .application import *
|
||||
from .application_user import *
|
||||
from .mixin import *
|
||||
from .remote_app import *
|
||||
|
@ -2,18 +2,13 @@
|
||||
#
|
||||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from rest_framework import generics
|
||||
|
||||
from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin
|
||||
from .. import models, serializers
|
||||
from ..hands import IsOrgAdminOrAppUser
|
||||
from .. import serializers
|
||||
from ..models import Application
|
||||
from assets.models import SystemUser
|
||||
from assets.serializers import SystemUserListSerializer
|
||||
from perms.models import ApplicationPermission
|
||||
from ..const import ApplicationCategoryChoices
|
||||
|
||||
|
||||
__all__ = ['ApplicationViewSet', 'ApplicationUserListApi']
|
||||
__all__ = ['ApplicationViewSet']
|
||||
|
||||
|
||||
class ApplicationViewSet(OrgBulkModelViewSet):
|
||||
@ -22,29 +17,3 @@ class ApplicationViewSet(OrgBulkModelViewSet):
|
||||
search_fields = filterset_fields
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.ApplicationSerializer
|
||||
|
||||
|
||||
class ApplicationUserListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin, )
|
||||
filterset_fields = ('name', 'username')
|
||||
search_fields = filterset_fields
|
||||
serializer_class = SystemUserListSerializer
|
||||
|
||||
def get_application(self):
|
||||
application = None
|
||||
app_id = self.request.query_params.get('application_id')
|
||||
if app_id:
|
||||
application = Application.objects.get(id=app_id)
|
||||
return application
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = SystemUser.objects.none()
|
||||
application = self.get_application()
|
||||
if not application:
|
||||
return queryset
|
||||
system_user_ids = ApplicationPermission.objects.filter(applications=application)\
|
||||
.values_list('system_users', flat=True)
|
||||
if not system_user_ids:
|
||||
return queryset
|
||||
queryset = SystemUser.objects.filter(id__in=system_user_ids)
|
||||
return queryset
|
||||
|
55
apps/applications/api/application_user.py
Normal file
55
apps/applications/api/application_user.py
Normal file
@ -0,0 +1,55 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from rest_framework import generics
|
||||
from django.conf import settings
|
||||
|
||||
from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin, NeedMFAVerify
|
||||
from .. import serializers
|
||||
from ..models import Application, ApplicationUser
|
||||
from perms.models import ApplicationPermission
|
||||
|
||||
|
||||
class ApplicationUserListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin, )
|
||||
filterset_fields = ('name', 'username')
|
||||
search_fields = filterset_fields
|
||||
serializer_class = serializers.ApplicationUserSerializer
|
||||
_application = None
|
||||
|
||||
@property
|
||||
def application(self):
|
||||
if self._application is None:
|
||||
app_id = self.request.query_params.get('application_id')
|
||||
if app_id:
|
||||
self._application = Application.objects.get(id=app_id)
|
||||
return self._application
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context.update({
|
||||
'application': self.application
|
||||
})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = ApplicationUser.objects.none()
|
||||
if not self.application:
|
||||
return queryset
|
||||
system_user_ids = ApplicationPermission.objects.filter(applications=self.application)\
|
||||
.values_list('system_users', flat=True)
|
||||
if not system_user_ids:
|
||||
return queryset
|
||||
queryset = ApplicationUser.objects.filter(id__in=system_user_ids)
|
||||
return queryset
|
||||
|
||||
|
||||
class ApplicationUserAuthInfoListApi(ApplicationUserListApi):
|
||||
serializer_class = serializers.ApplicationUserWithAuthInfoSerializer
|
||||
http_method_names = ['get']
|
||||
permission_classes = [IsOrgAdminOrAppUser]
|
||||
|
||||
def get_permissions(self):
|
||||
if settings.SECURITY_VIEW_AUTH_NEED_MFA:
|
||||
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
|
||||
return super().get_permissions()
|
@ -11,5 +11,5 @@
|
||||
"""
|
||||
|
||||
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser, NeedMFAVerify
|
||||
from users.models import User, UserGroup
|
||||
|
@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from common.mixins import CommonModelMixin
|
||||
from assets.models import Asset
|
||||
from assets.models import Asset, SystemUser
|
||||
from .. import const
|
||||
|
||||
|
||||
@ -68,3 +68,8 @@ class Application(CommonModelMixin, OrgModelMixin):
|
||||
raise ValueError("Remote App not has asset attr")
|
||||
asset = Asset.objects.filter(id=asset_id).first()
|
||||
return asset
|
||||
|
||||
|
||||
class ApplicationUser(SystemUser):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
@ -6,11 +6,12 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from common.drf.serializers import MethodSerializer
|
||||
from .attrs import category_serializer_classes_mapping, type_serializer_classes_mapping
|
||||
|
||||
from assets.serializers import SystemUserSerializer
|
||||
from .. import models
|
||||
|
||||
__all__ = [
|
||||
'ApplicationSerializer', 'ApplicationSerializerMixin',
|
||||
'ApplicationUserSerializer', 'ApplicationUserWithAuthInfoSerializer'
|
||||
]
|
||||
|
||||
|
||||
@ -66,3 +67,42 @@ class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSeri
|
||||
_attrs.update(attrs)
|
||||
return _attrs
|
||||
|
||||
|
||||
class ApplicationUserSerializer(SystemUserSerializer):
|
||||
application_name = serializers.SerializerMethodField(label=_('Application name'))
|
||||
application_category = serializers.SerializerMethodField(label=_('Application category'))
|
||||
application_type = serializers.SerializerMethodField(label=_('Application type'))
|
||||
|
||||
class Meta(SystemUserSerializer.Meta):
|
||||
model = models.ApplicationUser
|
||||
fields_mini = [
|
||||
'id', 'application_name', 'application_category', 'application_type', 'name', 'username'
|
||||
]
|
||||
fields_small = fields_mini + [
|
||||
'protocol', 'login_mode', 'login_mode_display', 'priority',
|
||||
"username_same_with_user", 'comment',
|
||||
]
|
||||
fields = fields_small
|
||||
extra_kwargs = {
|
||||
'login_mode_display': {'label': _('Login mode display')},
|
||||
'created_by': {'read_only': True},
|
||||
}
|
||||
|
||||
@property
|
||||
def application(self):
|
||||
return self.context['application']
|
||||
|
||||
def get_application_name(self, obj):
|
||||
return self.application.name
|
||||
|
||||
def get_application_category(self, obj):
|
||||
return self.application.get_category_display()
|
||||
|
||||
def get_application_type(self, obj):
|
||||
return self.application.get_type_display()
|
||||
|
||||
|
||||
class ApplicationUserWithAuthInfoSerializer(ApplicationUserSerializer):
|
||||
|
||||
class Meta(ApplicationUserSerializer.Meta):
|
||||
fields = ApplicationUserSerializer.Meta.fields + ['password']
|
||||
|
@ -14,7 +14,8 @@ router.register(r'applications', api.ApplicationViewSet, 'application')
|
||||
|
||||
urlpatterns = [
|
||||
path('remote-apps/<uuid:pk>/connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'),
|
||||
path('application-users/', api.ApplicationUserListApi.as_view(), name='application-user')
|
||||
path('application-users/', api.ApplicationUserListApi.as_view(), name='application-user'),
|
||||
path('application-user-auth-infos/', api.ApplicationUserAuthInfoListApi.as_view(), name='application-user-auth-info')
|
||||
]
|
||||
|
||||
|
||||
|
@ -94,8 +94,14 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
|
||||
date_range_filter_fields = [
|
||||
('date_start', ('date_from', 'date_to'))
|
||||
]
|
||||
filterset_fields = ['user__name', 'command', 'run_as__name', 'is_finished']
|
||||
search_fields = ['command', 'user__name', 'run_as__name']
|
||||
filterset_fields = [
|
||||
'user__name', 'user__username', 'command',
|
||||
'run_as__name', 'run_as__username', 'is_finished'
|
||||
]
|
||||
search_fields = [
|
||||
'command', 'user__name', 'user__username',
|
||||
'run_as__name', 'run_as__username',
|
||||
]
|
||||
ordering = ['-date_created']
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -82,7 +82,7 @@ class CommandExecutionSerializer(serializers.ModelSerializer):
|
||||
model = CommandExecution
|
||||
fields_mini = ['id']
|
||||
fields_small = fields_mini + [
|
||||
'run_as', 'command', 'user', 'is_finished',
|
||||
'run_as', 'command', 'is_finished', 'user',
|
||||
'date_start', 'result', 'is_success', 'org_id'
|
||||
]
|
||||
fields = fields_small + ['hosts', 'hosts_display', 'run_as_display', 'user_display']
|
||||
|
@ -57,6 +57,7 @@ class AuthBackendLabelMapping(LazyObject):
|
||||
backend_label_mapping[settings.AUTH_BACKEND_PUBKEY] = _('SSH Key')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_MODEL] = _('Password')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_SSO] = _('SSO')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_AUTH_TOKEN] = _('Auth Token')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_WECOM] = _('WeCom')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_DINGTALK] = _('DingTalk')
|
||||
return backend_label_mapping
|
||||
@ -156,12 +157,13 @@ def get_login_backend(request):
|
||||
return backend_label
|
||||
|
||||
|
||||
def generate_data(username, request):
|
||||
def generate_data(username, request, login_type=None):
|
||||
user_agent = request.META.get('HTTP_USER_AGENT', '')
|
||||
login_ip = get_request_ip(request) or '0.0.0.0'
|
||||
if isinstance(request, Request):
|
||||
|
||||
if login_type is None and isinstance(request, Request):
|
||||
login_type = request.META.get('HTTP_X_JMS_LOGIN_TYPE', 'U')
|
||||
else:
|
||||
if login_type is None:
|
||||
login_type = 'W'
|
||||
|
||||
data = {
|
||||
@ -176,9 +178,9 @@ def generate_data(username, request):
|
||||
|
||||
|
||||
@receiver(post_auth_success)
|
||||
def on_user_auth_success(sender, user, request, **kwargs):
|
||||
def on_user_auth_success(sender, user, request, login_type=None, **kwargs):
|
||||
logger.debug('User login success: {}'.format(user.username))
|
||||
data = generate_data(user.username, request)
|
||||
data = generate_data(user.username, request, login_type=login_type)
|
||||
data.update({'mfa': int(user.mfa_enabled), 'status': True})
|
||||
write_login_log(**data)
|
||||
|
||||
|
@ -6,12 +6,14 @@ from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework import serializers
|
||||
|
||||
from authentication.signals import post_auth_failed, post_auth_success
|
||||
from common.utils import get_logger, random_string
|
||||
from common.drf.api import SerializerMixin2
|
||||
from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser
|
||||
@ -51,10 +53,6 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView
|
||||
return True
|
||||
|
||||
def create_token(self, user, asset, application, system_user, ttl=5*60):
|
||||
if not settings.CONNECTION_TOKEN_ENABLED:
|
||||
raise PermissionDenied('Connection token disabled')
|
||||
if not user:
|
||||
user = self.request.user
|
||||
if not self.request.user.is_superuser and user != self.request.user:
|
||||
raise PermissionDenied('Only super user can create user token')
|
||||
self.check_resource_permission(user, asset, application, system_user)
|
||||
@ -233,12 +231,24 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView
|
||||
|
||||
if asset and not asset.is_active:
|
||||
raise serializers.ValidationError("Asset disabled")
|
||||
|
||||
try:
|
||||
self.check_resource_permission(user, asset, app, system_user)
|
||||
except PermissionDenied:
|
||||
raise serializers.ValidationError('Permission expired or invalid')
|
||||
return value, user, system_user, asset, app
|
||||
|
||||
@action(methods=['POST'], detail=False, permission_classes=[IsSuperUserOrAppUser], url_path='secret-info/detail')
|
||||
def get_secret_detail(self, request, *args, **kwargs):
|
||||
token = request.data.get('token', '')
|
||||
value, user, system_user, asset, app = self.valid_token(token)
|
||||
try:
|
||||
value, user, system_user, asset, app = self.valid_token(token)
|
||||
except serializers.ValidationError as e:
|
||||
post_auth_failed.send(
|
||||
sender=self.__class__, username='', request=self.request,
|
||||
reason=_('Invalid token')
|
||||
)
|
||||
raise e
|
||||
|
||||
data = dict(user=user, system_user=system_user)
|
||||
if asset:
|
||||
@ -252,6 +262,9 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView
|
||||
data['type'] = 'application'
|
||||
data.update(app_detail)
|
||||
|
||||
self.request.session['auth_backend'] = settings.AUTH_BACKEND_AUTH_TOKEN
|
||||
post_auth_success.send(sender=self.__class__, user=user, request=self.request, login_type='T')
|
||||
|
||||
serializer = self.get_serializer(data)
|
||||
return Response(data=serializer.data, status=200)
|
||||
|
||||
|
@ -228,3 +228,11 @@ class DingTalkAuthentication(ModelBackend):
|
||||
|
||||
def authenticate(self, request, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationTokenAuthentication(ModelBackend):
|
||||
"""
|
||||
什么也不做呀😺
|
||||
"""
|
||||
def authenticate(self, request, **kwargs):
|
||||
pass
|
||||
|
@ -2,9 +2,11 @@ from channels.auth import AuthMiddlewareStack
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
|
||||
from ops.urls.ws_urls import urlpatterns as ops_urlpatterns
|
||||
from notifications.urls.ws_urls import urlpatterns as notifications_urlpatterns
|
||||
|
||||
urlpatterns = []
|
||||
urlpatterns += ops_urlpatterns
|
||||
urlpatterns += ops_urlpatterns \
|
||||
+ notifications_urlpatterns
|
||||
|
||||
application = ProtocolTypeRouter({
|
||||
'websocket': AuthMiddlewareStack(
|
||||
|
@ -130,10 +130,12 @@ AUTH_BACKEND_CAS = 'authentication.backends.cas.CASBackend'
|
||||
AUTH_BACKEND_SSO = 'authentication.backends.api.SSOAuthentication'
|
||||
AUTH_BACKEND_WECOM = 'authentication.backends.api.WeComAuthentication'
|
||||
AUTH_BACKEND_DINGTALK = 'authentication.backends.api.DingTalkAuthentication'
|
||||
AUTH_BACKEND_AUTH_TOKEN = 'authentication.backends.api.AuthorizationTokenAuthentication'
|
||||
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_WECOM, AUTH_BACKEND_DINGTALK
|
||||
AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_WECOM,
|
||||
AUTH_BACKEND_DINGTALK, AUTH_BACKEND_AUTH_TOKEN
|
||||
]
|
||||
|
||||
if AUTH_CAS:
|
||||
|
@ -48,7 +48,7 @@ INSTALLED_APPS = [
|
||||
'applications.apps.ApplicationsConfig',
|
||||
'tickets.apps.TicketsConfig',
|
||||
'acls.apps.AclsConfig',
|
||||
'notifications',
|
||||
'notifications.apps.NotificationsConfig',
|
||||
'common.apps.CommonConfig',
|
||||
'jms_oidc_rp',
|
||||
'rest_framework',
|
||||
|
@ -23,7 +23,7 @@ api_v1 = [
|
||||
path('applications/', include('applications.urls.api_urls', namespace='api-applications')),
|
||||
path('tickets/', include('tickets.urls.api_urls', namespace='api-tickets')),
|
||||
path('acls/', include('acls.urls.api_urls', namespace='api-acls')),
|
||||
path('notifications/', include('notifications.urls.notifications', namespace='api-notifications')),
|
||||
path('notifications/', include('notifications.urls.api_urls', namespace='api-notifications')),
|
||||
path('prometheus/metrics/', api.PrometheusMetricsApi.as_view()),
|
||||
]
|
||||
|
||||
|
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-04 11:29+0800\n"
|
||||
"POT-Creation-Date: 2021-06-11 11:06+0800\n"
|
||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||
@ -121,7 +121,7 @@ msgstr "系统用户"
|
||||
#: applications/serializers/attrs/application_category/remote_app.py:33
|
||||
#: assets/models/asset.py:355 assets/models/authbook.py:26
|
||||
#: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:34
|
||||
#: assets/serializers/asset_user.py:48 assets/serializers/asset_user.py:90
|
||||
#: assets/serializers/asset_user.py:48 assets/serializers/asset_user.py:91
|
||||
#: assets/serializers/system_user.py:202 audits/models.py:38
|
||||
#: perms/models/asset_permission.py:99 templates/index.html:82
|
||||
#: terminal/backends/command/models.py:19
|
||||
@ -158,7 +158,7 @@ msgstr ""
|
||||
#: acls/serializers/login_acl.py:30 acls/serializers/login_asset_acl.py:31
|
||||
#: applications/serializers/attrs/application_type/mysql_workbench.py:18
|
||||
#: assets/models/asset.py:183 assets/models/domain.py:52
|
||||
#: assets/serializers/asset_user.py:47 settings/serializers/settings.py:117
|
||||
#: assets/serializers/asset_user.py:47 settings/serializers/settings.py:113
|
||||
#: users/templates/users/_granted_assets.html:26
|
||||
#: users/templates/users/user_asset_permission.html:156
|
||||
msgid "IP"
|
||||
@ -199,7 +199,7 @@ msgstr ""
|
||||
|
||||
#: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:184
|
||||
#: assets/serializers/asset_user.py:46 assets/serializers/gathered_user.py:23
|
||||
#: settings/serializers/settings.py:116
|
||||
#: settings/serializers/settings.py:112
|
||||
#: users/templates/users/_granted_assets.html:25
|
||||
#: users/templates/users/user_asset_permission.html:157
|
||||
msgid "Hostname"
|
||||
@ -272,14 +272,32 @@ msgstr "网域"
|
||||
msgid "Attrs"
|
||||
msgstr ""
|
||||
|
||||
#: applications/serializers/application.py:47
|
||||
#: applications/serializers/application.py:48
|
||||
msgid "Category(Display)"
|
||||
msgstr "类别 (显示名称)"
|
||||
|
||||
#: applications/serializers/application.py:48
|
||||
#: applications/serializers/application.py:49
|
||||
msgid "Type(Dispaly)"
|
||||
msgstr "类型 (显示名称)"
|
||||
|
||||
#: applications/serializers/application.py:72
|
||||
msgid "Application name"
|
||||
msgstr "应用名称"
|
||||
|
||||
#: applications/serializers/application.py:73
|
||||
msgid "Application category"
|
||||
msgstr "应用类别"
|
||||
|
||||
#: applications/serializers/application.py:74
|
||||
msgid "Application type"
|
||||
msgstr "应用类型"
|
||||
|
||||
#: applications/serializers/application.py:87
|
||||
#: assets/serializers/system_user.py:49 assets/serializers/system_user.py:177
|
||||
#: assets/serializers/system_user.py:203
|
||||
msgid "Login mode display"
|
||||
msgstr "登录模式(显示名称)"
|
||||
|
||||
#: applications/serializers/attrs/application_category/cloud.py:9
|
||||
#: assets/models/cluster.py:40
|
||||
msgid "Cluster"
|
||||
@ -316,10 +334,10 @@ msgstr "目标URL"
|
||||
#: applications/serializers/attrs/application_type/custom.py:25
|
||||
#: applications/serializers/attrs/application_type/mysql_workbench.py:34
|
||||
#: applications/serializers/attrs/application_type/vmware_client.py:30
|
||||
#: assets/models/base.py:251 assets/serializers/asset_user.py:77
|
||||
#: assets/models/base.py:251 assets/serializers/asset_user.py:78
|
||||
#: audits/signals_handler.py:58 authentication/forms.py:22
|
||||
#: authentication/templates/authentication/login.html:164
|
||||
#: settings/serializers/settings.py:98 users/forms/profile.py:21
|
||||
#: settings/serializers/settings.py:94 users/forms/profile.py:21
|
||||
#: users/templates/users/user_otp_check_password.html:13
|
||||
#: users/templates/users/user_password_update.html:43
|
||||
#: users/templates/users/user_password_verify.html:18
|
||||
@ -822,12 +840,12 @@ msgstr "后端"
|
||||
msgid "Source"
|
||||
msgstr "来源"
|
||||
|
||||
#: assets/serializers/asset_user.py:81 users/forms/profile.py:160
|
||||
#: assets/serializers/asset_user.py:82 users/forms/profile.py:160
|
||||
#: users/models/user.py:580 users/templates/users/user_password_update.html:48
|
||||
msgid "Public key"
|
||||
msgstr "SSH公钥"
|
||||
|
||||
#: assets/serializers/asset_user.py:85 users/models/user.py:577
|
||||
#: assets/serializers/asset_user.py:86 users/models/user.py:577
|
||||
msgid "Private key"
|
||||
msgstr "ssh私钥"
|
||||
|
||||
@ -875,11 +893,6 @@ msgstr "同级别节点名字不能重复"
|
||||
msgid "Nodes amount"
|
||||
msgstr "节点数量"
|
||||
|
||||
#: assets/serializers/system_user.py:49 assets/serializers/system_user.py:177
|
||||
#: assets/serializers/system_user.py:203
|
||||
msgid "Login mode display"
|
||||
msgstr "登录模式(显示名称)"
|
||||
|
||||
#: assets/serializers/system_user.py:51 assets/serializers/system_user.py:179
|
||||
msgid "Ad domain"
|
||||
msgstr "Ad 网域"
|
||||
@ -2049,7 +2062,7 @@ msgstr "应用程序"
|
||||
msgid "Application permission"
|
||||
msgstr "应用管理"
|
||||
|
||||
#: perms/models/asset_permission.py:37 settings/serializers/settings.py:121
|
||||
#: perms/models/asset_permission.py:37 settings/serializers/settings.py:117
|
||||
msgid "All"
|
||||
msgstr "全部"
|
||||
|
||||
@ -2194,27 +2207,19 @@ msgstr "当前站点URL"
|
||||
msgid "eg: http://dev.jumpserver.org:8080"
|
||||
msgstr "如: http://dev.jumpserver.org:8080"
|
||||
|
||||
#: settings/serializers/settings.py:19
|
||||
msgid "RDP address"
|
||||
msgstr "RDP 地址"
|
||||
|
||||
#: settings/serializers/settings.py:21
|
||||
msgid "RDP visit address, eg: dev.jumpserver.org:3389"
|
||||
msgstr "RDP 访问地址, 如: dev.jumpserver.org:3389"
|
||||
|
||||
#: settings/serializers/settings.py:24
|
||||
#: settings/serializers/settings.py:20
|
||||
msgid "User guide url"
|
||||
msgstr "用户向导URL"
|
||||
|
||||
#: settings/serializers/settings.py:25
|
||||
#: settings/serializers/settings.py:21
|
||||
msgid "User first login update profile done redirect to it"
|
||||
msgstr "用户第一次登录,修改profile后重定向到地址, 可以是 wiki 或 其他说明文档"
|
||||
|
||||
#: settings/serializers/settings.py:28
|
||||
#: settings/serializers/settings.py:24
|
||||
msgid "Forgot password url"
|
||||
msgstr "忘记密码URL"
|
||||
|
||||
#: settings/serializers/settings.py:29
|
||||
#: settings/serializers/settings.py:25
|
||||
msgid ""
|
||||
"The forgot password url on login page, If you use ldap or cas external "
|
||||
"authentication, you can set it"
|
||||
@ -2222,138 +2227,138 @@ msgstr ""
|
||||
"登录页面忘记密码URL, 如果使用了 LDAP, OPENID 等外部认证系统,可以自定义用户重"
|
||||
"置密码访问的地址"
|
||||
|
||||
#: settings/serializers/settings.py:33
|
||||
#: settings/serializers/settings.py:29
|
||||
msgid "Global organization name"
|
||||
msgstr "全局组织名"
|
||||
|
||||
#: settings/serializers/settings.py:34
|
||||
#: settings/serializers/settings.py:30
|
||||
msgid "The name of global organization to display"
|
||||
msgstr "全局组织的显示名称,默认为 全局组织"
|
||||
|
||||
#: settings/serializers/settings.py:41
|
||||
#: settings/serializers/settings.py:37
|
||||
msgid "SMTP host"
|
||||
msgstr "SMTP 主机"
|
||||
|
||||
#: settings/serializers/settings.py:42
|
||||
#: settings/serializers/settings.py:38
|
||||
msgid "SMTP port"
|
||||
msgstr "SMTP 端口"
|
||||
|
||||
#: settings/serializers/settings.py:43
|
||||
#: settings/serializers/settings.py:39
|
||||
msgid "SMTP account"
|
||||
msgstr "SMTP 账号"
|
||||
|
||||
#: settings/serializers/settings.py:45
|
||||
#: settings/serializers/settings.py:41
|
||||
msgid "SMTP password"
|
||||
msgstr "SMTP 密码"
|
||||
|
||||
#: settings/serializers/settings.py:46
|
||||
#: settings/serializers/settings.py:42
|
||||
msgid "Tips: Some provider use token except password"
|
||||
msgstr "提示:一些邮件提供商需要输入的是授权码"
|
||||
|
||||
#: settings/serializers/settings.py:49
|
||||
#: settings/serializers/settings.py:45
|
||||
msgid "Send user"
|
||||
msgstr "发件人"
|
||||
|
||||
#: settings/serializers/settings.py:50
|
||||
#: settings/serializers/settings.py:46
|
||||
msgid "Tips: Send mail account, default SMTP account as the send account"
|
||||
msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号"
|
||||
|
||||
#: settings/serializers/settings.py:53
|
||||
#: settings/serializers/settings.py:49
|
||||
msgid "Test recipient"
|
||||
msgstr "测试收件人"
|
||||
|
||||
#: settings/serializers/settings.py:54
|
||||
#: settings/serializers/settings.py:50
|
||||
msgid "Tips: Used only as a test mail recipient"
|
||||
msgstr "提示:仅用来作为测试邮件收件人"
|
||||
|
||||
#: settings/serializers/settings.py:57
|
||||
#: settings/serializers/settings.py:53
|
||||
msgid "Use SSL"
|
||||
msgstr "使用 SSL"
|
||||
|
||||
#: settings/serializers/settings.py:58
|
||||
#: settings/serializers/settings.py:54
|
||||
msgid "If SMTP port is 465, may be select"
|
||||
msgstr "如果SMTP端口是465,通常需要启用 SSL"
|
||||
|
||||
#: settings/serializers/settings.py:61
|
||||
#: settings/serializers/settings.py:57
|
||||
msgid "Use TLS"
|
||||
msgstr "使用 TLS"
|
||||
|
||||
#: settings/serializers/settings.py:62
|
||||
#: settings/serializers/settings.py:58
|
||||
msgid "If SMTP port is 587, may be select"
|
||||
msgstr "如果SMTP端口是587,通常需要启用 TLS"
|
||||
|
||||
#: settings/serializers/settings.py:65
|
||||
#: settings/serializers/settings.py:61
|
||||
msgid "Subject prefix"
|
||||
msgstr "主题前缀"
|
||||
|
||||
#: settings/serializers/settings.py:72
|
||||
#: settings/serializers/settings.py:68
|
||||
msgid "Create user email subject"
|
||||
msgstr "邮件主题"
|
||||
|
||||
#: settings/serializers/settings.py:73
|
||||
#: settings/serializers/settings.py:69
|
||||
msgid ""
|
||||
"Tips: When creating a user, send the subject of the email (eg:Create account "
|
||||
"successfully)"
|
||||
msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)"
|
||||
|
||||
#: settings/serializers/settings.py:77
|
||||
#: settings/serializers/settings.py:73
|
||||
msgid "Create user honorific"
|
||||
msgstr "邮件的敬语"
|
||||
|
||||
#: settings/serializers/settings.py:78
|
||||
#: settings/serializers/settings.py:74
|
||||
msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)"
|
||||
msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)"
|
||||
|
||||
#: settings/serializers/settings.py:82
|
||||
#: settings/serializers/settings.py:78
|
||||
msgid "Create user email content"
|
||||
msgstr "邮件的内容"
|
||||
|
||||
#: settings/serializers/settings.py:83
|
||||
#: settings/serializers/settings.py:79
|
||||
msgid "Tips:When creating a user, send the content of the email"
|
||||
msgstr "提示: 创建用户时,发送设置密码邮件的内容"
|
||||
|
||||
#: settings/serializers/settings.py:86
|
||||
#: settings/serializers/settings.py:82
|
||||
msgid "Signature"
|
||||
msgstr "署名"
|
||||
|
||||
#: settings/serializers/settings.py:87
|
||||
#: settings/serializers/settings.py:83
|
||||
msgid "Tips: Email signature (eg:jumpserver)"
|
||||
msgstr "邮件署名 (如:jumpserver)"
|
||||
|
||||
#: settings/serializers/settings.py:95
|
||||
#: settings/serializers/settings.py:91
|
||||
msgid "LDAP server"
|
||||
msgstr "LDAP 地址"
|
||||
|
||||
#: settings/serializers/settings.py:95
|
||||
#: settings/serializers/settings.py:91
|
||||
msgid "eg: ldap://localhost:389"
|
||||
msgstr ""
|
||||
|
||||
#: settings/serializers/settings.py:97
|
||||
#: settings/serializers/settings.py:93
|
||||
msgid "Bind DN"
|
||||
msgstr "绑定 DN"
|
||||
|
||||
#: settings/serializers/settings.py:100
|
||||
#: settings/serializers/settings.py:96
|
||||
msgid "User OU"
|
||||
msgstr "用户 OU"
|
||||
|
||||
#: settings/serializers/settings.py:101
|
||||
#: settings/serializers/settings.py:97
|
||||
msgid "Use | split multi OUs"
|
||||
msgstr "多个 OU 使用 | 分割"
|
||||
|
||||
#: settings/serializers/settings.py:104
|
||||
#: settings/serializers/settings.py:100
|
||||
msgid "User search filter"
|
||||
msgstr "用户过滤器"
|
||||
|
||||
#: settings/serializers/settings.py:105
|
||||
#: settings/serializers/settings.py:101
|
||||
#, python-format
|
||||
msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)"
|
||||
msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)"
|
||||
|
||||
#: settings/serializers/settings.py:108
|
||||
#: settings/serializers/settings.py:104
|
||||
msgid "User attr map"
|
||||
msgstr "用户属性映射"
|
||||
|
||||
#: settings/serializers/settings.py:109
|
||||
#: settings/serializers/settings.py:105
|
||||
msgid ""
|
||||
"User attr map present how to map LDAP user attr to jumpserver, username,name,"
|
||||
"email is jumpserver attr"
|
||||
@ -2361,23 +2366,23 @@ msgstr ""
|
||||
"用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name,"
|
||||
"email 是jumpserver的用户需要属性"
|
||||
|
||||
#: settings/serializers/settings.py:111
|
||||
#: settings/serializers/settings.py:107
|
||||
msgid "Enable LDAP auth"
|
||||
msgstr "启用 LDAP 认证"
|
||||
|
||||
#: settings/serializers/settings.py:122
|
||||
#: settings/serializers/settings.py:118
|
||||
msgid "Auto"
|
||||
msgstr "自动"
|
||||
|
||||
#: settings/serializers/settings.py:128
|
||||
#: settings/serializers/settings.py:124
|
||||
msgid "Password auth"
|
||||
msgstr "密码认证"
|
||||
|
||||
#: settings/serializers/settings.py:130
|
||||
#: settings/serializers/settings.py:126
|
||||
msgid "Public key auth"
|
||||
msgstr "密钥认证"
|
||||
|
||||
#: settings/serializers/settings.py:131
|
||||
#: settings/serializers/settings.py:127
|
||||
msgid ""
|
||||
"Tips: If use other auth method, like AD/LDAP, you should disable this to "
|
||||
"avoid being able to log in after deleting"
|
||||
@ -2385,19 +2390,19 @@ msgstr ""
|
||||
"提示:如果你使用其它认证方式,如 AD/LDAP,你应该禁用此项,以避免第三方系统删"
|
||||
"除后,还可以登录"
|
||||
|
||||
#: settings/serializers/settings.py:134
|
||||
#: settings/serializers/settings.py:130
|
||||
msgid "List sort by"
|
||||
msgstr "资产列表排序"
|
||||
|
||||
#: settings/serializers/settings.py:135
|
||||
#: settings/serializers/settings.py:131
|
||||
msgid "List page size"
|
||||
msgstr "资产列表每页数量"
|
||||
|
||||
#: settings/serializers/settings.py:137
|
||||
#: settings/serializers/settings.py:133
|
||||
msgid "Session keep duration"
|
||||
msgstr "会话日志保存时间"
|
||||
|
||||
#: settings/serializers/settings.py:138
|
||||
#: settings/serializers/settings.py:134
|
||||
msgid ""
|
||||
"Units: days, Session, record, command will be delete if more than duration, "
|
||||
"only in database"
|
||||
@ -2405,64 +2410,72 @@ msgstr ""
|
||||
"单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不"
|
||||
"受影响)"
|
||||
|
||||
#: settings/serializers/settings.py:140
|
||||
#: settings/serializers/settings.py:136
|
||||
msgid "Telnet login regex"
|
||||
msgstr "Telnet 成功正则表达式"
|
||||
|
||||
#: settings/serializers/settings.py:145
|
||||
#: settings/serializers/settings.py:138
|
||||
msgid "RDP address"
|
||||
msgstr "RDP 地址"
|
||||
|
||||
#: settings/serializers/settings.py:141
|
||||
msgid "RDP visit address, eg: dev.jumpserver.org:3389"
|
||||
msgstr "RDP 访问地址, 如: dev.jumpserver.org:3389"
|
||||
|
||||
#: settings/serializers/settings.py:147
|
||||
msgid "Global MFA auth"
|
||||
msgstr "全局启用 MFA 认证"
|
||||
|
||||
#: settings/serializers/settings.py:146
|
||||
#: settings/serializers/settings.py:148
|
||||
msgid "All user enable MFA"
|
||||
msgstr "强制所有用户启用多因子认证"
|
||||
|
||||
#: settings/serializers/settings.py:149
|
||||
#: settings/serializers/settings.py:151
|
||||
msgid "Batch command execution"
|
||||
msgstr "批量命令执行"
|
||||
|
||||
#: settings/serializers/settings.py:150
|
||||
#: settings/serializers/settings.py:152
|
||||
msgid "Allow user run batch command or not using ansible"
|
||||
msgstr "是否允许用户使用 ansible 执行批量命令"
|
||||
|
||||
#: settings/serializers/settings.py:153
|
||||
#: settings/serializers/settings.py:155
|
||||
msgid "Enable terminal register"
|
||||
msgstr "终端注册"
|
||||
|
||||
#: settings/serializers/settings.py:154
|
||||
#: settings/serializers/settings.py:156
|
||||
msgid ""
|
||||
"Allow terminal register, after all terminal setup, you should disable this "
|
||||
"for security"
|
||||
msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭"
|
||||
|
||||
#: settings/serializers/settings.py:158
|
||||
#: settings/serializers/settings.py:160
|
||||
msgid "Limit the number of login failures"
|
||||
msgstr "限制登录失败次数"
|
||||
|
||||
#: settings/serializers/settings.py:162
|
||||
#: settings/serializers/settings.py:164
|
||||
msgid "Block logon interval"
|
||||
msgstr "禁止登录时间间隔"
|
||||
|
||||
#: settings/serializers/settings.py:163
|
||||
#: settings/serializers/settings.py:165
|
||||
msgid ""
|
||||
"Tip: (unit/minute) if the user has failed to log in for a limited number of "
|
||||
"times, no login is allowed during this time interval."
|
||||
msgstr ""
|
||||
"提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录"
|
||||
|
||||
#: settings/serializers/settings.py:167
|
||||
#: settings/serializers/settings.py:169
|
||||
msgid "Connection max idle time"
|
||||
msgstr "连接最大空闲时间"
|
||||
|
||||
#: settings/serializers/settings.py:168
|
||||
#: settings/serializers/settings.py:170
|
||||
msgid "If idle time more than it, disconnect connection Unit: minute"
|
||||
msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)"
|
||||
|
||||
#: settings/serializers/settings.py:172
|
||||
#: settings/serializers/settings.py:174
|
||||
msgid "User password expiration"
|
||||
msgstr "用户密码过期时间"
|
||||
|
||||
#: settings/serializers/settings.py:173
|
||||
#: settings/serializers/settings.py:175
|
||||
msgid ""
|
||||
"Tip: (unit: day) If the user does not update the password during the time, "
|
||||
"the user password will expire failure;The password expiration reminder mail "
|
||||
@ -2472,53 +2485,53 @@ msgstr ""
|
||||
"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期"
|
||||
"提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户"
|
||||
|
||||
#: settings/serializers/settings.py:177
|
||||
#: settings/serializers/settings.py:179
|
||||
msgid "Number of repeated historical passwords"
|
||||
msgstr "不能设置近几次密码"
|
||||
|
||||
#: settings/serializers/settings.py:178
|
||||
#: settings/serializers/settings.py:180
|
||||
msgid ""
|
||||
"Tip: When the user resets the password, it cannot be the previous n "
|
||||
"historical passwords of the user"
|
||||
msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码"
|
||||
|
||||
#: settings/serializers/settings.py:182
|
||||
#: settings/serializers/settings.py:184
|
||||
msgid "Password minimum length"
|
||||
msgstr "密码最小长度"
|
||||
|
||||
#: settings/serializers/settings.py:185
|
||||
#: settings/serializers/settings.py:187
|
||||
msgid "Must contain capital"
|
||||
msgstr "必须包含大写字符"
|
||||
|
||||
#: settings/serializers/settings.py:187
|
||||
#: settings/serializers/settings.py:189
|
||||
msgid "Must contain lowercase"
|
||||
msgstr "必须包含小写字符"
|
||||
|
||||
#: settings/serializers/settings.py:188
|
||||
#: settings/serializers/settings.py:190
|
||||
msgid "Must contain numeric"
|
||||
msgstr "必须包含数字"
|
||||
|
||||
#: settings/serializers/settings.py:189
|
||||
#: settings/serializers/settings.py:191
|
||||
msgid "Must contain special"
|
||||
msgstr "必须包含特殊字符"
|
||||
|
||||
#: settings/serializers/settings.py:190
|
||||
#: settings/serializers/settings.py:192
|
||||
msgid "Insecure command alert"
|
||||
msgstr "危险命令告警"
|
||||
|
||||
#: settings/serializers/settings.py:192
|
||||
#: settings/serializers/settings.py:194
|
||||
msgid "Email recipient"
|
||||
msgstr "邮件收件人"
|
||||
|
||||
#: settings/serializers/settings.py:193
|
||||
#: settings/serializers/settings.py:195
|
||||
msgid "Multiple user using , split"
|
||||
msgstr "多个用户,使用 , 分割"
|
||||
|
||||
#: settings/serializers/settings.py:201
|
||||
#: settings/serializers/settings.py:203
|
||||
msgid "Enable WeCom Auth"
|
||||
msgstr "启用企业微信认证"
|
||||
|
||||
#: settings/serializers/settings.py:208
|
||||
#: settings/serializers/settings.py:210
|
||||
msgid "Enable DingTalk Auth"
|
||||
msgstr "启用钉钉认证"
|
||||
|
||||
|
@ -10,7 +10,7 @@ from ..serializers import (
|
||||
SiteMessageDetailSerializer, SiteMessageIdsSerializer,
|
||||
SiteMessageSendSerializer,
|
||||
)
|
||||
from ..site_msg import SiteMessage
|
||||
from ..site_msg import SiteMessageUtil
|
||||
from ..filters import SiteMsgFilter
|
||||
|
||||
__all__ = ('SiteMessageViewSet', )
|
||||
@ -30,15 +30,15 @@ class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JmsGenericViewSet):
|
||||
has_read = self.request.query_params.get('has_read')
|
||||
|
||||
if has_read is None:
|
||||
msgs = SiteMessage.get_user_all_msgs(user.id)
|
||||
msgs = SiteMessageUtil.get_user_all_msgs(user.id)
|
||||
else:
|
||||
msgs = SiteMessage.filter_user_msgs(user.id, has_read=is_true(has_read))
|
||||
msgs = SiteMessageUtil.filter_user_msgs(user.id, has_read=is_true(has_read))
|
||||
return msgs
|
||||
|
||||
@action(methods=[GET], detail=False, url_path='unread-total')
|
||||
def unread_total(self, request, **kwargs):
|
||||
user = request.user
|
||||
msgs = SiteMessage.filter_user_msgs(user.id, has_read=False)
|
||||
msgs = SiteMessageUtil.filter_user_msgs(user.id, has_read=False)
|
||||
return Response(data={'total': msgs.count()})
|
||||
|
||||
@action(methods=[PATCH], detail=False, url_path='mark-as-read')
|
||||
@ -47,12 +47,12 @@ class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JmsGenericViewSet):
|
||||
seri = self.get_serializer(data=request.data)
|
||||
seri.is_valid(raise_exception=True)
|
||||
ids = seri.validated_data['ids']
|
||||
SiteMessage.mark_msgs_as_read(user.id, ids)
|
||||
SiteMessageUtil.mark_msgs_as_read(user.id, ids)
|
||||
return Response({'detail': 'ok'})
|
||||
|
||||
@action(methods=[POST], detail=False)
|
||||
def send(self, request, **kwargs):
|
||||
seri = self.get_serializer(data=request.data)
|
||||
seri.is_valid(raise_exception=True)
|
||||
SiteMessage.send_msg(**seri.validated_data, sender=request.user)
|
||||
SiteMessageUtil.send_msg(**seri.validated_data, sender=request.user)
|
||||
return Response({'detail': 'ok'})
|
||||
|
@ -3,3 +3,7 @@ from django.apps import AppConfig
|
||||
|
||||
class NotificationsConfig(AppConfig):
|
||||
name = 'notifications'
|
||||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
super().ready()
|
||||
|
@ -1,4 +1,4 @@
|
||||
from notifications.site_msg import SiteMessage as Client
|
||||
from notifications.site_msg import SiteMessageUtil as Client
|
||||
from .base import BackendBase
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SiteMessage',
|
||||
name='SiteMessageUtil',
|
||||
fields=[
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')),
|
||||
|
43
apps/notifications/signals_handler.py
Normal file
43
apps/notifications/signals_handler.py
Normal file
@ -0,0 +1,43 @@
|
||||
import json
|
||||
|
||||
from django.utils.functional import LazyObject
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from common.utils.connection import RedisPubSub
|
||||
from common.utils import get_logger
|
||||
from common.decorator import on_transaction_commit
|
||||
from .models import SiteMessage
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def new_site_msg_pub_sub():
|
||||
return RedisPubSub('notifications.SiteMessageCome')
|
||||
|
||||
|
||||
class NewSiteMsgSubPub(LazyObject):
|
||||
def _setup(self):
|
||||
self._wrapped = new_site_msg_pub_sub()
|
||||
|
||||
|
||||
new_site_msg_chan = NewSiteMsgSubPub()
|
||||
|
||||
|
||||
@receiver(post_save, sender=SiteMessage)
|
||||
@on_transaction_commit
|
||||
def on_site_message_create(sender, instance, created, **kwargs):
|
||||
if not created:
|
||||
return
|
||||
logger.debug('New site msg created, publish it')
|
||||
user_ids = instance.users.all().values_list('id', flat=True)
|
||||
user_ids = [str(i) for i in user_ids]
|
||||
data = {
|
||||
'id': str(instance.id),
|
||||
'subject': instance.subject,
|
||||
'message': instance.message,
|
||||
'users': user_ids
|
||||
}
|
||||
data = json.dumps(data)
|
||||
new_site_msg_chan.publish(data)
|
@ -1,11 +1,12 @@
|
||||
from django.db.models import F
|
||||
from django.db import transaction
|
||||
|
||||
from common.utils.timezone import now
|
||||
from users.models import User
|
||||
from .models import SiteMessage as SiteMessageModel, SiteMessageUsers
|
||||
|
||||
|
||||
class SiteMessage:
|
||||
class SiteMessageUtil:
|
||||
|
||||
@classmethod
|
||||
def send_msg(cls, subject, message, user_ids=(), group_ids=(),
|
||||
@ -13,24 +14,24 @@ class SiteMessage:
|
||||
if not any((user_ids, group_ids, is_broadcast)):
|
||||
raise ValueError('No recipient is specified')
|
||||
|
||||
site_msg = SiteMessageModel.objects.create(
|
||||
subject=subject, message=message,
|
||||
is_broadcast=is_broadcast, sender=sender,
|
||||
)
|
||||
with transaction.atomic():
|
||||
site_msg = SiteMessageModel.objects.create(
|
||||
subject=subject, message=message,
|
||||
is_broadcast=is_broadcast, sender=sender,
|
||||
)
|
||||
|
||||
if is_broadcast:
|
||||
user_ids = User.objects.all().values_list('id', flat=True)
|
||||
else:
|
||||
if group_ids:
|
||||
site_msg.groups.add(*group_ids)
|
||||
if is_broadcast:
|
||||
user_ids = User.objects.all().values_list('id', flat=True)
|
||||
else:
|
||||
if 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_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 = [*user_ids, *user_ids_from_group]
|
||||
|
||||
site_msg.users.add(*user_ids)
|
||||
site_msg.users.add(*user_ids)
|
||||
|
||||
@classmethod
|
||||
def get_user_all_msgs(cls, user_id):
|
||||
@ -72,14 +73,14 @@ class SiteMessage:
|
||||
|
||||
@classmethod
|
||||
def mark_msgs_as_read(cls, user_id, msg_ids):
|
||||
sitemsg_users = SiteMessageUsers.objects.filter(
|
||||
site_msg_users = SiteMessageUsers.objects.filter(
|
||||
user_id=user_id, sitemessage_id__in=msg_ids,
|
||||
has_read=False
|
||||
)
|
||||
|
||||
for sitemsg_user in sitemsg_users:
|
||||
sitemsg_user.has_read = True
|
||||
sitemsg_user.read_at = now()
|
||||
for site_msg_user in site_msg_users:
|
||||
site_msg_user.has_read = True
|
||||
site_msg_user.read_at = now()
|
||||
|
||||
SiteMessageUsers.objects.bulk_update(
|
||||
sitemsg_users, fields=('has_read', 'read_at'))
|
||||
site_msg_users, fields=('has_read', 'read_at'))
|
||||
|
9
apps/notifications/urls/ws_urls.py
Normal file
9
apps/notifications/urls/ws_urls.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django.urls import path
|
||||
|
||||
from .. import ws
|
||||
|
||||
app_name = 'notifications'
|
||||
|
||||
urlpatterns = [
|
||||
path('ws/notifications/site-msg/', ws.SiteMsgWebsocket, name='site-msg-ws'),
|
||||
]
|
70
apps/notifications/ws.py
Normal file
70
apps/notifications/ws.py
Normal file
@ -0,0 +1,70 @@
|
||||
import threading
|
||||
import json
|
||||
|
||||
from channels.generic.websocket import JsonWebsocketConsumer
|
||||
|
||||
from common.utils import get_logger
|
||||
from .models import SiteMessage
|
||||
from .site_msg import SiteMessageUtil
|
||||
from .signals_handler import new_site_msg_chan
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class SiteMsgWebsocket(JsonWebsocketConsumer):
|
||||
disconnected = False
|
||||
refresh_every_seconds = 10
|
||||
|
||||
def connect(self):
|
||||
user = self.scope["user"]
|
||||
if user.is_authenticated:
|
||||
self.accept()
|
||||
|
||||
thread = threading.Thread(target=self.unread_site_msg_count)
|
||||
thread.start()
|
||||
else:
|
||||
self.close()
|
||||
|
||||
def receive(self, text_data=None, bytes_data=None, **kwargs):
|
||||
data = json.loads(text_data)
|
||||
refresh_every_seconds = data.get('refresh_every_seconds')
|
||||
|
||||
try:
|
||||
refresh_every_seconds = int(refresh_every_seconds)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return
|
||||
|
||||
if refresh_every_seconds > 0:
|
||||
self.refresh_every_seconds = refresh_every_seconds
|
||||
|
||||
def send_unread_msg_count(self):
|
||||
user_id = self.scope["user"].id
|
||||
unread_count = SiteMessageUtil.get_user_unread_msgs_count(user_id)
|
||||
logger.debug('Send unread count to user: {} {}'.format(user_id, unread_count))
|
||||
self.send_json({'type': 'unread_count', 'unread_count': unread_count})
|
||||
|
||||
def unread_site_msg_count(self):
|
||||
user_id = str(self.scope["user"].id)
|
||||
self.send_unread_msg_count()
|
||||
|
||||
while not self.disconnected:
|
||||
subscribe = new_site_msg_chan.subscribe()
|
||||
for message in subscribe.listen():
|
||||
if message['type'] != 'message':
|
||||
continue
|
||||
try:
|
||||
msg = json.loads(message['data'].decode())
|
||||
logger.debug('New site msg recv, may be mine: {}'.format(msg))
|
||||
if not msg:
|
||||
continue
|
||||
users = msg.get('users', [])
|
||||
logger.debug('Message users: {}'.format(users))
|
||||
if user_id in users:
|
||||
self.send_unread_msg_count()
|
||||
except json.JSONDecoder as e:
|
||||
logger.debug('Decode json error: ', e)
|
||||
|
||||
def disconnect(self, close_code):
|
||||
self.disconnected = True
|
||||
self.close()
|
Loading…
Reference in New Issue
Block a user