mirror of https://github.com/jumpserver/jumpserver
[Feature] 增加上传replay log的api
parent
7f9ce57318
commit
818ead85ca
|
@ -24,3 +24,4 @@ tags
|
||||||
jumpserver.iml
|
jumpserver.iml
|
||||||
.python-version
|
.python-version
|
||||||
tmp/*
|
tmp/*
|
||||||
|
sessions/*
|
||||||
|
|
|
@ -2,11 +2,15 @@
|
||||||
#
|
#
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import copy
|
import copy
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import os
|
||||||
from rest_framework import viewsets, serializers
|
from rest_framework import viewsets, serializers
|
||||||
from rest_framework.views import APIView, Response
|
from rest_framework.views import APIView, Response
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from .models import Terminal, TerminalStatus, TerminalSession, TerminalTask
|
from .models import Terminal, TerminalStatus, TerminalSession, TerminalTask
|
||||||
from .serializers import TerminalSerializer, TerminalStatusSerializer, \
|
from .serializers import TerminalSerializer, TerminalStatusSerializer, \
|
||||||
|
@ -15,6 +19,8 @@ from .hands import IsSuperUserOrAppUser, IsAppUser, ProxyLog, \
|
||||||
IsSuperUserOrAppUserOrUserReadonly
|
IsSuperUserOrAppUserOrUserReadonly
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
|
|
||||||
|
logger = logging.getLogger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class TerminalViewSet(viewsets.ModelViewSet):
|
class TerminalViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Terminal.objects.filter(is_deleted=False)
|
queryset = Terminal.objects.filter(is_deleted=False)
|
||||||
|
@ -62,20 +68,28 @@ class TerminalStatusViewSet(viewsets.ModelViewSet):
|
||||||
session_serializer_class = TerminalSessionSerializer
|
session_serializer_class = TerminalSessionSerializer
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
|
self.handle_sessions()
|
||||||
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def handle_sessions(self):
|
||||||
sessions_active = []
|
sessions_active = []
|
||||||
for session_data in request.data.get("sessions", []):
|
for session_data in self.request.data.get("sessions", []):
|
||||||
session_data["terminal"] = self.request.user.terminal.id
|
session_data["terminal"] = self.request.user.terminal.id
|
||||||
_id = session_data["id"]
|
_id = session_data["id"]
|
||||||
session = get_object_or_none(TerminalSession, id=_id)
|
session = get_object_or_none(TerminalSession, id=_id)
|
||||||
if session:
|
if session:
|
||||||
serializer = TerminalSessionSerializer(data=session_data, instance=session)
|
serializer = TerminalSessionSerializer(data=session_data,
|
||||||
|
instance=session)
|
||||||
else:
|
else:
|
||||||
serializer = TerminalSessionSerializer(data=session_data)
|
serializer = TerminalSessionSerializer(data=session_data)
|
||||||
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
|
else:
|
||||||
|
logger.error("session serializer is not valid {}".format(
|
||||||
|
serializer.errors))
|
||||||
|
|
||||||
if session_data["is_finished"]:
|
if not session_data["is_finished"]:
|
||||||
sessions_active.append(session_data["id"])
|
sessions_active.append(session_data["id"])
|
||||||
|
|
||||||
sessions_in_db_active = TerminalSession.objects.filter(
|
sessions_in_db_active = TerminalSession.objects.filter(
|
||||||
|
@ -83,13 +97,11 @@ class TerminalStatusViewSet(viewsets.ModelViewSet):
|
||||||
)
|
)
|
||||||
|
|
||||||
for session in sessions_in_db_active:
|
for session in sessions_in_db_active:
|
||||||
if session.id not in sessions_active:
|
if str(session.id) not in sessions_active:
|
||||||
session.is_finished = True
|
session.is_finished = True
|
||||||
session.date_end = timezone.now()
|
session.date_end = timezone.now()
|
||||||
session.save()
|
session.save()
|
||||||
|
|
||||||
return super().create(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
terminal_id = self.kwargs.get("terminal", None)
|
terminal_id = self.kwargs.get("terminal", None)
|
||||||
if terminal_id:
|
if terminal_id:
|
||||||
|
@ -135,3 +147,29 @@ class TerminalTaskViewSet(viewsets.ModelViewSet):
|
||||||
terminal = self.request.user.terminal
|
terminal = self.request.user.terminal
|
||||||
self.queryset = terminal.terminalstatus_set.all()
|
self.queryset = terminal.terminalstatus_set.all()
|
||||||
return self.queryset
|
return self.queryset
|
||||||
|
|
||||||
|
|
||||||
|
class SessionReplayAPI(APIView):
|
||||||
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
|
|
||||||
|
def post(self, request, **kwargs):
|
||||||
|
session_id = kwargs.get("pk", None)
|
||||||
|
session = get_object_or_404(TerminalSession, id=session_id)
|
||||||
|
record_dir = settings.CONFIG.SESSION_RECORDE_DIR
|
||||||
|
date = session.date_start.strftime("%Y-%m-%d")
|
||||||
|
record_dir = os.path.join(record_dir, date)
|
||||||
|
record_filename = os.path.join(record_dir, str(session.id))
|
||||||
|
|
||||||
|
if not os.path.exists(record_dir):
|
||||||
|
os.makedirs(record_dir)
|
||||||
|
|
||||||
|
archive_stream = request.data.get("archive")
|
||||||
|
if not archive_stream:
|
||||||
|
return Response("None file upload", status=400)
|
||||||
|
|
||||||
|
with open(record_filename, 'wb') as f:
|
||||||
|
for chunk in archive_stream.chunks():
|
||||||
|
f.write(chunk)
|
||||||
|
session.has_replay = True
|
||||||
|
session.save()
|
||||||
|
return Response({"session_id": session.id}, status=201)
|
||||||
|
|
|
@ -69,9 +69,6 @@ class TerminalStatus(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'terminal_status'
|
db_table = 'terminal_status'
|
||||||
|
|
||||||
# def __str__(self):
|
|
||||||
# return "<{} status>".format(self.terminal.name)
|
|
||||||
|
|
||||||
|
|
||||||
class TerminalSession(models.Model):
|
class TerminalSession(models.Model):
|
||||||
LOGIN_FROM_CHOICES = (
|
LOGIN_FROM_CHOICES = (
|
||||||
|
@ -85,6 +82,8 @@ class TerminalSession(models.Model):
|
||||||
system_user = models.CharField(max_length=128, verbose_name=_("System User"))
|
system_user = models.CharField(max_length=128, verbose_name=_("System User"))
|
||||||
login_from = models.CharField(max_length=2, choices=LOGIN_FROM_CHOICES, default="ST")
|
login_from = models.CharField(max_length=2, choices=LOGIN_FROM_CHOICES, default="ST")
|
||||||
is_finished = models.BooleanField(default=False)
|
is_finished = models.BooleanField(default=False)
|
||||||
|
has_replay = models.BooleanField(default=False, verbose_name=_("Replay"))
|
||||||
|
has_command = models.BooleanField(default=False, verbose_name=_("Command"))
|
||||||
terminal = models.IntegerField(null=True, verbose_name=_("Terminal"))
|
terminal = models.IntegerField(null=True, verbose_name=_("Terminal"))
|
||||||
date_start = models.DateTimeField(verbose_name=_("Date Start"))
|
date_start = models.DateTimeField(verbose_name=_("Date Start"))
|
||||||
date_end = models.DateTimeField(verbose_name=_("Date End"), null=True)
|
date_end = models.DateTimeField(verbose_name=_("Date End"), null=True)
|
||||||
|
|
|
@ -19,11 +19,11 @@ class TerminalSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_session_connected(obj):
|
def get_session_connected(obj):
|
||||||
return ProxyLog.objects.filter(terminal=obj.name, is_finished=False).count()
|
return TerminalSession.objects.filter(terminal=obj.id, is_finished=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_is_alive(obj):
|
def get_is_alive(obj):
|
||||||
log = obj.terminalheatbeat_set.last()
|
log = obj.terminalstatus_set.last()
|
||||||
if log and timezone.now() - log.date_created < timezone.timedelta(seconds=600):
|
if log and timezone.now() - log.date_created < timezone.timedelta(seconds=600):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -79,7 +79,7 @@ $(document).ready(function(){
|
||||||
var reject_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="99991937" data-name="99991938">{% trans "Reject" %}</a>'
|
var reject_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="99991937" data-name="99991938">{% trans "Reject" %}</a>'
|
||||||
.replace('99991937', cellData)
|
.replace('99991937', cellData)
|
||||||
.replace('99991938', rowData.name);
|
.replace('99991938', rowData.name);
|
||||||
var connect_btn = '<a href="{% url "applications:terminal-connect" pk=99991937 %}"" class="btn btn-xs btn-warning btn-connect" >{% trans "Connect" %}</a> '
|
var connect_btn = '<a href="" class="btn btn-xs btn-warning btn-connect" >{% trans "Connect" %}</a> '
|
||||||
.replace('99991937', cellData);
|
.replace('99991937', cellData);
|
||||||
if (rowData.is_accepted) {
|
if (rowData.is_accepted) {
|
||||||
{% if user.is_superuser %}
|
{% if user.is_superuser %}
|
||||||
|
@ -96,7 +96,7 @@ $(document).ready(function(){
|
||||||
],
|
],
|
||||||
ajax_url: '{% url "api-applications:terminal-list" %}',
|
ajax_url: '{% url "api-applications:terminal-list" %}',
|
||||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "remote_addr" }, {data: "ssh_port"}, {data: "http_port"},
|
columns: [{data: function(){return ""}}, {data: "name" }, {data: "remote_addr" }, {data: "ssh_port"}, {data: "http_port"},
|
||||||
{data: "session_connected"}, {data: "is_alive" }, {data: 'is_alive'}, {data: "id"}],
|
{data: "session_connected"}, {data: "is_accepted" }, {data: 'is_alive'}, {data: "id"}],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
jumpserver.initDataTable(options);
|
jumpserver.initDataTable(options);
|
||||||
|
|
|
@ -33,8 +33,8 @@
|
||||||
<h3>{% trans 'Info' %}</h3>
|
<h3>{% trans 'Info' %}</h3>
|
||||||
{% bootstrap_field form.name layout="horizontal" %}
|
{% bootstrap_field form.name layout="horizontal" %}
|
||||||
{% bootstrap_field form.remote_addr layout="horizontal" %}
|
{% bootstrap_field form.remote_addr layout="horizontal" %}
|
||||||
{% bootstrap_field form.type layout="horizontal" %}
|
{% bootstrap_field form.ssh_port layout="horizontal" %}
|
||||||
{% bootstrap_field form.url layout="horizontal" %}
|
{% bootstrap_field form.http_port layout="horizontal" %}
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Other' %}</h3>
|
<h3>{% trans 'Other' %}</h3>
|
||||||
|
|
|
@ -12,11 +12,12 @@ app_name = 'applications'
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?status', api.TerminalStatusViewSet, 'terminal-status')
|
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?status', api.TerminalStatusViewSet, 'terminal-status')
|
||||||
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?sessions', api.TerminalSessionViewSet, 'terminal-sessions')
|
router.register(r'v1/terminal/(?P<terminal>[0-9]+)?/?sessions', api.TerminalSessionViewSet, 'terminal-sessions')
|
||||||
router.register(r'v1/terminal$', api.TerminalViewSet, 'terminal')
|
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# url(r'^v1/terminate/connection/$', api.TerminateConnectionView.as_view(),
|
# url(r'^v1/terminate/connection/$', api.TerminateConnectionView.as_view(),
|
||||||
# name='terminate-connection')
|
# name='terminate-connection')
|
||||||
|
url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-_]+)/replay/$', api.SessionReplayAPI.as_view(), name='session-replay'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += router.urls
|
urlpatterns += router.urls
|
||||||
|
|
|
@ -123,7 +123,7 @@
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
var the_url = "{% url 'api-applications:terminate-connection' %}";
|
var the_url = "";
|
||||||
APIUpdateAttr({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
|
APIUpdateAttr({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
|
||||||
}
|
}
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
|
@ -137,7 +137,7 @@
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
var the_url = "{% url 'api-applications:terminate-connection' %}";
|
var the_url = "";
|
||||||
APIUpdateAttr({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
|
APIUpdateAttr({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
|
||||||
}
|
}
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
|
@ -10,7 +10,6 @@ urlpatterns = [
|
||||||
name='proxy-log-online-list'),
|
name='proxy-log-online-list'),
|
||||||
url(r'^proxy-log/(?P<pk>\d+)/$', views.ProxyLogDetailView.as_view(),
|
url(r'^proxy-log/(?P<pk>\d+)/$', views.ProxyLogDetailView.as_view(),
|
||||||
name='proxy-log-detail'),
|
name='proxy-log-detail'),
|
||||||
# url(r'^proxy-log/(?P<pk>\d+)/commands$', views.ProxyLogCommandsListView.as_view(), name='proxy-log-commands-list'),
|
|
||||||
url(r'^command-log/$', views.CommandLogListView.as_view(),
|
url(r'^command-log/$', views.CommandLogListView.as_view(),
|
||||||
name='command-log-list'),
|
name='command-log-list'),
|
||||||
url(r'^login-log/$', views.LoginLogListView.as_view(),
|
url(r'^login-log/$', views.LoginLogListView.as_view(),
|
||||||
|
|
Binary file not shown.
|
@ -1375,7 +1375,7 @@ msgstr "终止所选"
|
||||||
#: audits/views.py:72 audits/views.py:209 audits/views.py:263
|
#: audits/views.py:72 audits/views.py:209 audits/views.py:263
|
||||||
#: templates/_nav.html:56
|
#: templates/_nav.html:56
|
||||||
msgid "Audits"
|
msgid "Audits"
|
||||||
msgstr "审计"
|
msgstr "审计中心"
|
||||||
|
|
||||||
#: audits/views.py:73 audits/views.py:264
|
#: audits/views.py:73 audits/views.py:264
|
||||||
msgid "Proxy log list"
|
msgid "Proxy log list"
|
||||||
|
|
|
@ -6,6 +6,7 @@ import errno
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
os.makedirs('../logs')
|
os.makedirs('../logs')
|
||||||
|
os.makedirs('../sessions')
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue