mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-10-22 08:19:04 +00:00
[Change] Rename applications -> terminal
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@@ -1,219 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import base64
|
||||
from collections import OrderedDict
|
||||
import copy
|
||||
import logging
|
||||
import tarfile
|
||||
|
||||
import os
|
||||
from rest_framework import viewsets, serializers
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from .models import Terminal, TerminalStatus, TerminalSession, TerminalTask
|
||||
from .serializers import TerminalSerializer, TerminalStatusSerializer, \
|
||||
TerminalSessionSerializer, TerminalTaskSerializer
|
||||
from .hands import IsSuperUserOrAppUser, IsAppUser, ProxyLog, \
|
||||
IsSuperUserOrAppUserOrUserReadonly
|
||||
from .backends import get_command_store, get_replay_store, SessionCommandSerializer
|
||||
|
||||
logger = logging.getLogger(__file__)
|
||||
|
||||
|
||||
class TerminalViewSet(viewsets.ModelViewSet):
|
||||
queryset = Terminal.objects.filter(is_deleted=False)
|
||||
serializer_class = TerminalSerializer
|
||||
permission_classes = (IsSuperUserOrAppUserOrUserReadonly,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
name = request.data.get('name')
|
||||
remote_ip = request.META.get('REMOTE_ADDR')
|
||||
x_real_ip = request.META.get('X-Real-IP')
|
||||
remote_addr = x_real_ip or remote_ip
|
||||
|
||||
terminal = get_object_or_none(Terminal, name=name)
|
||||
if terminal:
|
||||
msg = 'Terminal name %s already used' % name
|
||||
return Response({'msg': msg}, status=409)
|
||||
|
||||
serializer = self.serializer_class(data={
|
||||
'name': name, 'remote_addr': remote_addr
|
||||
})
|
||||
|
||||
if serializer.is_valid():
|
||||
terminal = serializer.save()
|
||||
app_user, access_key = terminal.create_app_user()
|
||||
data = OrderedDict()
|
||||
data['terminal'] = copy.deepcopy(serializer.data)
|
||||
data['user'] = app_user.to_json()
|
||||
data['access_key'] = {'id': access_key.id,
|
||||
'secret': access_key.secret}
|
||||
return Response(data, status=201)
|
||||
else:
|
||||
data = serializer.errors
|
||||
return Response(data, status=400)
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == "create":
|
||||
self.permission_classes = (AllowAny,)
|
||||
return super().get_permissions()
|
||||
|
||||
|
||||
class TerminalStatusViewSet(viewsets.ModelViewSet):
|
||||
queryset = TerminalStatus.objects.all()
|
||||
serializer_class = TerminalStatusSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
session_serializer_class = TerminalSessionSerializer
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
self.handle_sessions()
|
||||
return super().create(request, *args, **kwargs)
|
||||
|
||||
def handle_sessions(self):
|
||||
sessions_active = []
|
||||
for session_data in self.request.data.get("sessions", []):
|
||||
session_data["terminal"] = self.request.user.terminal.id
|
||||
_id = session_data["id"]
|
||||
session = get_object_or_none(TerminalSession, id=_id)
|
||||
if session:
|
||||
serializer = TerminalSessionSerializer(data=session_data,
|
||||
instance=session)
|
||||
else:
|
||||
serializer = TerminalSessionSerializer(data=session_data)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
else:
|
||||
logger.error("session serializer is not valid {}".format(
|
||||
serializer.errors))
|
||||
|
||||
if not session_data["is_finished"]:
|
||||
sessions_active.append(session_data["id"])
|
||||
|
||||
sessions_in_db_active = TerminalSession.objects.filter(
|
||||
is_finished=False, terminal=self.request.user.terminal.id
|
||||
)
|
||||
|
||||
for session in sessions_in_db_active:
|
||||
if str(session.id) not in sessions_active:
|
||||
session.is_finished = True
|
||||
session.date_end = timezone.now()
|
||||
session.save()
|
||||
|
||||
def get_queryset(self):
|
||||
terminal_id = self.kwargs.get("terminal", None)
|
||||
if terminal_id:
|
||||
terminal = get_object_or_404(Terminal, id=terminal_id)
|
||||
self.queryset = terminal.terminalstatus_set.all()
|
||||
return self.queryset
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.validated_data["terminal"] = self.request.user.terminal
|
||||
return super().perform_create(serializer)
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == "create":
|
||||
self.permission_classes = (IsAppUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
|
||||
class TerminalSessionViewSet(viewsets.ModelViewSet):
|
||||
queryset = TerminalSession.objects.all()
|
||||
serializers_class = TerminalSessionSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
terminal_id = self.kwargs.get("terminal", None)
|
||||
if terminal_id:
|
||||
terminal = get_object_or_404(Terminal, id=terminal_id)
|
||||
self.queryset = terminal.terminalstatus_set.all()
|
||||
return self.queryset
|
||||
|
||||
|
||||
class TerminalTaskViewSet(viewsets.ModelViewSet):
|
||||
queryset = TerminalTask.objects.all()
|
||||
serializer_class = TerminalTaskSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
terminal_id = self.kwargs.get("terminal", None)
|
||||
if terminal_id:
|
||||
terminal = get_object_or_404(Terminal, id=terminal_id)
|
||||
self.queryset = terminal.terminalstatus_set.all()
|
||||
|
||||
if hasattr(self.request.user, "terminal"):
|
||||
terminal = self.request.user.terminal
|
||||
self.queryset = terminal.terminalstatus_set.all()
|
||||
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, str(session.id))
|
||||
record_filename = os.path.join(record_dir, "replay.tar.gz2")
|
||||
|
||||
if not os.path.isdir(record_dir):
|
||||
try:
|
||||
os.makedirs(record_dir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class SessionCommandViewSet(viewsets.ViewSet):
|
||||
"""接受app发送来的command log, 格式如下
|
||||
{
|
||||
"user": "admin",
|
||||
"asset": "localhost",
|
||||
"system_user": "web",
|
||||
"session": "xxxxxx",
|
||||
"input": "whoami",
|
||||
"output": "d2hvbWFp", # base64.b64encode(s)
|
||||
"timestamp": 1485238673.0
|
||||
}
|
||||
|
||||
"""
|
||||
command_store = get_command_store()
|
||||
serializer_class = SessionCommandSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
self.command_store.all()
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.serializer_class(data=request.data, many=True)
|
||||
if serializer.is_valid():
|
||||
ok = self.command_store.bulk_save(serializer.validated_data)
|
||||
if ok:
|
||||
return Response("ok", status=201)
|
||||
else:
|
||||
return Response("save error", status=500)
|
||||
else:
|
||||
print(serializer.errors)
|
||||
return Response({"msg": "Not valid: {}".format(serializer.errors)}, status=401)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = list(self.command_store.all())
|
||||
serializer = self.serializer_class(queryset, many=True)
|
||||
return Response(serializer.data)
|
@@ -1,7 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApplicationsConfig(AppConfig):
|
||||
name = 'applications'
|
@@ -1,17 +0,0 @@
|
||||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from .command.serializers import SessionCommandSerializer
|
||||
|
||||
|
||||
def get_command_store():
|
||||
command_engine = import_module(settings.COMMAND_STORE_BACKEND)
|
||||
command_store = command_engine.CommandStore()
|
||||
return command_store
|
||||
|
||||
|
||||
def get_replay_store():
|
||||
replay_engine = import_module(settings.RECORD_STORE_BACKEND)
|
||||
replay_store = replay_engine.RecordStore()
|
||||
return replay_store
|
@@ -1,22 +0,0 @@
|
||||
# coding: utf-8
|
||||
import abc
|
||||
|
||||
|
||||
class CommandBase(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def save(self, command):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def bulk_save(self, commands):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def filter(self, date_from=None, date_to=None, user=None,
|
||||
asset=None, system_user=None, command=None, session=None):
|
||||
pass
|
||||
|
||||
|
||||
|
@@ -1,70 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
import datetime
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from .base import CommandBase
|
||||
|
||||
|
||||
class CommandStore(CommandBase):
|
||||
|
||||
def __init__(self):
|
||||
from applications.models import SessionCommand
|
||||
self.model = SessionCommand
|
||||
|
||||
def save(self, command):
|
||||
"""
|
||||
保存命令到数据库
|
||||
"""
|
||||
|
||||
self.model.objects.create(
|
||||
user=command["user"], asset=command["asset"],
|
||||
system_user=command["system_user"], input=command["input"],
|
||||
output=command["output"], session=command["session"],
|
||||
timestamp=command["timestamp"]
|
||||
)
|
||||
|
||||
def bulk_save(self, commands):
|
||||
"""
|
||||
批量保存命令到数据库, command的顺序和save中一致
|
||||
"""
|
||||
_commands = []
|
||||
for c in commands:
|
||||
_commands.append(self.model(
|
||||
user=c["user"], asset=c["asset"], system_user=c["system_user"],
|
||||
input=c["input"], output=c["output"], session=c["session"],
|
||||
timestamp=c["timestamp"]
|
||||
))
|
||||
return self.model.objects.bulk_create(_commands)
|
||||
|
||||
def filter(self, date_from=None, date_to=None, user=None,
|
||||
asset=None, system_user=None, _input=None, session=None):
|
||||
filter_kwargs = {}
|
||||
|
||||
if date_from:
|
||||
filter_kwargs['timestamp__gte'] = int(date_from.timestamp())
|
||||
else:
|
||||
week_ago = timezone.now() - datetime.timedelta(days=7)
|
||||
filter_kwargs['timestamp__gte'] = int(week_ago.timestamp())
|
||||
if date_to:
|
||||
filter_kwargs['timestamp__lte'] = int(date_to.timestamp())
|
||||
else:
|
||||
filter_kwargs['timestamp__lte'] = int(timezone.now().timestamp())
|
||||
if user:
|
||||
filter_kwargs['user'] = user
|
||||
if asset:
|
||||
filter_kwargs['asset'] = asset
|
||||
if system_user:
|
||||
filter_kwargs['system_user'] = system_user
|
||||
if _input:
|
||||
filter_kwargs['input__icontains'] = _input
|
||||
if session:
|
||||
filter_kwargs['session'] = session
|
||||
|
||||
queryset = self.model.objects.filter(**filter_kwargs)
|
||||
return queryset
|
||||
|
||||
def all(self):
|
||||
"""返回所有数据"""
|
||||
return self.model.objects.iterator()
|
||||
|
@@ -1,22 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class AbstractSessionCommand(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=64, verbose_name=_("User"))
|
||||
asset = models.CharField(max_length=128, verbose_name=_("Asset"))
|
||||
system_user = models.CharField(max_length=64, verbose_name=_("System user"))
|
||||
input = models.CharField(max_length=128, db_index=True, verbose_name=_("Input"))
|
||||
output = models.CharField(max_length=1024, verbose_name=_("Output"))
|
||||
session = models.CharField(max_length=36, db_index=True, verbose_name=_("Session"))
|
||||
timestamp = models.IntegerField(db_index=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __str__(self):
|
||||
return self.input
|
@@ -1,16 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class SessionCommandSerializer(serializers.Serializer):
|
||||
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
|
||||
|
||||
id = serializers.UUIDField(read_only=True)
|
||||
user = serializers.CharField(max_length=64)
|
||||
asset = serializers.CharField(max_length=128)
|
||||
system_user = serializers.CharField(max_length=64)
|
||||
input = serializers.CharField(max_length=128)
|
||||
output = serializers.CharField(max_length=1024)
|
||||
session = serializers.CharField(max_length=36)
|
||||
timestamp = serializers.IntegerField()
|
||||
|
@@ -1,2 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
@@ -1,14 +0,0 @@
|
||||
# coding: utf-8
|
||||
import abc
|
||||
|
||||
|
||||
class RecordBase(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def save(self, proxy_log_id, output, timestamp):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def filter(self, date_from_ts=None, proxy_log_id=None):
|
||||
pass
|
@@ -1,31 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from .base import RecordBase
|
||||
from audits.models import RecordLog
|
||||
|
||||
|
||||
class RecordStore(RecordBase):
|
||||
model = RecordLog
|
||||
queryset = []
|
||||
|
||||
def save(self, proxy_log_id, output, timestamp):
|
||||
return self.model.objects.create(
|
||||
proxy_log_id=proxy_log_id, output=output, timestamp=timestamp
|
||||
)
|
||||
|
||||
def filter(self, date_from_ts=None, proxy_log_id=''):
|
||||
filter_kwargs = {}
|
||||
|
||||
if date_from_ts:
|
||||
filter_kwargs['timestamp__gte'] = date_from_ts
|
||||
if proxy_log_id:
|
||||
filter_kwargs['proxy_log_id'] = proxy_log_id
|
||||
|
||||
if filter_kwargs:
|
||||
self.queryset = self.model.objects.filter(**filter_kwargs)
|
||||
return self.queryset
|
||||
|
||||
def all(self):
|
||||
"""返回所有数据"""
|
||||
return self.model.objects.all()
|
||||
|
@@ -1,20 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
import base64
|
||||
from rest_framework import serializers
|
||||
from audits.models import RecordLog
|
||||
from audits.backends import record_store
|
||||
|
||||
|
||||
class RecordSerializer(serializers.ModelSerializer):
|
||||
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
|
||||
class Meta:
|
||||
model = RecordLog
|
||||
fields = '__all__'
|
||||
|
||||
def create(self, validated_data):
|
||||
try:
|
||||
output = validated_data['output']
|
||||
validated_data['output'] = base64.b64decode(output)
|
||||
except IndexError:
|
||||
pass
|
||||
return record_store.save(**dict(validated_data))
|
@@ -1,21 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .models import Terminal
|
||||
|
||||
|
||||
class TerminalForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Terminal
|
||||
fields = ['name', 'remote_addr', 'ssh_port', 'http_port', 'comment']
|
||||
help_texts = {
|
||||
'remote_addr': _('A unique addr of every terminal, user browser can arrive it'),
|
||||
'ssh_port': _("Coco ssh listen port"),
|
||||
'http_port': _("Coco http/ws listen port"),
|
||||
}
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'readonly': 'readonly'})
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from users.models import User
|
||||
from users.permissions import IsSuperUserOrAppUser, IsAppUser, \
|
||||
IsSuperUserOrAppUserOrUserReadonly
|
||||
from audits.models import ProxyLog
|
||||
from users.utils import AdminUserRequiredMixin
|
@@ -1,119 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from users.models import User
|
||||
from .backends.command.models import AbstractSessionCommand
|
||||
|
||||
|
||||
class Terminal(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=32, unique=True, verbose_name=_('Name'))
|
||||
remote_addr = models.CharField(max_length=128, verbose_name=_('Remote Address'))
|
||||
ssh_port = models.IntegerField(verbose_name=_('SSH Port'), default=2222)
|
||||
http_port = models.IntegerField(verbose_name=_('HTTP Port'), default=5000)
|
||||
user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, on_delete=models.CASCADE)
|
||||
is_accepted = models.BooleanField(default=False, verbose_name='Is Accepted')
|
||||
is_deleted = models.BooleanField(default=False)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
if self.user and self.user.is_active:
|
||||
return True
|
||||
return False
|
||||
|
||||
@is_active.setter
|
||||
def is_active(self, active):
|
||||
if self.user:
|
||||
self.user.is_active = active
|
||||
self.user.save()
|
||||
|
||||
def create_app_user(self):
|
||||
user, access_key = User.create_app_user(name=self.name, comment=self.comment)
|
||||
self.user = user
|
||||
self.save()
|
||||
return user, access_key
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.user:
|
||||
self.user.delete()
|
||||
self.is_deleted = True
|
||||
self.save()
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
status = "Active"
|
||||
if not self.is_accepted:
|
||||
status = "NotAccept"
|
||||
elif self.is_deleted:
|
||||
status = "Deleted"
|
||||
elif not self.is_active:
|
||||
status = "Disable"
|
||||
return '%s: %s' % (self.name, status)
|
||||
|
||||
class Meta:
|
||||
ordering = ('is_accepted',)
|
||||
|
||||
|
||||
class TerminalStatus(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
session_online = models.IntegerField(verbose_name=_("Session Online"), default=0)
|
||||
cpu_used = models.FloatField(verbose_name=_("CPU Usage"))
|
||||
memory_used = models.FloatField(verbose_name=_("Memory Used"))
|
||||
connections = models.IntegerField(verbose_name=_("Connections"))
|
||||
threads = models.IntegerField(verbose_name=_("Threads"))
|
||||
boot_time = models.FloatField(verbose_name=_("Boot Time"))
|
||||
terminal = models.ForeignKey(Terminal, null=True, on_delete=models.CASCADE)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'terminal_status'
|
||||
|
||||
|
||||
class TerminalSession(models.Model):
|
||||
LOGIN_FROM_CHOICES = (
|
||||
('ST', 'SSH Terminal'),
|
||||
('WT', 'Web Terminal'),
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=128, verbose_name=_("User"))
|
||||
asset = models.CharField(max_length=1024, verbose_name=_("Asset"))
|
||||
system_user = models.CharField(max_length=128, verbose_name=_("System User"))
|
||||
login_from = models.CharField(max_length=2, choices=LOGIN_FROM_CHOICES, default="ST")
|
||||
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.UUIDField(null=True, verbose_name=_("Terminal"))
|
||||
date_start = models.DateTimeField(verbose_name=_("Date Start"))
|
||||
date_end = models.DateTimeField(verbose_name=_("Date End"), null=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "terminal_session"
|
||||
|
||||
def __str__(self):
|
||||
return "{0.id} of {0.user} to {0.asset}".format(self)
|
||||
|
||||
|
||||
class TerminalTask(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||
args = models.CharField(max_length=1024, verbose_name=_("Task Args"))
|
||||
terminal = models.ForeignKey(Terminal, null=True, on_delete=models.CASCADE)
|
||||
is_finished = models.BooleanField(default=False)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_finished = models.DateTimeField(null=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "terminal_task"
|
||||
|
||||
|
||||
class SessionCommand(AbstractSessionCommand):
|
||||
|
||||
class Meta:
|
||||
db_table = "session_command"
|
@@ -1,51 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils import timezone
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Terminal, TerminalStatus, TerminalSession, TerminalTask
|
||||
from .hands import ProxyLog
|
||||
|
||||
|
||||
class TerminalSerializer(serializers.ModelSerializer):
|
||||
session_connected = serializers.SerializerMethodField()
|
||||
is_alive = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Terminal
|
||||
fields = ['id', 'name', 'remote_addr', 'http_port', 'ssh_port',
|
||||
'comment', 'is_accepted', 'session_connected', 'is_alive']
|
||||
|
||||
@staticmethod
|
||||
def get_session_connected(obj):
|
||||
return TerminalSession.objects.filter(terminal=obj.id, is_finished=False)
|
||||
|
||||
@staticmethod
|
||||
def get_is_alive(obj):
|
||||
log = obj.terminalstatus_set.last()
|
||||
if log and timezone.now() - log.date_created < timezone.timedelta(seconds=600):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class TerminalSessionSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = TerminalSession
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class TerminalStatusSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
model = TerminalStatus
|
||||
|
||||
|
||||
class TerminalTaskSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
model = TerminalTask
|
@@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from celery import shared_task
|
||||
|
||||
|
||||
# Todo: 定期清理上报history
|
||||
@shared_task
|
||||
def clean_terminal_history():
|
||||
pass
|
@@ -1,77 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Terminal detail' %} </a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'applications:terminal-update' pk=terminal.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ terminal.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td width="20%">{% trans 'Name' %}:</td>
|
||||
<td><b>{{ terminal.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Remote address' %}:</td>
|
||||
<td><b>{{ terminal.remote_addr }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'URL to login' %}:</td>
|
||||
<td><b>{{ terminal.url }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Terminal type' %}:</td>
|
||||
<td><b>{{ terminal.get_type_display }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ terminal.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ asset.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@@ -1,149 +0,0 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block custom_head_css_js %}
|
||||
{{ block.super }}
|
||||
|
||||
<style>
|
||||
div.dataTables_wrapper div.dataTables_filter,
|
||||
.dataTables_length {
|
||||
float: right !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_filter {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#modal .modal-body { max-height: 200px; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
{#<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>#}
|
||||
<table class="table table-striped table-bordered table-hover " id="terminal_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<div class="checkbox checkbox-default">
|
||||
<input type="checkbox" class="ipt_check_all">
|
||||
</div>
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Addr' %}</th>
|
||||
<th class="text-center">{% trans 'SSH port' %}</th>
|
||||
<th class="text-center">{% trans 'Http port' %}</th>
|
||||
<th class="text-center">{% trans 'Sessions' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Alive' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'applications/terminal_modal_accept.html' %}
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#terminal_list_table'),
|
||||
buttons: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "applications:terminal-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 8, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "applications:terminal-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
|
||||
.replace('99991937', cellData);
|
||||
var delete_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="99991937" data-name="99991938">{% trans "Delete" %}</a>'
|
||||
.replace('99991937', cellData)
|
||||
.replace('99991938', rowData.name);
|
||||
var accept_btn = '<a class="btn btn-xs btn-primary btn-accept" data-id="99991937">{% trans "Accept" %}</a> '
|
||||
.replace('99991937', cellData);
|
||||
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('99991938', rowData.name);
|
||||
var connect_btn = '<a href="" class="btn btn-xs btn-warning btn-connect" >{% trans "Connect" %}</a> '
|
||||
.replace('99991937', cellData);
|
||||
if (rowData.is_accepted) {
|
||||
{% if user.is_superuser %}
|
||||
$(td).html(connect_btn + update_btn + delete_btn);
|
||||
{% else %}
|
||||
$(td).html(connect_btn);
|
||||
{% endif %}
|
||||
} else {
|
||||
{% if user.is_superuser %}
|
||||
$(td).html(accept_btn + reject_btn);
|
||||
{% endif %}
|
||||
}
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-applications:terminal-list" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "remote_addr" }, {data: "ssh_port"}, {data: "http_port"},
|
||||
{data: "session_connected"}, {data: "is_accepted" }, {data: 'is_alive'}, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
|
||||
$('#btn_terminal_accept').click(function () {
|
||||
var $form = $('#form_terminal_accept');
|
||||
function success(data, textStatus, jqXHR) {
|
||||
if (data.success === true) {
|
||||
window.location.reload()
|
||||
} else {
|
||||
$('#modal-error').html(data.msg).css('display', 'block');
|
||||
}
|
||||
}
|
||||
$form.ajaxSubmit({success: success});
|
||||
})
|
||||
|
||||
}).on('click', '.btn-del', function(){
|
||||
var $this = $(this);
|
||||
var id = $this.data('id');
|
||||
var name = $(this).data('name');
|
||||
var the_url = '{% url "api-applications:terminal-detail" pk=99991937 %}'.replace('99991937', id);
|
||||
objectDelete($this, name, the_url)
|
||||
|
||||
}).on('click', '.btn-accept', function () {
|
||||
var $this = $(this);
|
||||
var terminal_id = $this.data('id');
|
||||
var the_url = "{% url 'api-applications:terminal-detail' pk=99991937 %}".replace('99991937', terminal_id);
|
||||
var post_url = $('#form_terminal_accept').attr('action').replace('99991937', terminal_id);
|
||||
console.log(post_url);
|
||||
$.ajax({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: function (data) {
|
||||
$('#id_name').val(data.name);
|
||||
$('#id_remote_addr').val(data.remote_addr);
|
||||
$('#id_url').val(data.url);
|
||||
$('#id_comment').val(data.comment);
|
||||
$('#form_terminal_accept').attr('action', post_url)
|
||||
}
|
||||
});
|
||||
$('#modal_terminal_accept').modal({
|
||||
show: true
|
||||
});
|
||||
}).on('click', '.btn-connect', function () {
|
||||
var $this = $(this);
|
||||
var id = $this.data('id');
|
||||
console.log(id)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
@@ -1,19 +0,0 @@
|
||||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% block modal_id %}modal_terminal_accept{% endblock %}
|
||||
{% block modal_class %}modal-lg{% endblock %}
|
||||
{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
{% load bootstrap3 %}
|
||||
<form action="{% url 'applications:terminal-modal-accept' pk="99991937" %}" method="post" class="form-horizontal" id="form_terminal_accept" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<p class="alert alert-danger" id="modal-error" style="display: none"></p>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.remote_addr layout="horizontal" %}
|
||||
{% bootstrap_field form.ssh_port layout="horizontal" %}
|
||||
{% bootstrap_field form.http_port layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_terminal_accept{% endblock %}
|
@@ -1,5 +0,0 @@
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
@@ -1,72 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
<link href="{% static "css/plugins/datepicker/datepicker3.css" %}" rel="stylesheet">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<form method="post" class="form-horizontal" action="" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Info' %}</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.remote_addr layout="horizontal" %}
|
||||
{% bootstrap_field form.ssh_port layout="horizontal" %}
|
||||
{% bootstrap_field form.http_port layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
|
||||
$('.input-group.date').datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
todayBtn: "linked",
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
calendarWeeks: true,
|
||||
autoclose: true
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@@ -1 +0,0 @@
|
||||
|
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.conf.urls import url
|
||||
from rest_framework import routers
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'applications'
|
||||
|
||||
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]+)?/?sessions', api.TerminalSessionViewSet, 'terminal-sessions')
|
||||
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
||||
router.register(r'v1/command', api.SessionCommandViewSet, 'command')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-_]+)/replay/$', api.SessionReplayAPI.as_view(), name='session-replay'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .. import views
|
||||
|
||||
app_name = 'applications'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^terminal/$', views.TerminalListView.as_view(), name='terminal-list'),
|
||||
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]+)/$', views.TerminalDetailView.as_view(),
|
||||
name='terminal-detail'),
|
||||
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]+)/connect/$', views.TerminalConnectView.as_view(),
|
||||
name='terminal-connect'),
|
||||
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]+)/update$', views.TerminalUpdateView.as_view(),
|
||||
name='terminal-update'),
|
||||
url(r'^terminal/(?P<pk>[0-9a-zA-Z\-]+)/modal/accept$', views.TerminalModelAccept.as_view(),
|
||||
name='terminal-modal-accept'),
|
||||
]
|
@@ -1,110 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from django.views.generic import ListView, UpdateView, DeleteView, \
|
||||
DetailView, TemplateView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from .models import Terminal
|
||||
from .forms import TerminalForm
|
||||
from .hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
class TerminalListView(LoginRequiredMixin, ListView):
|
||||
model = Terminal
|
||||
template_name = 'applications/terminal_list.html'
|
||||
form_class = TerminalForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TerminalListView, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _('Terminal'),
|
||||
'action': _('Terminal list'),
|
||||
'form': self.form_class()
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class TerminalUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = Terminal
|
||||
form_class = TerminalForm
|
||||
template_name = 'applications/terminal_update.html'
|
||||
success_url = reverse_lazy('applications:terminal-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TerminalUpdateView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Applications'), 'action': _('Update terminal')})
|
||||
return context
|
||||
|
||||
|
||||
class TerminalDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Terminal
|
||||
template_name = 'applications/terminal_detail.html'
|
||||
context_object_name = 'terminal'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TerminalDetailView, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _('Applications'),
|
||||
'action': _('Terminal detail')
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class TerminalDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = Terminal
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
success_url = reverse_lazy('applications:applications-list')
|
||||
|
||||
|
||||
class TerminalModelAccept(AdminUserRequiredMixin, JSONResponseMixin, UpdateView):
|
||||
model = Terminal
|
||||
form_class = TerminalForm
|
||||
template_name = 'applications/terminal_modal_test.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
terminal = form.save()
|
||||
terminal.is_accepted = True
|
||||
terminal.is_active = True
|
||||
terminal.save()
|
||||
data = {
|
||||
'success': True,
|
||||
'msg': 'success'
|
||||
}
|
||||
return self.render_json_response(data)
|
||||
|
||||
def form_invalid(self, form):
|
||||
data = {
|
||||
'success': False,
|
||||
'msg': str(form.errors),
|
||||
}
|
||||
return self.render_json_response(data)
|
||||
|
||||
|
||||
class TerminalConnectView(LoginRequiredMixin, DetailView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
model = Terminal
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
if self.object.type == 'Web':
|
||||
context = {
|
||||
'title': _('Redirect to web terminal'),
|
||||
'messages': _('Redirect to web terminal') + self.object.url,
|
||||
'auto_redirect': True,
|
||||
'interval': 3,
|
||||
'redirect_url': self.object.url
|
||||
}
|
||||
else:
|
||||
context = {
|
||||
'title': _('Connect ssh terminal'),
|
||||
'messages': _('You should use your ssh client tools '
|
||||
'connect terminal: {} <br /> <br />'
|
||||
'{}'.format(self.object.name, self.object.url)),
|
||||
'redirect_url': reverse('applications:terminal-list')
|
||||
}
|
||||
|
||||
kwargs.update(context)
|
||||
return super(TerminalConnectView, self).get_context_data(**kwargs)
|
Reference in New Issue
Block a user