From c3a54a8927e878ea376dd1c04fce2b107c0c08b5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Jul 2019 22:28:20 +0800 Subject: [PATCH] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9session?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/filters.py | 42 ++ apps/common/mixins/api.py | 4 +- apps/common/mixins/serializers.py | 5 +- apps/orgs/mixins.py | 216 ---------- apps/orgs/mixins/__init__.py | 7 + apps/orgs/mixins/api.py | 51 +++ apps/orgs/mixins/forms.py | 16 + apps/orgs/mixins/models.py | 115 +++++ apps/orgs/mixins/serializers.py | 56 +++ apps/static/js/jumpserver.js | 37 +- apps/terminal/api/session.py | 20 +- apps/terminal/models.py | 4 + apps/terminal/serializers/v1.py | 10 +- .../templates/terminal/command_list.html | 1 - .../templates/terminal/session_list.html | 392 +++++++++--------- apps/terminal/views/command.py | 10 +- apps/terminal/views/session.py | 42 +- 17 files changed, 571 insertions(+), 457 deletions(-) create mode 100644 apps/common/filters.py delete mode 100644 apps/orgs/mixins.py create mode 100644 apps/orgs/mixins/__init__.py create mode 100644 apps/orgs/mixins/api.py create mode 100644 apps/orgs/mixins/forms.py create mode 100644 apps/orgs/mixins/models.py create mode 100644 apps/orgs/mixins/serializers.py diff --git a/apps/common/filters.py b/apps/common/filters.py new file mode 100644 index 000000000..701f9c730 --- /dev/null +++ b/apps/common/filters.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import filters +from rest_framework.fields import DateTimeField +from rest_framework.serializers import ValidationError +import logging + +__all__ = ["DatetimeRangeFilter"] + + +class DatetimeRangeFilter(filters.BaseFilterBackend): + def filter_queryset(self, request, queryset, view): + if not hasattr(view, 'date_range_filter_fields'): + return queryset + try: + fields = dict(view.date_range_filter_fields) + except ValueError: + msg = "View {} datetime_filter_fields set is error".format(view.name) + logging.error(msg) + return queryset + kwargs = {} + for attr, date_range_keyword in fields.items(): + if len(date_range_keyword) != 2: + continue + for i, v in enumerate(date_range_keyword): + value = request.query_params.get(v) + if not value: + continue + try: + field = DateTimeField() + value = field.to_internal_value(value) + if i == 0: + lookup = "__gte" + else: + lookup = "__lte" + kwargs[attr+lookup] = value + except ValidationError as e: + print(e) + continue + if kwargs: + queryset = queryset.filter(**kwargs) + return queryset diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py index 410f077eb..2bd1b5a16 100644 --- a/apps/common/mixins/api.py +++ b/apps/common/mixins/api.py @@ -38,8 +38,10 @@ class IDInFilterMixin(object): class IDInCacheFilterMixin(object): def filter_queryset(self, queryset): - queryset = super(IDInCacheFilterMixin, self).filter_queryset(queryset) + queryset = super().filter_queryset(queryset) spm = self.request.query_params.get('spm') + if not spm: + return queryset cache_key = KEY_CACHE_RESOURCES_ID.format(spm) resources_id = cache.get(cache_key) if resources_id and isinstance(resources_id, list): diff --git a/apps/common/mixins/serializers.py b/apps/common/mixins/serializers.py index 009d0b994..008cd5eee 100644 --- a/apps/common/mixins/serializers.py +++ b/apps/common/mixins/serializers.py @@ -27,13 +27,14 @@ class BulkSerializerMixin(object): if all((isinstance(self.root, BulkListSerializer), id_attr, request_method in ('PUT', 'PATCH'))): - id_field = self.fields[id_attr] + id_field = self.fields.get("id") or self.fields.get('pk') if data.get("id"): id_value = id_field.to_internal_value(data.get("id")) else: id_value = id_field.to_internal_value(data.get("pk")) + print(">>>>>>>>>>>>>>>>>>>") + print(id_attr) ret[id_attr] = id_value - return ret diff --git a/apps/orgs/mixins.py b/apps/orgs/mixins.py deleted file mode 100644 index 228b5b102..000000000 --- a/apps/orgs/mixins.py +++ /dev/null @@ -1,216 +0,0 @@ -# -*- coding: utf-8 -*- -# -import traceback -from django.db import models -from django.utils.translation import ugettext_lazy as _ -from django.shortcuts import redirect, get_object_or_404 -from django import forms -from django.core.exceptions import ValidationError -from django.http.response import HttpResponseForbidden -from rest_framework import serializers -from rest_framework.validators import UniqueTogetherValidator - -from common.utils import get_logger -from common.validators import ProjectUniqueValidator -from common.mixins import BulkSerializerMixin -from .utils import ( - set_current_org, set_to_root_org, get_current_org, current_org, - get_current_org_id_for_serializer, -) -from .models import Organization - -logger = get_logger(__file__) - -__all__ = [ - 'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm', - 'RootOrgViewMixin', 'OrgMembershipSerializerMixin', - 'OrgMembershipModelViewSetMixin', 'OrgResourceSerializerMixin', - 'BulkOrgResourceSerializerMixin', 'BulkOrgResourceModelSerializer', -] - - -class OrgManager(models.Manager): - - def get_queryset(self): - queryset = super(OrgManager, self).get_queryset() - kwargs = {} - - _current_org = get_current_org() - if _current_org is None: - kwargs['id'] = None - elif _current_org.is_real(): - kwargs['org_id'] = _current_org.id - elif _current_org.is_default(): - queryset = queryset.filter(org_id="") - - # lines = traceback.format_stack() - # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>") - # for line in lines[-10:-5]: - # print(line) - # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<") - - queryset = queryset.filter(**kwargs) - return queryset - - def all(self): - if not current_org: - msg = 'You can `objects.set_current_org(org).all()` then run it' - return self - else: - return super(OrgManager, self).all() - - def set_current_org(self, org): - if isinstance(org, str): - org = Organization.get_instance(org) - set_current_org(org) - return self - - -class OrgModelMixin(models.Model): - org_id = models.CharField(max_length=36, blank=True, default='', - verbose_name=_("Organization"), db_index=True) - objects = OrgManager() - - sep = '@' - - def save(self, *args, **kwargs): - if current_org is not None and current_org.is_real(): - self.org_id = current_org.id - return super().save(*args, **kwargs) - - @property - def org(self): - from orgs.models import Organization - org = Organization.get_instance(self.org_id) - return org - - @property - def org_name(self): - return self.org.name - - @property - def fullname(self, attr=None): - name = '' - if attr and hasattr(self, attr): - name = getattr(self, attr) - elif hasattr(self, 'name'): - name = self.name - elif hasattr(self, 'hostname'): - name = self.hostname - if self.org.is_real(): - return name + self.sep + self.org_name - else: - return name - - def validate_unique(self, exclude=None): - """ - Check unique constraints on the model and raise ValidationError if any - failed. - Form 提交时会使用这个检验 - """ - self.org_id = current_org.id if current_org.is_real() else '' - if exclude and 'org_id' in exclude: - exclude.remove('org_id') - unique_checks, date_checks = self._get_unique_checks(exclude=exclude) - - errors = self._perform_unique_checks(unique_checks) - date_errors = self._perform_date_checks(date_checks) - - for k, v in date_errors.items(): - errors.setdefault(k, []).extend(v) - - if errors: - raise ValidationError(errors) - - class Meta: - abstract = True - - -class OrgViewGenericMixin: - def dispatch(self, request, *args, **kwargs): - if current_org is None: - return redirect('orgs:switch-a-org') - - if not current_org.can_admin_by(request.user): - if request.user.is_org_admin: - return redirect('orgs:switch-a-org') - return HttpResponseForbidden() - return super().dispatch(request, *args, **kwargs) - - -class RootOrgViewMixin: - def dispatch(self, request, *args, **kwargs): - set_to_root_org() - return super().dispatch(request, *args, **kwargs) - - -class OrgModelForm(forms.ModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - for name, field in self.fields.items(): - if not hasattr(field, 'queryset'): - continue - model = field.queryset.model - field.queryset = model.objects.all() - - -class OrgMembershipSerializerMixin: - def run_validation(self, initial_data=None): - initial_data['organization'] = str(self.context['org'].id) - return super().run_validation(initial_data) - - -class OrgMembershipModelViewSetMixin: - org = None - membership_class = None - lookup_field = 'user' - lookup_url_kwarg = 'user_id' - http_method_names = ['get', 'post', 'delete', 'head', 'options'] - - def dispatch(self, request, *args, **kwargs): - self.org = get_object_or_404(Organization, pk=kwargs.get('org_id')) - return super().dispatch(request, *args, **kwargs) - - def get_serializer_context(self): - context = super().get_serializer_context() - context['org'] = self.org - return context - - def get_queryset(self): - queryset = self.membership_class.objects.filter(organization=self.org) - return queryset - - -class OrgResourceSerializerMixin(serializers.Serializer): - """ - 通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id - (同时为serializer.is_valid()对Model的unique_together校验做准备) - 由于HiddenField字段不可读,API获取资产信息时获取不到org_id, - 但是coco需要资产的org_id字段,所以修改为CharField类型 - """ - org_id = serializers.ReadOnlyField(default=get_current_org_id_for_serializer, label=_("Organization")) - org_name = serializers.ReadOnlyField(label=_("Org name")) - - def get_validators(self): - _validators = super().get_validators() - validators = [] - - for v in _validators: - if isinstance(v, UniqueTogetherValidator) \ - and "org_id" in v.fields: - v = ProjectUniqueValidator(v.queryset, v.fields) - validators.append(v) - return validators - - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields.extend(["org_id", "org_name"]) - return fields - - -class BulkOrgResourceSerializerMixin(OrgResourceSerializerMixin, BulkSerializerMixin): - pass - - -class BulkOrgResourceModelSerializer(BulkOrgResourceSerializerMixin, serializers.ModelSerializer): - pass diff --git a/apps/orgs/mixins/__init__.py b/apps/orgs/mixins/__init__.py new file mode 100644 index 000000000..3d1a4a7db --- /dev/null +++ b/apps/orgs/mixins/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# + +from .models import * +from .serializers import * +from .forms import * +from .api import * diff --git a/apps/orgs/mixins/api.py b/apps/orgs/mixins/api.py new file mode 100644 index 000000000..5e0cef5c3 --- /dev/null +++ b/apps/orgs/mixins/api.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# +from django.shortcuts import get_object_or_404 +from rest_framework.viewsets import ModelViewSet +from rest_framework_bulk import BulkModelViewSet +from common.mixins import IDInCacheFilterMixin + +from ..utils import set_to_root_org +from ..models import Organization + +__all__ = [ + 'RootOrgViewMixin', 'OrgMembershipModelViewSetMixin', 'OrgModelViewSet', + 'OrgBulkModelViewSet', +] + + +class RootOrgViewMixin: + def dispatch(self, request, *args, **kwargs): + set_to_root_org() + return super().dispatch(request, *args, **kwargs) + + +class OrgModelViewSet(IDInCacheFilterMixin, ModelViewSet): + def get_queryset(self): + return super().get_queryset().all() + + +class OrgBulkModelViewSet(IDInCacheFilterMixin, BulkModelViewSet): + def get_queryset(self): + return super().get_queryset().all() + + +class OrgMembershipModelViewSetMixin: + org = None + membership_class = None + lookup_field = 'user' + lookup_url_kwarg = 'user_id' + http_method_names = ['get', 'post', 'delete', 'head', 'options'] + + def dispatch(self, request, *args, **kwargs): + self.org = get_object_or_404(Organization, pk=kwargs.get('org_id')) + return super().dispatch(request, *args, **kwargs) + + def get_serializer_context(self): + context = super().get_serializer_context() + context['org'] = self.org + return context + + def get_queryset(self): + queryset = self.membership_class.objects.filter(organization=self.org) + return queryset diff --git a/apps/orgs/mixins/forms.py b/apps/orgs/mixins/forms.py new file mode 100644 index 000000000..49ec106f1 --- /dev/null +++ b/apps/orgs/mixins/forms.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# + +from django import forms + +__all__ = ['OrgModelForm'] + + +class OrgModelForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for name, field in self.fields.items(): + if not hasattr(field, 'queryset'): + continue + model = field.queryset.model + field.queryset = model.objects.all() diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py new file mode 100644 index 000000000..d579bab37 --- /dev/null +++ b/apps/orgs/mixins/models.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# + +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError + +from common.utils import get_logger +from ..utils import ( + set_current_org, get_current_org, current_org, +) +from ..models import Organization + +logger = get_logger(__file__) + +__all__ = [ + 'OrgManager', 'OrgModelMixin', +] + + +class OrgManager(models.Manager): + + def get_queryset(self): + queryset = super(OrgManager, self).get_queryset() + kwargs = {} + + _current_org = get_current_org() + if _current_org is None: + kwargs['id'] = None + elif _current_org.is_real(): + kwargs['org_id'] = _current_org.id + elif _current_org.is_default(): + queryset = queryset.filter(org_id="") + + # lines = traceback.format_stack() + # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>") + # for line in lines[-10:-5]: + # print(line) + # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + + queryset = queryset.filter(**kwargs) + return queryset + + def all(self): + if not current_org: + msg = 'You can `objects.set_current_org(org).all()` then run it' + return self + else: + return super(OrgManager, self).all() + + def set_current_org(self, org): + if isinstance(org, str): + org = Organization.get_instance(org) + set_current_org(org) + return self + + +class OrgModelMixin(models.Model): + org_id = models.CharField(max_length=36, blank=True, default='', + verbose_name=_("Organization"), db_index=True) + objects = OrgManager() + + sep = '@' + + def save(self, *args, **kwargs): + if current_org is not None and current_org.is_real(): + self.org_id = current_org.id + return super().save(*args, **kwargs) + + @property + def org(self): + from orgs.models import Organization + org = Organization.get_instance(self.org_id) + return org + + @property + def org_name(self): + return self.org.name + + @property + def fullname(self, attr=None): + name = '' + if attr and hasattr(self, attr): + name = getattr(self, attr) + elif hasattr(self, 'name'): + name = self.name + elif hasattr(self, 'hostname'): + name = self.hostname + if self.org.is_real(): + return name + self.sep + self.org_name + else: + return name + + def validate_unique(self, exclude=None): + """ + Check unique constraints on the model and raise ValidationError if any + failed. + Form 提交时会使用这个检验 + """ + self.org_id = current_org.id if current_org.is_real() else '' + if exclude and 'org_id' in exclude: + exclude.remove('org_id') + unique_checks, date_checks = self._get_unique_checks(exclude=exclude) + + errors = self._perform_unique_checks(unique_checks) + date_errors = self._perform_date_checks(date_checks) + + for k, v in date_errors.items(): + errors.setdefault(k, []).extend(v) + + if errors: + raise ValidationError(errors) + + class Meta: + abstract = True diff --git a/apps/orgs/mixins/serializers.py b/apps/orgs/mixins/serializers.py new file mode 100644 index 000000000..ea4e4bd6b --- /dev/null +++ b/apps/orgs/mixins/serializers.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers +from rest_framework.validators import UniqueTogetherValidator + +from common.validators import ProjectUniqueValidator +from common.mixins import BulkSerializerMixin +from ..utils import get_current_org_id_for_serializer + + +__all__ = [ + "OrgResourceSerializerMixin", "BulkOrgResourceSerializerMixin", + "BulkOrgResourceModelSerializer", "OrgMembershipSerializerMixin" +] + + +class OrgResourceSerializerMixin(serializers.Serializer): + """ + 通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id + (同时为serializer.is_valid()对Model的unique_together校验做准备) + 由于HiddenField字段不可读,API获取资产信息时获取不到org_id, + 但是coco需要资产的org_id字段,所以修改为CharField类型 + """ + org_id = serializers.ReadOnlyField(default=get_current_org_id_for_serializer, label=_("Organization")) + org_name = serializers.ReadOnlyField(label=_("Org name")) + + def get_validators(self): + _validators = super().get_validators() + validators = [] + + for v in _validators: + if isinstance(v, UniqueTogetherValidator) \ + and "org_id" in v.fields: + v = ProjectUniqueValidator(v.queryset, v.fields) + validators.append(v) + return validators + + def get_field_names(self, declared_fields, info): + fields = super().get_field_names(declared_fields, info) + fields.extend(["org_id", "org_name"]) + return fields + + +class BulkOrgResourceSerializerMixin(OrgResourceSerializerMixin, BulkSerializerMixin): + pass + + +class BulkOrgResourceModelSerializer(BulkOrgResourceSerializerMixin, serializers.ModelSerializer): + pass + + +class OrgMembershipSerializerMixin: + def run_validation(self, initial_data=None): + initial_data['organization'] = str(self.context['org'].id) + return super().run_validation(initial_data) diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 1b8b9321e..b22516863 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -1108,9 +1108,44 @@ function formatDateAsCN(d) { function getUrlParams(url) { url = url.split("?"); - let params = ""; + var params = ""; if (url.length === 2){ params = url[1]; } return params +} + +function getTimeUnits(u) { + var units = { + "d": "天", + "h": "时", + "m": "分", + "s": "秒", + }; + if (navigator.language || "zh-CN") { + return units[u] + } + return u +} + +function timeOffset(a, b) { + var start = new Date(a); + var end = new Date(b); + var offset = (end - start)/1000; + + var days = offset / 3600 / 24; + var hours = offset / 3600; + var minutes = offset / 60; + var seconds = offset; + + if (days > 1) { + return days.toFixed(1) + " " + getTimeUnits("d"); + } else if (hours > 1) { + return hours.toFixed(1) + " " + getTimeUnits("h"); + } else if (minutes > 1) { + return minutes.toFixed(1) + " " + getTimeUnits("m") + } else if (seconds > 1) { + return seconds.toFixed(1) + " " + getTimeUnits("s") + } + return "" } \ No newline at end of file diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index deb005b77..e81772fae 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -9,12 +9,13 @@ from django.conf import settings from rest_framework.pagination import LimitOffsetPagination from rest_framework import viewsets from rest_framework.response import Response -from rest_framework_bulk import BulkModelViewSet +from rest_framework.generics import GenericAPIView import jms_storage - from common.utils import is_uuid, get_logger from common.permissions import IsOrgAdminOrAppUser, IsAuditor +from common.filters import DatetimeRangeFilter +from orgs.mixins import OrgBulkModelViewSet from ..hands import SystemUser from ..models import Session from .. import serializers @@ -24,12 +25,17 @@ __all__ = ['SessionViewSet', 'SessionReplayViewSet',] logger = get_logger(__name__) -class SessionViewSet(BulkModelViewSet): +class SessionViewSet(OrgBulkModelViewSet): queryset = Session.objects.all() serializer_class = serializers.SessionSerializer pagination_class = LimitOffsetPagination permission_classes = (IsOrgAdminOrAppUser | IsAuditor, ) - filter_fields = ["user", "asset", "system_user", "terminal"] + filter_fields = [ + "user", "asset", "system_user", "terminal", "is_finished", + ] + date_range_filter_fields = [ + ('date_start', ('date_from', 'date_to')) + ] def get_object(self): # 解决guacamole更新session时并发导致幽灵会话的问题 @@ -38,6 +44,12 @@ class SessionViewSet(BulkModelViewSet): obj = obj.select_for_update() return obj + @property + def filter_backends(self): + backends = list(GenericAPIView.filter_backends) + backends.append(DatetimeRangeFilter) + return backends + def perform_create(self, serializer): if hasattr(self.request.user, 'terminal'): serializer.validated_data["terminal"] = self.request.user.terminal diff --git a/apps/terminal/models.py b/apps/terminal/models.py index 0bcceef58..2c5c7d666 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -241,6 +241,10 @@ class Session(OrgModelMixin): command_store = get_multi_command_storage() return command_store.count(session=str(self.id)) + @property + def login_from_display(self): + return self.get_login_from_display() + class Meta: db_table = "terminal_session" ordering = ["-date_start"] diff --git a/apps/terminal/serializers/v1.py b/apps/terminal/serializers/v1.py index 27b12b77a..10898ebd6 100644 --- a/apps/terminal/serializers/v1.py +++ b/apps/terminal/serializers/v1.py @@ -2,6 +2,7 @@ # from rest_framework import serializers +from orgs.mixins import BulkOrgResourceModelSerializer from common.mixins import BulkSerializerMixin from common.serializers import AdaptedBulkListSerializer from ..models import Terminal, Status, Session, Task @@ -24,13 +25,18 @@ class TerminalSerializer(serializers.ModelSerializer): return Session.objects.filter(terminal=obj, is_finished=False).count() -class SessionSerializer(BulkSerializerMixin, serializers.ModelSerializer): +class SessionSerializer(BulkOrgResourceModelSerializer): command_amount = serializers.IntegerField(read_only=True) class Meta: model = Session list_serializer_class = AdaptedBulkListSerializer - fields = '__all__' + fields = [ + "id", "user", "asset", "system_user", "login_from", + "login_from_display", "remote_addr", "is_finished", + "has_replay", "can_replay", "protocol", "date_start", "date_end", + "terminal", "command_amount", + ] class StatusSerializer(serializers.ModelSerializer): diff --git a/apps/terminal/templates/terminal/command_list.html b/apps/terminal/templates/terminal/command_list.html index addece387..751e63ac9 100644 --- a/apps/terminal/templates/terminal/command_list.html +++ b/apps/terminal/templates/terminal/command_list.html @@ -8,7 +8,6 @@ .toggle { cursor: pointer; } - .detail-key { width: 70px; } diff --git a/apps/terminal/templates/terminal/session_list.html b/apps/terminal/templates/terminal/session_list.html index 44bbaa32b..db70bce69 100644 --- a/apps/terminal/templates/terminal/session_list.html +++ b/apps/terminal/templates/terminal/session_list.html @@ -7,116 +7,41 @@ - {% endblock %} {% block content_left_head %} {% endblock %} +{% block table_pagination %} +{% endblock %} {% block table_search %} -
-
-
- - - to - -
-
-
- -
-
- -
-
- -
-{#
#} -{# #} -{#
#} -
-
- -
-
-
{% endblock %} -{% block table_head %} - - {% trans 'ID' %} - {% trans 'User' %} - {% trans 'Asset' %} - {% trans 'System user' %} - {% trans 'Remote addr' %} - {% trans 'Protocol' %} - {% trans 'Login from' %} - {% trans 'Command' %} - {% trans 'Date start' %} -{# {% trans 'Date last active' %}#} - {% trans 'Duration' %} - {% trans 'Action' %} -{% endblock %} - -{% block table_body %} - {% for session in session_list %} - - - - {{ forloop.counter }} - - {{ session.user }} - {{ session.asset }} - {{ session.system_user }} - {{ session.remote_addr|default:"" }} - {{ session.protocol }} - {{ session.get_login_from_display }} - {{ session.command_amount }} - - {{ session.date_start }} -{# {{ session.date_last_active }}#} - {{ session.date_start|time_util_with_seconds:session.date_end }} - - {% if session.is_finished %} - {% trans "Replay" %} - {% else %} - {% if session.protocol == 'ssh' and request.user.is_org_admin%} - {% trans "Terminate" %} - {% else %} - {% trans "Terminate" %} - {% endif %} - {% endif %} - +{% block table_container %} + + + + + + + + + + + + + + + - {% endfor %} -{% endblock %} + + + +
{% trans 'ID' %}{% trans 'User' %}{% trans 'Asset' %}{% trans 'System user' %}{% trans 'Remote addr' %}{% trans 'Protocol' %}{% trans 'Login from' %}{% trans 'Command' %}{% trans 'Date start' %}{% trans 'Duration' %}{% trans 'Action' %}
-{% block content_bottom_left %} - {% if request.user.is_org_admin %} - + + {% endblock %} + {% block custom_foot_js %} - - + +function initTable() { + dateFrom = new Date(dateFrom * 1000).toISOString(); + dateTo = new Date(dateTo * 1000).toISOString(); + sessionListUrl = setUrlParam(sessionListUrl, "date_from", dateFrom); + sessionListUrl = setUrlParam(sessionListUrl, "date_to", dateTo); + var options = { + ele: $('#session_table'), + ordering: false, + columnDefs: [ + {targets: 1, createdCell: function (td, cellData, rowData, i) { + var index = i + 1; + var data = '' + + index + ""; + data = data.replace("{{ DEFAULT_PK }}", cellData); + + $(td).html(data); + }}, + {targets: 9, createdCell: function (td, cellData) { + var data = formatDateAsCN(cellData); + $(td).html(data); + }}, + {targets: 10, createdCell: function (td, cellData, rowData) { + var data = ""; + if (cellData && rowData.date_start) { + data = timeOffset(rowData.date_start, cellData) + } + $(td).html(data); + }}, + {targets: 11, createdCell: function (td, cellData, rowData) { + var btnGroup = ""; + var replayBtn = '{% trans "Replay" %}' + {#var replayBtn = '{% trans "Replay" %}'#} + replayBtn = replayBtn.replace("sessionID", rowData.id); + if (rowData.can_replay) { + replayBtn = replayBtn.replace("disabled", "") + } + var termBtn = '{% trans "Terminate" %}'; + if (rowData.protocol === "ssh" && "{{ request.user.is_org_admin }}" === "True") { + termBtn = termBtn.replace("disabled", "") + .replace("sessionID", cellData) + .replace("terminalID", rowData.terminal) + } + if (rowData.is_finished) { + btnGroup += replayBtn + } else { + btnGroup += termBtn; + } + $(td).html(btnGroup); + }}, + ], + ajax_url: sessionListUrl, + columns: [ + {data: "id"}, {data: "id"}, {data: "user", orderable: false}, + {data: "asset", orderable: false}, {data: "system_user", orderable: false}, + {data: "remote_addr"}, {data: "protocol"}, {data: "login_from_display"}, + {data: "command_amount"}, {data: "date_start"}, + {data: "date_end"}, {data: "id"}, + ], + op_html: $('#actions').html(), + fb_html: $("#daterange").html(), + }; + table = jumpserver.initServerSideDataTable(options); + return table +} + +function finishedSession(data) { + var the_url = "{% url 'api-terminal:session-list' %}"; + var success_message = '{% trans "Finish session success" %}'; + var success = function() { + location.reload(); + }; + APIUpdateAttr({ + url: the_url, + method: 'PATCH', + body: JSON.stringify(data), + success: success, + success_message: success_message + }); +} +var table; +$(document).ready(function() { + table = initTable("#session_table"); + $('.select2').select2({ + dropdownAutoWidth: true, + width: "auto" + }); + $('.input-daterange.input-group').datepicker({ + format: "yyyy-mm-dd", + todayBtn: "linked", + keyboardNavigation: false, + forceParse: false, + calendarWeeks: true, + autoclose: true + }); +}).on('click', '.btn-term', function () { + var $this = $(this); + var session_id = $this.attr('value'); + var data = [ + session_id + ]; + terminateSession(data) +}).on('click', '.btn-replay', function () { + var sessionID = $(this).data("session"); + var replayUrl = "/luna/replay/" + sessionID; + window.open(replayUrl, "height=600, width=800, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no"); +}) +.on('click', '#btn_bulk_update', function () { + var action = $('#slct_bulk_update').val(); + var idList = table.selected; + + if (idList.length === 0) { + return false; + } + + function doTerminate() { + terminateSession(idList) + } + + function doFinishSession() { + var data = []; + $.each(idList, function (i, v) { + data.push({ + "id": v, + "is_finished": true + }) + }); + finishedSession(data) + } + switch(action) { + case 'terminate': + doTerminate(); + break; + case "finished": + doFinishSession(); + break; + default: + break; + } +}); + {% endblock %} diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index 4167b3d47..b1b4b98a5 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -1,22 +1,16 @@ # -*- coding: utf-8 -*- # -from django.views.generic import View, TemplateView +from django.views.generic import TemplateView from django.utils.translation import ugettext as _ -from django.http import HttpResponse -from django.template import loader from django.utils import timezone -import time -from common.mixins import DatetimeSearchMixin from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor -from ..backends import get_multi_command_storage __all__ = ['CommandListView'] -common_storage = get_multi_command_storage() -class CommandListView(DatetimeSearchMixin, PermissionsMixin, TemplateView): +class CommandListView(PermissionsMixin, TemplateView): template_name = "terminal/command_list.html" permission_classes = [IsOrgAdmin | IsAuditor] default_days_ago = 5 diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py index 6903c90fd..af401996a 100644 --- a/apps/terminal/views/session.py +++ b/apps/terminal/views/session.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -from django.views.generic import ListView +from django.views.generic import ListView, TemplateView from django.views.generic.edit import SingleObjectMixin from django.utils.translation import ugettext as _ from django.utils import timezone @@ -20,68 +20,40 @@ __all__ = [ ] -class SessionListView(PermissionsMixin, DatetimeSearchMixin, ListView): +class SessionListView(PermissionsMixin, TemplateView): model = Session template_name = 'terminal/session_list.html' - context_object_name = 'session_list' - paginate_by = settings.DISPLAY_PER_PAGE - user = asset = system_user = '' date_from = date_to = None permission_classes = [IsOrgAdmin | IsAuditor] - - def get_queryset(self): - self.queryset = super().get_queryset() - self.user = self.request.GET.get('user') - self.asset = self.request.GET.get('asset') - self.system_user = self.request.GET.get('system_user') - - filter_kwargs = dict() - filter_kwargs['date_start__gt'] = self.date_from - filter_kwargs['date_start__lt'] = self.date_to - return self.queryset + default_days_ago = 5 def get_context_data(self, **kwargs): + now = timezone.now() context = { - 'asset_list': utils.get_session_asset_list()[:10], - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user': self.user, - 'asset': self.asset, - 'system_user': self.system_user, + 'date_from': now - timezone.timedelta(days=self.default_days_ago), + 'date_to': now, } kwargs.update(context) return super().get_context_data(**kwargs) class SessionOnlineListView(SessionListView): - - def get_queryset(self): - queryset = super().get_queryset().filter(is_finished=False) - return queryset - def get_context_data(self, **kwargs): context = { 'app': _('Sessions'), 'action': _('Session online list'), 'type': 'online', - 'now': timezone.now(), } kwargs.update(context) return super().get_context_data(**kwargs) class SessionOfflineListView(SessionListView): - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.filter(is_finished=True) - return queryset - def get_context_data(self, **kwargs): context = { 'app': _('Sessions'), 'action': _('Session offline'), - 'now': timezone.now(), + 'type': 'offline', } kwargs.update(context) return super().get_context_data(**kwargs)