mirror of https://github.com/jumpserver/jumpserver
[Update] 修改session
parent
dfcbdb0c35
commit
c3a54a8927
|
@ -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
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .models import *
|
||||
from .serializers import *
|
||||
from .forms import *
|
||||
from .api import *
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -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)
|
|
@ -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 ""
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
.toggle {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.detail-key {
|
||||
width: 70px;
|
||||
}
|
||||
|
|
|
@ -7,65 +7,21 @@
|
|||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
<style>
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_left_head %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
<div class="form-group" id="date">
|
||||
<div class="input-daterange input-group" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from|date:'Y-m-d' }}">
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to|date:'Y-m-d' }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="user">
|
||||
<option value="">{% trans 'User' %}</option>
|
||||
{% for u in user_list %}
|
||||
<option value="{{ u }}" {% if u == user %} selected {% endif %}>{{ u }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="asset">
|
||||
<option value="">{% trans 'Asset' %}</option>
|
||||
{% for a in asset_list %}
|
||||
<option value="{{ a }}" {% if a == asset %} selected {% endif %}>{{ a }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="system_user">
|
||||
<option value="">{% trans 'System user' %}</option>
|
||||
{% for su in system_user_list %}
|
||||
<option value="{{ su }}" {% if su == system_user %} selected {% endif %}>{{ su }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{# <div class="input-group">#}
|
||||
{# <input type="text" class="form-control input-sm" name="keyword" placeholder="Keyword" value="{{ keyword }}">#}
|
||||
{# </div>#}
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
{% trans 'Search' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% block table_pagination %}
|
||||
{% endblock %}
|
||||
|
||||
{% block table_head %}
|
||||
{% block table_search %}
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
<table class="table table-striped table-bordered table-hover" id="session_table" data-page="false" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% trans 'ID' %}</th>
|
||||
<th class="text-center">{% trans 'User' %}</th>
|
||||
|
@ -76,47 +32,16 @@
|
|||
<th class="text-center">{% trans 'Login from' %}</th>
|
||||
<th class="text-center">{% trans 'Command' %}</th>
|
||||
<th class="text-center">{% trans 'Date start' %}</th>
|
||||
{# <th class="text-center">{% trans 'Date last active' %}</th>#}
|
||||
<th class="text-center">{% trans 'Duration' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_body %}
|
||||
{% for session in session_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center"><input type="checkbox" class="cbx-term" value="{{ session.id }}"></td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'terminal:session-detail' pk=session.id %}">{{ forloop.counter }}</a>
|
||||
</td>
|
||||
<td class="text-center">{{ session.user }}</td>
|
||||
<td class="text-center">{{ session.asset }}</td>
|
||||
<td class="text-center">{{ session.system_user }}</td>
|
||||
<td class="text-center">{{ session.remote_addr|default:"" }}</td>
|
||||
<td class="text-center">{{ session.protocol }}</td>
|
||||
<td class="text-center">{{ session.get_login_from_display }}</td>
|
||||
<td class="text-center">{{ session.command_amount }}</td>
|
||||
|
||||
<td class="text-center">{{ session.date_start }}</td>
|
||||
{# <td class="text-center">{{ session.date_last_active }}</td>#}
|
||||
<td class="text-center">{{ session.date_start|time_util_with_seconds:session.date_end }}</td>
|
||||
<td>
|
||||
{% if session.is_finished %}
|
||||
<a {% if not session.can_replay %} disabled="" {% endif %} onclick="window.open('/luna/replay/{{ session.id }}','luna', 'height=600, width=800, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-xs btn-warning btn-replay" >{% trans "Replay" %}</a>
|
||||
{% else %}
|
||||
{% if session.protocol == 'ssh' and request.user.is_org_admin%}
|
||||
<a class="btn btn-xs btn-danger btn-term" value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a>
|
||||
{% else %}
|
||||
<a class="btn btn-xs btn-danger btn-term" disabled value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% block content_bottom_left %}
|
||||
{% if request.user.is_org_admin %}
|
||||
<div id="actions" {% if type != "online" %} style="display: none" {% endif %}>
|
||||
<div id="actions" class="hide">
|
||||
{% if type == "online" %}
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="terminate">{% trans 'Terminate selected' %}</option>
|
||||
|
@ -128,14 +53,34 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="hide" id="daterange">
|
||||
<div class="form-group p-l-5" id="date" style="padding-left: 5px">
|
||||
<div class="input-daterange input-group p-l-5" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" id="date_from" name="date_from" value="{{ date_from|date:'Y-m-d' }}">
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" id="date_to" name="date_to" value="{{ date_to|date:'Y-m-d' }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="dropdown-menu search-help">
|
||||
<li><a class="search-item" data-value="user">{% trans 'User' %}</a></li>
|
||||
<li><a class="search-item" data-value="asset">{% trans 'Asset' %}</a></li>
|
||||
<li><a class="search-item" data-value="system_user">{% trans 'System user' %}</a></li>
|
||||
<li><a class="search-item" data-value="input">{% trans 'Command' %}</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
function terminateSession(data) {
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
|
||||
function terminateSession(data) {
|
||||
function success() {
|
||||
window.setTimeout(function () {
|
||||
window.location.reload()
|
||||
|
@ -150,9 +95,78 @@
|
|||
success: success,
|
||||
success_message: success_message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function finishedSession(data) {
|
||||
var sessionListUrl = "{% url 'api-terminal:session-list' %}?is_finished={% if type == "online" %}0{% else %}1{% endif %}";
|
||||
var dateFrom = "{{ date_from.timestamp }}";
|
||||
var dateTo = "{{ date_to.timestamp }}";
|
||||
|
||||
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 = '<a href="{% url 'terminal:session-detail' pk=DEFAULT_PK %}">'
|
||||
+ index + "</a>";
|
||||
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 = '<a disabled data-session="sessionID" class="btn btn-xs btn-warning btn-replay" >{% trans "Replay" %}</a>'
|
||||
{#var replayBtn = '<a disabled onclick="window.open("url", "height=600, width=800, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no)" class="btn btn-xs btn-warning btn-replay" >{% trans "Replay" %}</a>'#}
|
||||
replayBtn = replayBtn.replace("sessionID", rowData.id);
|
||||
if (rowData.can_replay) {
|
||||
replayBtn = replayBtn.replace("disabled", "")
|
||||
}
|
||||
var termBtn = '<a class="btn btn-xs btn-danger btn-term" disabled value="sessionID" terminal="terminalID" >{% trans "Terminate" %}</a>';
|
||||
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() {
|
||||
|
@ -165,9 +179,10 @@
|
|||
success: success,
|
||||
success_message: success_message
|
||||
});
|
||||
}
|
||||
$(document).ready(function() {
|
||||
jumpserver.initStaticTable('table');
|
||||
}
|
||||
var table;
|
||||
$(document).ready(function() {
|
||||
table = initTable("#session_table");
|
||||
$('.select2').select2({
|
||||
dropdownAutoWidth: true,
|
||||
width: "auto"
|
||||
|
@ -180,32 +195,35 @@
|
|||
calendarWeeks: true,
|
||||
autoclose: true
|
||||
});
|
||||
}).on('click', '.btn-term', function () {
|
||||
}).on('click', '.btn-term', function () {
|
||||
var $this = $(this);
|
||||
var session_id = $this.attr('value');
|
||||
var data = [
|
||||
session_id
|
||||
];
|
||||
terminateSession(data)
|
||||
}).on('click', '#btn_bulk_update', function () {
|
||||
}).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 id_list = [];
|
||||
$(".cbx-term:checked").each(function (index, data) {
|
||||
id_list.push($(data).attr("value"))
|
||||
});
|
||||
if (id_list.length === 0) {
|
||||
var idList = table.selected;
|
||||
|
||||
if (idList.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function doTerminate() {
|
||||
terminateSession(id_list)
|
||||
terminateSession(idList)
|
||||
}
|
||||
|
||||
function doFinishSession() {
|
||||
var data = [];
|
||||
$.each(id_list, function (i, v) {
|
||||
$.each(idList, function (i, v) {
|
||||
data.push({
|
||||
"pk": v,
|
||||
"id": v,
|
||||
"is_finished": true
|
||||
})
|
||||
});
|
||||
|
@ -221,7 +239,7 @@
|
|||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue