[Update] 修改session

pull/2882/head
ibuler 2019-07-03 22:28:20 +08:00
parent dfcbdb0c35
commit c3a54a8927
17 changed files with 571 additions and 457 deletions

42
apps/common/filters.py Normal file
View File

@ -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

View File

@ -38,8 +38,10 @@ class IDInFilterMixin(object):
class IDInCacheFilterMixin(object): class IDInCacheFilterMixin(object):
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
queryset = super(IDInCacheFilterMixin, self).filter_queryset(queryset) queryset = super().filter_queryset(queryset)
spm = self.request.query_params.get('spm') spm = self.request.query_params.get('spm')
if not spm:
return queryset
cache_key = KEY_CACHE_RESOURCES_ID.format(spm) cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
resources_id = cache.get(cache_key) resources_id = cache.get(cache_key)
if resources_id and isinstance(resources_id, list): if resources_id and isinstance(resources_id, list):

View File

@ -27,13 +27,14 @@ class BulkSerializerMixin(object):
if all((isinstance(self.root, BulkListSerializer), if all((isinstance(self.root, BulkListSerializer),
id_attr, id_attr,
request_method in ('PUT', 'PATCH'))): 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"): if data.get("id"):
id_value = id_field.to_internal_value(data.get("id")) id_value = id_field.to_internal_value(data.get("id"))
else: else:
id_value = id_field.to_internal_value(data.get("pk")) id_value = id_field.to_internal_value(data.get("pk"))
print(">>>>>>>>>>>>>>>>>>>")
print(id_attr)
ret[id_attr] = id_value ret[id_attr] = id_value
return ret return ret

View File

@ -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

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
#
from .models import *
from .serializers import *
from .forms import *
from .api import *

51
apps/orgs/mixins/api.py Normal file
View File

@ -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

16
apps/orgs/mixins/forms.py Normal file
View File

@ -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()

115
apps/orgs/mixins/models.py Normal file
View File

@ -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

View File

@ -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)

View File

@ -1108,9 +1108,44 @@ function formatDateAsCN(d) {
function getUrlParams(url) { function getUrlParams(url) {
url = url.split("?"); url = url.split("?");
let params = ""; var params = "";
if (url.length === 2){ if (url.length === 2){
params = url[1]; params = url[1];
} }
return params 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 ""
}

View File

@ -9,12 +9,13 @@ from django.conf import settings
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework.generics import GenericAPIView
import jms_storage import jms_storage
from common.utils import is_uuid, get_logger from common.utils import is_uuid, get_logger
from common.permissions import IsOrgAdminOrAppUser, IsAuditor from common.permissions import IsOrgAdminOrAppUser, IsAuditor
from common.filters import DatetimeRangeFilter
from orgs.mixins import OrgBulkModelViewSet
from ..hands import SystemUser from ..hands import SystemUser
from ..models import Session from ..models import Session
from .. import serializers from .. import serializers
@ -24,12 +25,17 @@ __all__ = ['SessionViewSet', 'SessionReplayViewSet',]
logger = get_logger(__name__) logger = get_logger(__name__)
class SessionViewSet(BulkModelViewSet): class SessionViewSet(OrgBulkModelViewSet):
queryset = Session.objects.all() queryset = Session.objects.all()
serializer_class = serializers.SessionSerializer serializer_class = serializers.SessionSerializer
pagination_class = LimitOffsetPagination pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser | IsAuditor, ) 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): def get_object(self):
# 解决guacamole更新session时并发导致幽灵会话的问题 # 解决guacamole更新session时并发导致幽灵会话的问题
@ -38,6 +44,12 @@ class SessionViewSet(BulkModelViewSet):
obj = obj.select_for_update() obj = obj.select_for_update()
return obj return obj
@property
def filter_backends(self):
backends = list(GenericAPIView.filter_backends)
backends.append(DatetimeRangeFilter)
return backends
def perform_create(self, serializer): def perform_create(self, serializer):
if hasattr(self.request.user, 'terminal'): if hasattr(self.request.user, 'terminal'):
serializer.validated_data["terminal"] = self.request.user.terminal serializer.validated_data["terminal"] = self.request.user.terminal

View File

@ -241,6 +241,10 @@ class Session(OrgModelMixin):
command_store = get_multi_command_storage() command_store = get_multi_command_storage()
return command_store.count(session=str(self.id)) return command_store.count(session=str(self.id))
@property
def login_from_display(self):
return self.get_login_from_display()
class Meta: class Meta:
db_table = "terminal_session" db_table = "terminal_session"
ordering = ["-date_start"] ordering = ["-date_start"]

View File

@ -2,6 +2,7 @@
# #
from rest_framework import serializers from rest_framework import serializers
from orgs.mixins import BulkOrgResourceModelSerializer
from common.mixins import BulkSerializerMixin from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import Terminal, Status, Session, Task 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() 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) command_amount = serializers.IntegerField(read_only=True)
class Meta: class Meta:
model = Session model = Session
list_serializer_class = AdaptedBulkListSerializer 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): class StatusSerializer(serializers.ModelSerializer):

View File

@ -8,7 +8,6 @@
.toggle { .toggle {
cursor: pointer; cursor: pointer;
} }
.detail-key { .detail-key {
width: 70px; width: 70px;
} }

View File

@ -7,65 +7,21 @@
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.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> <script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<style>
#search_btn {
margin-bottom: 0;
}
</style>
{% endblock %} {% endblock %}
{% block content_left_head %} {% block content_left_head %}
{% endblock %} {% endblock %}
{% block table_pagination %}
{% 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>
{% endblock %} {% 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"></th>
<th class="text-center">{% trans 'ID' %}</th> <th class="text-center">{% trans 'ID' %}</th>
<th class="text-center">{% trans 'User' %}</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 'Login from' %}</th>
<th class="text-center">{% trans 'Command' %}</th> <th class="text-center">{% trans 'Command' %}</th>
<th class="text-center">{% trans 'Date start' %}</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 'Duration' %}</th>
<th class="text-center">{% trans 'Action' %}</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> </tr>
{% endfor %} </thead>
{% endblock %} <tbody>
</tbody>
</table>
{% block content_bottom_left %} <div id="actions" class="hide">
{% if request.user.is_org_admin %} {% if type == "online" %}
<div id="actions" {% if type != "online" %} style="display: none" {% endif %}>
<div class="input-group"> <div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update"> <select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="terminate">{% trans 'Terminate selected' %}</option> <option value="terminate">{% trans 'Terminate selected' %}</option>
@ -128,14 +53,34 @@
</button> </button>
</div> </div>
</div> </div>
</div>
{% endif %} {% 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 %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script> <script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script> <script>
function terminateSession(data) {
function terminateSession(data) {
function success() { function success() {
window.setTimeout(function () { window.setTimeout(function () {
window.location.reload() window.location.reload()
@ -150,9 +95,78 @@
success: success, success: success,
success_message: success_message 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 the_url = "{% url 'api-terminal:session-list' %}";
var success_message = '{% trans "Finish session success" %}'; var success_message = '{% trans "Finish session success" %}';
var success = function() { var success = function() {
@ -165,9 +179,10 @@
success: success, success: success,
success_message: success_message success_message: success_message
}); });
} }
$(document).ready(function() { var table;
jumpserver.initStaticTable('table'); $(document).ready(function() {
table = initTable("#session_table");
$('.select2').select2({ $('.select2').select2({
dropdownAutoWidth: true, dropdownAutoWidth: true,
width: "auto" width: "auto"
@ -180,32 +195,35 @@
calendarWeeks: true, calendarWeeks: true,
autoclose: true autoclose: true
}); });
}).on('click', '.btn-term', function () { }).on('click', '.btn-term', function () {
var $this = $(this); var $this = $(this);
var session_id = $this.attr('value'); var session_id = $this.attr('value');
var data = [ var data = [
session_id session_id
]; ];
terminateSession(data) 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 action = $('#slct_bulk_update').val();
var id_list = []; var idList = table.selected;
$(".cbx-term:checked").each(function (index, data) {
id_list.push($(data).attr("value")) if (idList.length === 0) {
});
if (id_list.length === 0) {
return false; return false;
} }
function doTerminate() { function doTerminate() {
terminateSession(id_list) terminateSession(idList)
} }
function doFinishSession() { function doFinishSession() {
var data = []; var data = [];
$.each(id_list, function (i, v) { $.each(idList, function (i, v) {
data.push({ data.push({
"pk": v, "id": v,
"is_finished": true "is_finished": true
}) })
}); });
@ -221,7 +239,7 @@
default: default:
break; break;
} }
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -1,22 +1,16 @@
# -*- coding: utf-8 -*- # -*- 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.utils.translation import ugettext as _
from django.http import HttpResponse
from django.template import loader
from django.utils import timezone from django.utils import timezone
import time
from common.mixins import DatetimeSearchMixin
from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor
from ..backends import get_multi_command_storage
__all__ = ['CommandListView'] __all__ = ['CommandListView']
common_storage = get_multi_command_storage()
class CommandListView(DatetimeSearchMixin, PermissionsMixin, TemplateView): class CommandListView(PermissionsMixin, TemplateView):
template_name = "terminal/command_list.html" template_name = "terminal/command_list.html"
permission_classes = [IsOrgAdmin | IsAuditor] permission_classes = [IsOrgAdmin | IsAuditor]
default_days_ago = 5 default_days_ago = 5

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- 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.views.generic.edit import SingleObjectMixin
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils import timezone from django.utils import timezone
@ -20,68 +20,40 @@ __all__ = [
] ]
class SessionListView(PermissionsMixin, DatetimeSearchMixin, ListView): class SessionListView(PermissionsMixin, TemplateView):
model = Session model = Session
template_name = 'terminal/session_list.html' 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 date_from = date_to = None
permission_classes = [IsOrgAdmin | IsAuditor] permission_classes = [IsOrgAdmin | IsAuditor]
default_days_ago = 5
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
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
now = timezone.now()
context = { context = {
'asset_list': utils.get_session_asset_list()[:10], 'date_from': now - timezone.timedelta(days=self.default_days_ago),
'date_from': self.date_from, 'date_to': now,
'date_to': self.date_to,
'user': self.user,
'asset': self.asset,
'system_user': self.system_user,
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class SessionOnlineListView(SessionListView): class SessionOnlineListView(SessionListView):
def get_queryset(self):
queryset = super().get_queryset().filter(is_finished=False)
return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Sessions'), 'app': _('Sessions'),
'action': _('Session online list'), 'action': _('Session online list'),
'type': 'online', 'type': 'online',
'now': timezone.now(),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class SessionOfflineListView(SessionListView): 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): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Sessions'), 'app': _('Sessions'),
'action': _('Session offline'), 'action': _('Session offline'),
'now': timezone.now(), 'type': 'offline',
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)