mirror of https://github.com/jumpserver/jumpserver
Merge branch 'docs' of https://github.com/jumpserver/jumpserver into docs
commit
8e09151a02
|
@ -19,7 +19,7 @@ from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger, get_object_or_none
|
||||||
from ..hands import IsSuperUser
|
from ..hands import IsSuperUser
|
||||||
from ..models import Node
|
from ..models import Node
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
@ -29,6 +29,7 @@ logger = get_logger(__file__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'NodeViewSet', 'NodeChildrenApi',
|
'NodeViewSet', 'NodeChildrenApi',
|
||||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
|
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
|
||||||
|
'NodeAddChildrenApi',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,6 +76,24 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||||
return Response(response, status=200)
|
return Response(response, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeAddChildrenApi(generics.UpdateAPIView):
|
||||||
|
queryset = Node.objects.all()
|
||||||
|
permission_classes = (IsSuperUser,)
|
||||||
|
serializer_class = serializers.NodeAddChildrenSerializer
|
||||||
|
instance = None
|
||||||
|
|
||||||
|
def put(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
nodes_id = request.data.get("nodes")
|
||||||
|
children = [get_object_or_none(Node, id=pk) for pk in nodes_id]
|
||||||
|
for node in children:
|
||||||
|
if not node:
|
||||||
|
continue
|
||||||
|
node.parent = instance
|
||||||
|
node.save()
|
||||||
|
return Response("OK")
|
||||||
|
|
||||||
|
|
||||||
class NodeAddAssetsApi(generics.UpdateAPIView):
|
class NodeAddAssetsApi(generics.UpdateAPIView):
|
||||||
serializer_class = serializers.NodeAssetsSerializer
|
serializer_class = serializers.NodeAssetsSerializer
|
||||||
queryset = Node.objects.all()
|
queryset = Node.objects.all()
|
||||||
|
|
|
@ -15,7 +15,7 @@ class AssetCreateForm(forms.ModelForm):
|
||||||
model = Asset
|
model = Asset
|
||||||
fields = [
|
fields = [
|
||||||
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
||||||
'nodes', 'is_active', 'admin_user', 'labels',
|
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||||
|
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
|
@ -44,7 +44,7 @@ class AssetUpdateForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
fields = [
|
fields = [
|
||||||
'hostname', 'ip', 'port', 'nodes', 'is_active',
|
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
|
||||||
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
|
|
|
@ -38,6 +38,14 @@ def default_node():
|
||||||
|
|
||||||
class Asset(models.Model):
|
class Asset(models.Model):
|
||||||
# Important
|
# Important
|
||||||
|
PLATFORM_CHOICES = (
|
||||||
|
('Linux', 'Linux'),
|
||||||
|
('Unix', 'Unix'),
|
||||||
|
('MacOS', 'MacOS'),
|
||||||
|
('BSD', 'BSD'),
|
||||||
|
('Windows', 'Windows'),
|
||||||
|
('Other', 'Other'),
|
||||||
|
)
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||||
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
||||||
|
@ -64,7 +72,7 @@ class Asset(models.Model):
|
||||||
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
||||||
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
||||||
|
|
||||||
platform = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Platform'))
|
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
||||||
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
||||||
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
||||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
||||||
|
@ -87,6 +95,12 @@ class Asset(models.Model):
|
||||||
return True, ''
|
return True, ''
|
||||||
return False, warning
|
return False, warning
|
||||||
|
|
||||||
|
def is_unixlike(self):
|
||||||
|
if self.platform not in ("Windows", "Other"):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hardware_info(self):
|
def hardware_info(self):
|
||||||
if self.cpu_count:
|
if self.cpu_count:
|
||||||
|
@ -99,6 +113,8 @@ class Asset(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_connective(self):
|
def is_connective(self):
|
||||||
|
if not self.is_unixlike():
|
||||||
|
return True
|
||||||
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
|
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
|
||||||
if val == 1:
|
if val == 1:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -61,6 +61,9 @@ class Node(models.Model):
|
||||||
assets = Asset.objects.filter(nodes__id=self.id)
|
assets = Asset.objects.filter(nodes__id=self.id)
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
|
def get_active_assets(self):
|
||||||
|
return self.get_assets().filter(is_active=True)
|
||||||
|
|
||||||
def get_all_assets(self):
|
def get_all_assets(self):
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
if self.is_root():
|
if self.is_root():
|
||||||
|
@ -70,6 +73,9 @@ class Node(models.Model):
|
||||||
assets = Asset.objects.filter(nodes__in=nodes)
|
assets = Asset.objects.filter(nodes__in=nodes)
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
|
def get_all_active_assets(self):
|
||||||
|
return self.get_all_assets().filter(is_active=True)
|
||||||
|
|
||||||
def is_root(self):
|
def is_root(self):
|
||||||
return self.key == '0'
|
return self.key == '0'
|
||||||
|
|
||||||
|
@ -88,6 +94,10 @@ class Node(models.Model):
|
||||||
else:
|
else:
|
||||||
return parent
|
return parent
|
||||||
|
|
||||||
|
@parent.setter
|
||||||
|
def parent(self, parent):
|
||||||
|
self.key = parent.get_next_child_key()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ancestor(self):
|
def ancestor(self):
|
||||||
if self.parent == self.__class__.root():
|
if self.parent == self.__class__.root():
|
||||||
|
|
|
@ -26,14 +26,14 @@ signer = get_signer()
|
||||||
class AssetUser(models.Model):
|
class AssetUser(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||||
username = models.CharField(max_length=16, verbose_name=_('Username'))
|
username = models.CharField(max_length=128, verbose_name=_('Username'))
|
||||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||||
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
date_updated = models.DateTimeField(auto_now=True)
|
date_updated = models.DateTimeField(auto_now=True)
|
||||||
created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
|
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def password(self):
|
def password(self):
|
||||||
|
@ -175,15 +175,12 @@ class AdminUser(AssetUser):
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def get_related_assets(self):
|
def get_related_assets(self):
|
||||||
assets = []
|
assets = self.asset_set.all()
|
||||||
for cluster in self.cluster_set.all():
|
return assets
|
||||||
assets.extend(cluster.assets.all())
|
|
||||||
assets.extend(self.asset_set.all())
|
|
||||||
return list(set(assets))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def assets_amount(self):
|
def assets_amount(self):
|
||||||
return len(self.get_related_assets())
|
return self.get_related_assets().count()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
|
@ -65,4 +65,8 @@ class NodeAssetsSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Node
|
model = Node
|
||||||
fields = ['assets']
|
fields = ['assets']
|
||||||
|
|
||||||
|
|
||||||
|
class NodeAddChildrenSerializer(serializers.Serializer):
|
||||||
|
nodes = serializers.ListField()
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="ibox-title">
|
<div class="ibox-title">
|
||||||
<h5>{% trans 'Create system user' %}</h5>
|
<h5>{{ action }}</h5>
|
||||||
<div class="ibox-tools">
|
<div class="ibox-tools">
|
||||||
<a class="collapse-link">
|
<a class="collapse-link">
|
||||||
<i class="fa fa-chevron-up"></i>
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
@ -81,6 +81,14 @@
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||||
|
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||||
|
var password_id = '#' + '{{ form.password.id_for_label }}';
|
||||||
|
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||||
|
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
||||||
|
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
||||||
|
|
||||||
|
var need_change_field = [auto_generate_key, private_key_id, sudo_id, shell_id] ;
|
||||||
|
|
||||||
function authFieldsDisplay() {
|
function authFieldsDisplay() {
|
||||||
if ($(auto_generate_key).prop('checked')) {
|
if ($(auto_generate_key).prop('checked')) {
|
||||||
$('.auth-fields').addClass('hidden');
|
$('.auth-fields').addClass('hidden');
|
||||||
|
@ -88,9 +96,23 @@
|
||||||
$('.auth-fields').removeClass('hidden');
|
$('.auth-fields').removeClass('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function protocolChange() {
|
||||||
|
if ($(protocol_id).attr('value') === 'rdp') {
|
||||||
|
$.each(need_change_field, function (index, value) {
|
||||||
|
$(value).addClass('hidden')
|
||||||
|
});
|
||||||
|
$(password_id).removeClass('hidden')
|
||||||
|
} else {
|
||||||
|
$.each(need_change_field, function (index, value) {
|
||||||
|
$(value).removeClass('hidden')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2').select2();
|
$('.select2').select2();
|
||||||
authFieldsDisplay();
|
authFieldsDisplay();
|
||||||
|
protocolChange();
|
||||||
$(auto_generate_key).change(function () {
|
$(auto_generate_key).change(function () {
|
||||||
authFieldsDisplay();
|
authFieldsDisplay();
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="ibox-title">
|
<div class="ibox-title">
|
||||||
<h5>{% trans 'Create admin user' %}</h5>
|
<h5>{{ action }}</h5>
|
||||||
<div class="ibox-tools">
|
<div class="ibox-tools">
|
||||||
<a class="collapse-link">
|
<a class="collapse-link">
|
||||||
<i class="fa fa-chevron-up"></i>
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||||
{% bootstrap_field form.ip layout="horizontal" %}
|
{% bootstrap_field form.ip layout="horizontal" %}
|
||||||
{% bootstrap_field form.port layout="horizontal" %}
|
{% bootstrap_field form.port layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.platform layout="horizontal" %}
|
||||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
|
|
|
@ -2,6 +2,12 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block help_message %}
|
||||||
|
<div class="alert alert-info help-message">
|
||||||
|
左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block custom_head_css_js %}
|
{% block custom_head_css_js %}
|
||||||
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
||||||
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
||||||
|
@ -224,6 +230,9 @@ function editTreeNode() {
|
||||||
if (!current_node){
|
if (!current_node){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (current_node.value) {
|
||||||
|
current_node.name = current_node.value;
|
||||||
|
}
|
||||||
zTree.editName(current_node);
|
zTree.editName(current_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,6 +317,42 @@ function selectQueryNode() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function beforeDrag() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function beforeDrop(treeId, treeNodes, targetNode, moveType) {
|
||||||
|
var treeNodesNames = [];
|
||||||
|
$.each(treeNodes, function (index, value) {
|
||||||
|
treeNodesNames.push(value.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
|
||||||
|
if (confirm(msg)){
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDrag(event, treeId, treeNodes) {
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
|
||||||
|
var treeNodesIds = [];
|
||||||
|
$.each(treeNodes, function (index, value) {
|
||||||
|
treeNodesIds.push(value.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.id);
|
||||||
|
var body = {nodes: treeNodesIds};
|
||||||
|
APIUpdateAttr({
|
||||||
|
url: the_url,
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function initTree() {
|
function initTree() {
|
||||||
var setting = {
|
var setting = {
|
||||||
view: {
|
view: {
|
||||||
|
@ -319,11 +364,24 @@ function initTree() {
|
||||||
enable: true
|
enable: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
edit: {
|
||||||
|
enable: true,
|
||||||
|
showRemoveBtn: false,
|
||||||
|
showRenameBtn: false,
|
||||||
|
drag: {
|
||||||
|
isCopy: true,
|
||||||
|
isMove: true
|
||||||
|
}
|
||||||
|
},
|
||||||
callback: {
|
callback: {
|
||||||
onRightClick: OnRightClick,
|
onRightClick: OnRightClick,
|
||||||
beforeClick: beforeClick,
|
beforeClick: beforeClick,
|
||||||
onRename: onRename,
|
onRename: onRename,
|
||||||
onSelected: onSelected
|
onSelected: onSelected,
|
||||||
|
beforeDrag: beforeDrag,
|
||||||
|
onDrag: onDrag,
|
||||||
|
beforeDrop: beforeDrop,
|
||||||
|
onDrop: onDrop
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -334,7 +392,8 @@ function initTree() {
|
||||||
{#if (value["key"] === "0") {#}
|
{#if (value["key"] === "0") {#}
|
||||||
value["open"] = true;
|
value["open"] = true;
|
||||||
{# }#}
|
{# }#}
|
||||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')'
|
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||||
|
value['value'] = value['value'];
|
||||||
});
|
});
|
||||||
zNodes = data;
|
zNodes = data;
|
||||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||||
|
@ -415,7 +474,7 @@ $(document).ready(function(){
|
||||||
current_node = nodes[0];
|
current_node = nodes[0];
|
||||||
url += "?node_id=" + current_node.id;
|
url += "?node_id=" + current_node.id;
|
||||||
}
|
}
|
||||||
window.open(url);
|
window.open(url, '_self');
|
||||||
})
|
})
|
||||||
.on('click', '.btn_asset_delete', function () {
|
.on('click', '.btn_asset_delete', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||||
{% bootstrap_field form.ip layout="horizontal" %}
|
{% bootstrap_field form.ip layout="horizontal" %}
|
||||||
{% bootstrap_field form.port layout="horizontal" %}
|
{% bootstrap_field form.port layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.platform layout="horizontal" %}
|
||||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
|
|
|
@ -44,6 +44,7 @@ urlpatterns = [
|
||||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$', api.NodeChildrenApi.as_view(), name='node-children'),
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$', api.NodeChildrenApi.as_view(), name='node-children'),
|
||||||
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$', api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -58,8 +58,7 @@ class UserAssetListView(LoginRequiredMixin, TemplateView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'action': _('My assets'),
|
||||||
'action': _('Asset list'),
|
|
||||||
'system_users': SystemUser.objects.all(),
|
'system_users': SystemUser.objects.all(),
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
|
@ -248,6 +247,7 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||||
f = form.cleaned_data['file']
|
f = form.cleaned_data['file']
|
||||||
det_result = chardet.detect(f.read())
|
det_result = chardet.detect(f.read())
|
||||||
f.seek(0) # reset file seek index
|
f.seek(0) # reset file seek index
|
||||||
|
|
||||||
file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
|
file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
|
||||||
csv_file = StringIO(file_data)
|
csv_file = StringIO(file_data)
|
||||||
reader = csv.reader(csv_file)
|
reader = csv.reader(csv_file)
|
||||||
|
|
|
@ -68,10 +68,10 @@ class BaseForm(forms.Form):
|
||||||
class BasicSettingForm(BaseForm):
|
class BasicSettingForm(BaseForm):
|
||||||
SITE_URL = forms.URLField(
|
SITE_URL = forms.URLField(
|
||||||
label=_("Current SITE URL"),
|
label=_("Current SITE URL"),
|
||||||
help_text="http://jumpserver.abc.com:8080"
|
help_text="eg: http://jumpserver.abc.com:8080"
|
||||||
)
|
)
|
||||||
USER_GUIDE_URL = forms.URLField(
|
USER_GUIDE_URL = forms.URLField(
|
||||||
label=_("User Guide URL"),
|
label=_("User Guide URL"), required=False,
|
||||||
help_text=_("User first login update profile done redirect to it")
|
help_text=_("User first login update profile done redirect to it")
|
||||||
)
|
)
|
||||||
EMAIL_SUBJECT_PREFIX = forms.CharField(
|
EMAIL_SUBJECT_PREFIX = forms.CharField(
|
||||||
|
@ -135,7 +135,7 @@ class LDAPSettingForm(BaseForm):
|
||||||
AUTH_LDAP_START_TLS = forms.BooleanField(
|
AUTH_LDAP_START_TLS = forms.BooleanField(
|
||||||
label=_("Use SSL"), initial=False, required=False
|
label=_("Use SSL"), initial=False, required=False
|
||||||
)
|
)
|
||||||
AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), initial=False)
|
AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), initial=False, required=False)
|
||||||
|
|
||||||
|
|
||||||
class TerminalSettingForm(BaseForm):
|
class TerminalSettingForm(BaseForm):
|
||||||
|
|
|
@ -99,9 +99,8 @@ class DatetimeSearchMixin:
|
||||||
|
|
||||||
if date_from_s:
|
if date_from_s:
|
||||||
date_from = timezone.datetime.strptime(date_from_s, self.date_format)
|
date_from = timezone.datetime.strptime(date_from_s, self.date_format)
|
||||||
self.date_from = date_from.replace(
|
tz = timezone.get_current_timezone()
|
||||||
tzinfo=timezone.get_current_timezone()
|
self.date_from = tz.localize(date_from)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.date_from = timezone.now() - timezone.timedelta(7)
|
self.date_from = timezone.now() - timezone.timedelta(7)
|
||||||
|
|
||||||
|
|
|
@ -73,17 +73,20 @@ def to_html(s):
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def time_util_with_seconds(date_from, date_to):
|
def time_util_with_seconds(date_from, date_to):
|
||||||
if date_from and date_to:
|
if not date_from:
|
||||||
delta = date_to - date_from
|
|
||||||
seconds = delta.seconds
|
|
||||||
if seconds < 60:
|
|
||||||
return '{} s'.format(seconds)
|
|
||||||
elif seconds < 60*60:
|
|
||||||
return '{} m'.format(seconds//60)
|
|
||||||
else:
|
|
||||||
return '{} h'.format(seconds//3600)
|
|
||||||
else:
|
|
||||||
return ''
|
return ''
|
||||||
|
if not date_to:
|
||||||
|
return ''
|
||||||
|
date_to = timezone.now()
|
||||||
|
|
||||||
|
delta = date_to - date_from
|
||||||
|
seconds = delta.seconds
|
||||||
|
if seconds < 60:
|
||||||
|
return '{} s'.format(seconds)
|
||||||
|
elif seconds < 60*60:
|
||||||
|
return '{} m'.format(seconds//60)
|
||||||
|
else:
|
||||||
|
return '{} h'.format(seconds//3600)
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -397,6 +397,6 @@ BOOTSTRAP3 = {
|
||||||
}
|
}
|
||||||
|
|
||||||
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600
|
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600
|
||||||
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE
|
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE or 25
|
||||||
DEFAULT_EXPIRED_YEARS = 70
|
DEFAULT_EXPIRED_YEARS = 70
|
||||||
USER_GUIDE_URL = ""
|
USER_GUIDE_URL = ""
|
||||||
|
|
|
@ -4,16 +4,16 @@ from __future__ import unicode_literals
|
||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.views.static import serve as static_serve
|
|
||||||
|
|
||||||
from rest_framework.schemas import get_schema_view
|
from rest_framework.schemas import get_schema_view
|
||||||
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer
|
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer
|
||||||
|
|
||||||
from .views import IndexView
|
from .views import IndexView, LunaView
|
||||||
|
|
||||||
schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer])
|
schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer])
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', IndexView.as_view(), name='index'),
|
url(r'^$', IndexView.as_view(), name='index'),
|
||||||
|
url(r'^luna/$', LunaView.as_view(), name='luna-error'),
|
||||||
url(r'^users/', include('users.urls.views_urls', namespace='users')),
|
url(r'^users/', include('users.urls.views_urls', namespace='users')),
|
||||||
url(r'^assets/', include('assets.urls.views_urls', namespace='assets')),
|
url(r'^assets/', include('assets.urls.views_urls', namespace='assets')),
|
||||||
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
|
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.views.generic import TemplateView
|
from django.http import HttpResponse
|
||||||
|
from django.views.generic import TemplateView, View
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
@ -45,7 +46,8 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
||||||
return self.session_week.values('user').distinct().count()
|
return self.session_week.values('user').distinct().count()
|
||||||
|
|
||||||
def get_week_login_asset_count(self):
|
def get_week_login_asset_count(self):
|
||||||
return self.session_week.values('asset').distinct().count()
|
return self.session_week.count()
|
||||||
|
# return self.session_week.values('asset').distinct().count()
|
||||||
|
|
||||||
def get_month_day_metrics(self):
|
def get_month_day_metrics(self):
|
||||||
month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
|
month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
|
||||||
|
@ -149,3 +151,12 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
||||||
|
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super(IndexView, self).get_context_data(**kwargs)
|
return super(IndexView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class LunaView(View):
|
||||||
|
def get(self, request):
|
||||||
|
msg = """
|
||||||
|
Luna是单独部署的一个程序,你需要部署luna,coco,配置nginx做url分发,
|
||||||
|
如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运
|
||||||
|
"""
|
||||||
|
return HttpResponse(msg)
|
|
@ -54,7 +54,11 @@ class UserGrantedAssetsApi(ListAPIView):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
|
|
||||||
for k, v in NodePermissionUtil.get_user_assets(user).items():
|
for k, v in NodePermissionUtil.get_user_assets(user).items():
|
||||||
k.system_users_granted = v
|
if k.is_unixlike():
|
||||||
|
system_users_granted = [s for s in v if s.protocol == 'ssh']
|
||||||
|
else:
|
||||||
|
system_users_granted = [s for s in v if s.protocol == 'rdp']
|
||||||
|
k.system_users_granted = system_users_granted
|
||||||
queryset.append(k)
|
queryset.append(k)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -118,9 +122,16 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
|
||||||
user = get_object_or_404(User, id=user_id)
|
user = get_object_or_404(User, id=user_id)
|
||||||
|
|
||||||
nodes = NodePermissionUtil.get_user_nodes_with_assets(user)
|
nodes = NodePermissionUtil.get_user_nodes_with_assets(user)
|
||||||
|
assets = {}
|
||||||
|
for k, v in NodePermissionUtil.get_user_assets(user).items():
|
||||||
|
if k.is_unixlike():
|
||||||
|
system_users_granted = [s for s in v if s.protocol == 'ssh']
|
||||||
|
else:
|
||||||
|
system_users_granted = [s for s in v if s.protocol == 'rdp']
|
||||||
|
assets[k] = system_users_granted
|
||||||
for node, v in nodes.items():
|
for node, v in nodes.items():
|
||||||
for asset in v['assets']:
|
for asset in v['assets']:
|
||||||
asset.system_users_granted = v['system_users']
|
asset.system_users_granted = assets[asset]
|
||||||
node.assets_granted = v['assets']
|
node.assets_granted = v['assets']
|
||||||
queryset.append(node)
|
queryset.append(node)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
|
@ -12,7 +12,7 @@ class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = NodePermission
|
model = NodePermission
|
||||||
fields = [
|
fields = [
|
||||||
'node', 'user_group', 'system_user',
|
'id', 'node', 'user_group', 'system_user',
|
||||||
'is_active', 'date_expired'
|
'is_active', 'date_expired'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="ibox float-e-margins">
|
<div class="ibox float-e-margins">
|
||||||
<div class="ibox-title">
|
<div class="ibox-title">
|
||||||
<h5>{% trans 'Create asset permission ' %}</h5>
|
<h5>{{ action }}</h5>
|
||||||
<div class="ibox-tools">
|
<div class="ibox-tools">
|
||||||
<a class="collapse-link">
|
<a class="collapse-link">
|
||||||
<i class="fa fa-chevron-up"></i>
|
<i class="fa fa-chevron-up"></i>
|
||||||
|
|
|
@ -215,16 +215,6 @@ $(document).ready(function(){
|
||||||
initTable();
|
initTable();
|
||||||
initTree();
|
initTree();
|
||||||
})
|
})
|
||||||
.on('click', '.btn-create-asset', function () {
|
|
||||||
var url = "{% url 'assets:asset-create' %}";
|
|
||||||
var nodes = zTree.getSelectedNodes();
|
|
||||||
var current_node;
|
|
||||||
if (nodes && nodes.length ===1 ){
|
|
||||||
current_node = nodes[0];
|
|
||||||
url += "?node=" + current_node.id;
|
|
||||||
}
|
|
||||||
window.open(url);
|
|
||||||
})
|
|
||||||
.on('click', '.btn-del', function () {
|
.on('click', '.btn-del', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var uid = $this.data('uid');
|
var uid = $this.data('uid');
|
||||||
|
@ -241,7 +231,7 @@ $(document).ready(function(){
|
||||||
current_node = nodes[0];
|
current_node = nodes[0];
|
||||||
url += "?node_id=" + current_node.id;
|
url += "?node_id=" + current_node.id;
|
||||||
}
|
}
|
||||||
window.open(url);
|
window.open(url, '_self');
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -56,7 +56,7 @@ class NodePermissionUtil:
|
||||||
nodes_with_assets = dict()
|
nodes_with_assets = dict()
|
||||||
for node, system_users in nodes.items():
|
for node, system_users in nodes.items():
|
||||||
nodes_with_assets[node] = {
|
nodes_with_assets[node] = {
|
||||||
'assets': node.get_assets(),
|
'assets': node.get_active_assets(),
|
||||||
'system_users': system_users
|
'system_users': system_users
|
||||||
}
|
}
|
||||||
return nodes_with_assets
|
return nodes_with_assets
|
||||||
|
@ -87,7 +87,7 @@ class NodePermissionUtil:
|
||||||
nodes_with_assets = dict()
|
nodes_with_assets = dict()
|
||||||
for node, system_users in nodes.items():
|
for node, system_users in nodes.items():
|
||||||
nodes_with_assets[node] = {
|
nodes_with_assets[node] = {
|
||||||
'assets': node.get_assets(),
|
'assets': node.get_active_assets(),
|
||||||
'system_users': system_users
|
'system_users': system_users
|
||||||
}
|
}
|
||||||
return nodes_with_assets
|
return nodes_with_assets
|
||||||
|
|
|
@ -427,3 +427,9 @@ div.dataTables_wrapper div.dataTables_filter {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-dropdown li a {
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3299,7 +3299,7 @@ body.tour-open .animated {
|
||||||
border-bottom: 1px solid #e7eaec;
|
border-bottom: 1px solid #e7eaec;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
font-family: "open sans", "Helvetica Neue", "微软雅黑", Helvetica, Arial, sans-serif;
|
||||||
background-color: #2f4050;
|
background-color: #2f4050;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #676a6c;
|
color: #676a6c;
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<strong>Copyright</strong> 北京堆栈科技有限公司 © 2014-2018
|
|
@ -14,8 +14,13 @@
|
||||||
{# <span class="m-r-sm text-muted welcome-message">{% trans 'Welcome to use Jumpserver system' %}</span>#}
|
{# <span class="m-r-sm text-muted welcome-message">{% trans 'Welcome to use Jumpserver system' %}</span>#}
|
||||||
{# </li>#}
|
{# </li>#}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a class="dropdown-toggle count-info" data-toggle="dropdown" href="#">
|
<a class="dropdown-toggle count-info" data-toggle="dropdown" href="https://market.aliyun.com/products/53690006/cmgj026011.html?spm=5176.730005.0.0.cY2io1">
|
||||||
<span class="m-r-sm text-muted welcome-message">{% trans 'Help' %}</span>
|
<span class="m-r-sm text-muted welcome-message">{% trans 'Supports' %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<a class="count-info" href="http://jumpserver.readthedocs.io/">
|
||||||
|
<span class="m-r-sm text-muted welcome-message">{% trans 'Docs' %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
|
@ -28,9 +33,8 @@
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu animated fadeInRight m-t-xs">
|
<ul class="dropdown-menu animated fadeInRight m-t-xs profile-dropdown">
|
||||||
<li><a href="{% url 'users:user-profile' %}"><i class="fa fa-cogs"> </i><span> {% trans 'Profile' %}</span></a></li>
|
<li><a href="{% url 'users:user-profile' %}"><i class="fa fa-cogs"> </i><span> {% trans 'Profile' %}</span></a></li>
|
||||||
<li class="divider"></li>
|
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
{% if request.COOKIES.IN_ADMIN_PAGE == 'No' %}
|
{% if request.COOKIES.IN_ADMIN_PAGE == 'No' %}
|
||||||
<li><a id="switch_admin"><i class="fa fa-exchange"></i><span> {% trans 'Admin page' %}</span></a></li>
|
<li><a id="switch_admin"><i class="fa fa-exchange"></i><span> {% trans 'Admin page' %}</span></a></li>
|
||||||
|
@ -57,11 +61,11 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="">{% trans 'Dashboard' %}</a>
|
<a href="">{% trans 'Dashboard' %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
{% if app %}
|
{% if app %}
|
||||||
|
<li>
|
||||||
<a>{{ app }}</a>
|
<a>{{ app }}</a>
|
||||||
{% endif %}
|
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if action %}
|
{% if action %}
|
||||||
<li class="active">
|
<li class="active">
|
||||||
<strong>{{ action }}</strong>
|
<strong>{{ action }}</strong>
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<li id="index">
|
<li id="index">
|
||||||
<a href="{% url 'index' %}">
|
<a href="{% url 'index' %}">
|
||||||
<i class="fa fa-dashboard" style="font-size: 13px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span><span class="label label-info pull-right"></span>
|
<i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span><span
|
||||||
|
class="label label-info pull-right"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li id="users">
|
<li id="users">
|
||||||
<a href="#">
|
<a href="#">
|
||||||
<i class="fa fa-group" style="font-size: 13px"></i> <span class="nav-label">{% trans 'Users' %}</span><span class="fa arrow"></span>
|
<i class="fa fa-group" style="width: 14px"></i> <span class="nav-label">{% trans 'Users' %}</span><span class="fa arrow"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="nav nav-second-level active">
|
<ul class="nav nav-second-level active">
|
||||||
<li id="user"><a href="{% url 'users:user-list' %}">{% trans 'User list' %}</a></li>
|
<li id="user"><a href="{% url 'users:user-list' %}">{% trans 'User list' %}</a></li>
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li id="assets">
|
<li id="assets">
|
||||||
<a>
|
<a>
|
||||||
<i class="fa fa-inbox"></i> <span class="nav-label">{% trans 'Assets' %}</span><span class="fa arrow"></span>
|
<i class="fa fa-inbox" style="width: 14px"></i> <span class="nav-label">{% trans 'Assets' %}</span><span class="fa arrow"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="nav nav-second-level">
|
<ul class="nav nav-second-level">
|
||||||
<li id="asset"><a href="{% url 'assets:asset-list' %}">{% trans 'Asset list' %}</a></li>
|
<li id="asset"><a href="{% url 'assets:asset-list' %}">{% trans 'Asset list' %}</a></li>
|
||||||
|
@ -26,7 +27,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li id="perms">
|
<li id="perms">
|
||||||
<a href="#"><i class="fa fa-edit"></i> <span class="nav-label">{% trans 'Perms' %}</span><span class="fa arrow"></span></a>
|
<a href="#"><i class="fa fa-edit" style="width: 14px"></i> <span class="nav-label">{% trans 'Perms' %}</span><span class="fa arrow"></span></a>
|
||||||
<ul class="nav nav-second-level">
|
<ul class="nav nav-second-level">
|
||||||
<li id="asset-permission">
|
<li id="asset-permission">
|
||||||
<a href="{% url 'perms:asset-permission-list' %}">{% trans 'Asset permission' %}</a>
|
<a href="{% url 'perms:asset-permission-list' %}">{% trans 'Asset permission' %}</a>
|
||||||
|
@ -35,18 +36,23 @@
|
||||||
</li>
|
</li>
|
||||||
<li id="terminal">
|
<li id="terminal">
|
||||||
<a>
|
<a>
|
||||||
<i class="fa fa-rocket"></i> <span class="nav-label">{% trans 'Sessions' %}</span><span class="fa arrow"></span>
|
<i class="fa fa-rocket" style="width: 14px"></i> <span class="nav-label">{% trans 'Sessions' %}</span><span class="fa arrow"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="nav nav-second-level">
|
<ul class="nav nav-second-level">
|
||||||
<li id="session-online"><a href="{% url 'terminal:session-online-list' %}">{% trans 'Session online' %}</a></li>
|
<li id="session-online"><a href="{% url 'terminal:session-online-list' %}">{% trans 'Session online' %}</a></li>
|
||||||
<li id="session-offline"><a href="{% url 'terminal:session-offline-list' %}">{% trans 'Session offline' %}</a></li>
|
<li id="session-offline"><a href="{% url 'terminal:session-offline-list' %}">{% trans 'Session offline' %}</a></li>
|
||||||
<li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Commands' %}</a></li>
|
<li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Commands' %}</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'terminal:web-terminal' %}" target="_blank">
|
||||||
|
<span class="nav-label">{% trans 'Web terminal' %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li>
|
<li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li id="ops">
|
<li id="ops">
|
||||||
<a>
|
<a>
|
||||||
<i class="fa fa-coffee"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
|
<i class="fa fa-coffee" style="width: 14px"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="nav nav-second-level">
|
<ul class="nav nav-second-level">
|
||||||
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li>
|
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li>
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<li id="assets">
|
<li id="assets">
|
||||||
<a href="{% url 'assets:user-asset-list' %}">
|
<a href="{% url 'assets:user-asset-list' %}">
|
||||||
<i class="fa fa-files-o"></i><span class="nav-label">{% trans 'My assets' %}</span><span class="label label-info pull-right"></span>
|
<i class="fa fa-files-o" style="width: 14px"></i><span class="nav-label">{% trans 'My assets' %}</span><span class="label label-info pull-right"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li id="users">
|
<li id="users">
|
||||||
<a href="{% url 'users:user-profile' %}">
|
<a href="{% url 'users:user-profile' %}">
|
||||||
<i class="fa fa-user" ></i> <span class="nav-label">{% trans 'Profile' %}</span><span class="label label-info pull-right"></span>
|
<i class="fa fa-user" style="width: 14px"></i> <span class="nav-label">{% trans 'Profile' %}</span><span class="label label-info pull-right"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li >
|
<li >
|
||||||
<a href="{% url 'terminal:web-terminal' %}" target="_blank"><i class="fa fa-window-maximize"></i>
|
<a href="{% url 'terminal:web-terminal' %}" target="_blank"><i class="fa fa-window-maximize" style="width: 14px"></i>
|
||||||
<span class="nav-label">{% trans 'Web terminal' %}</span>
|
<span class="nav-label">{% trans 'Web terminal' %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
|
@ -3,7 +3,7 @@
|
||||||
<li class="nav-header">
|
<li class="nav-header">
|
||||||
<div class="dropdown profile-element">
|
<div class="dropdown profile-element">
|
||||||
<div href="http://www.jumpserver.org" target="_blank">
|
<div href="http://www.jumpserver.org" target="_blank">
|
||||||
<img alt="image" height="55" src="/static/img/logo-text.png" style="margin-left: 10px"/>
|
<img alt="logo" height="55" width="185" src="/static/img/logo-text.png" style="margin-left: 20px"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
Copyright Jumpserver.org
|
{% include '_copyright.html' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 text-right">
|
<div class="col-md-6 text-right">
|
||||||
<small>2014-2018</small>
|
<small>2014-2018</small>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ibox-content">
|
<div class="ibox-content">
|
||||||
<h1 class="no-margins"><a href="{% url 'users:user-list' %}">{{ users_count }}</a></h1>
|
<h1 class="no-margins"><a href="{% url 'users:user-list' %}">{{ users_count }}</a></h1>
|
||||||
<small>All user</small>
|
<small>All users</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ibox-content">
|
<div class="ibox-content">
|
||||||
<h1 class="no-margins"><a href="{% url 'assets:asset-list' %}">{{ assets_count }}</a></h1>
|
<h1 class="no-margins"><a href="{% url 'assets:asset-list' %}">{{ assets_count }}</a></h1>
|
||||||
<small>All host</small>
|
<small>All hosts</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ibox-content">
|
<div class="ibox-content">
|
||||||
<h1 class="no-margins"><a href="{% url 'terminal:session-online-list' %}"> <span id="online_users"></span>{{ online_user_count }}</a></h1>
|
<h1 class="no-margins"><a href="{% url 'terminal:session-online-list' %}"> <span id="online_users"></span>{{ online_user_count }}</a></h1>
|
||||||
<small>Online user</small>
|
<small>Online users</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-2 border-bottom white-bg dashboard-header" style="margin-left:15px;height: 346px">
|
<div class="col-sm-2 border-bottom white-bg dashboard-header" style="margin-left:15px;height: 346px">
|
||||||
<h2>活跃用户TOP5</h2>
|
<h2>活跃用户TOP5</h2>
|
||||||
<small>过去一周共有<span class="text-info">{{ user_visit_count_weekly }}</span>位用户登录<span class="text-success">{{ asset_visit_count_weekly }}</span>次服务器.</small>
|
<small>过去一周共有<span class="text-info">{{ user_visit_count_weekly }}</span>位用户登录<span class="text-success">{{ asset_visit_count_weekly }}</span>次资产.</small>
|
||||||
<ul class="list-group clear-list m-t">
|
<ul class="list-group clear-list m-t">
|
||||||
{% for data in user_visit_count_top_five %}
|
{% for data in user_visit_count_top_five %}
|
||||||
<li class="list-group-item fist-item">
|
<li class="list-group-item fist-item">
|
||||||
|
|
|
@ -25,18 +25,22 @@ def get_all_replay_storage():
|
||||||
|
|
||||||
|
|
||||||
class TerminalForm(forms.ModelForm):
|
class TerminalForm(forms.ModelForm):
|
||||||
command_storage = forms.ChoiceField(choices=get_all_command_storage(),
|
command_storage = forms.ChoiceField(
|
||||||
label=_("Command storage"))
|
choices=get_all_command_storage(),
|
||||||
replay_storage = forms.ChoiceField(choices=get_all_replay_storage(),
|
label=_("Command storage")
|
||||||
label=_("Replay storage"))
|
)
|
||||||
|
replay_storage = forms.ChoiceField(
|
||||||
|
choices=get_all_replay_storage(),
|
||||||
|
label=_("Replay storage")
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Terminal
|
model = Terminal
|
||||||
fields = ['name', 'remote_addr', 'ssh_port', 'http_port', 'comment', 'command_storage', 'replay_storage']
|
fields = [
|
||||||
|
'name', 'remote_addr', 'ssh_port', 'http_port', 'comment',
|
||||||
|
'command_storage', 'replay_storage',
|
||||||
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'ssh_port': _("Coco ssh listen port"),
|
'ssh_port': _("Coco ssh listen port"),
|
||||||
'http_port': _("Coco http/ws listen port"),
|
'http_port': _("Coco http/ws listen port"),
|
||||||
}
|
}
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(attrs={'readonly': 'readonly'})
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import uuid
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils import timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
@ -127,6 +128,7 @@ class Session(models.Model):
|
||||||
has_replay = models.BooleanField(default=False, verbose_name=_("Replay"))
|
has_replay = models.BooleanField(default=False, verbose_name=_("Replay"))
|
||||||
has_command = models.BooleanField(default=False, verbose_name=_("Command"))
|
has_command = models.BooleanField(default=False, verbose_name=_("Command"))
|
||||||
terminal = models.ForeignKey(Terminal, null=True, on_delete=models.CASCADE)
|
terminal = models.ForeignKey(Terminal, null=True, on_delete=models.CASCADE)
|
||||||
|
date_last_active = models.DateTimeField(verbose_name=_("Date last active"), default=timezone.now)
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,36 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from common.celery import register_as_period_task, after_app_ready_start, \
|
||||||
|
after_app_shutdown_clean
|
||||||
|
from .models import Status, Session
|
||||||
|
|
||||||
|
|
||||||
CACHE_REFRESH_INTERVAL = 10
|
CACHE_REFRESH_INTERVAL = 10
|
||||||
RUNNING = False
|
RUNNING = False
|
||||||
|
|
||||||
|
|
||||||
# Todo: 定期清理上报history
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def clean_terminal_history():
|
@register_as_period_task(interval=3600)
|
||||||
pass
|
@after_app_ready_start
|
||||||
|
@after_app_shutdown_clean
|
||||||
|
def delete_terminal_status_period():
|
||||||
|
yesterday = timezone.now() - datetime.timedelta(days=3)
|
||||||
|
Status.objects.filter(date_created__lt=yesterday).delete()
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
@register_as_period_task(interval=3600)
|
||||||
|
@after_app_ready_start
|
||||||
|
@after_app_shutdown_clean
|
||||||
|
def clean_orphan_session():
|
||||||
|
active_sessions = Session.objects.filter(is_finished=False)
|
||||||
|
for session in active_sessions:
|
||||||
|
if not session.terminal.is_active:
|
||||||
|
session.is_finished = True
|
||||||
|
session.save()
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
<th class="text-center">{% trans 'Terminal' %}</th>
|
<th class="text-center">{% trans 'Terminal' %}</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 %}
|
{% endblock %}
|
||||||
|
@ -94,6 +95,7 @@
|
||||||
<td class="text-center">{{ session.id | get_session_command_amount }}</td>
|
<td class="text-center">{{ session.id | get_session_command_amount }}</td>
|
||||||
|
|
||||||
<td class="text-center">{{ session.date_start }}</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 class="text-center">{{ session.date_start|time_util_with_seconds:session.date_end }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if session.is_finished %}
|
{% if session.is_finished %}
|
||||||
|
@ -107,6 +109,21 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content_bottom_left %}
|
||||||
|
<div id="actions" >
|
||||||
|
<div class="input-group">
|
||||||
|
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||||
|
<option value="delete">{% trans 'Terminate selected' %}</option>
|
||||||
|
</select>
|
||||||
|
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||||
|
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||||
|
{% trans 'Submit' %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% 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>
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from common.mixins import DatetimeSearchMixin
|
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
|
||||||
from ..models import Command
|
from ..models import Command
|
||||||
from .. import utils
|
from .. import utils
|
||||||
from ..backends import get_multi_command_store
|
from ..backends import get_multi_command_store
|
||||||
|
@ -15,7 +15,7 @@ __all__ = ['CommandListView']
|
||||||
common_storage = get_multi_command_store()
|
common_storage = get_multi_command_store()
|
||||||
|
|
||||||
|
|
||||||
class CommandListView(DatetimeSearchMixin, ListView):
|
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
|
||||||
model = Command
|
model = Command
|
||||||
template_name = "terminal/command_list.html"
|
template_name = "terminal/command_list.html"
|
||||||
context_object_name = 'command_list'
|
context_object_name = 'command_list'
|
||||||
|
|
|
@ -97,7 +97,7 @@ class SessionOfflineListView(SessionListView):
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class SessionDetailView(SingleObjectMixin, ListView):
|
class SessionDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView):
|
||||||
template_name = 'terminal/session_detail.html'
|
template_name = 'terminal/session_detail.html'
|
||||||
model = Session
|
model = Session
|
||||||
object = None
|
object = None
|
||||||
|
|
|
@ -145,7 +145,8 @@ class UserAuthApi(APIView):
|
||||||
|
|
||||||
if not login_ip:
|
if not login_ip:
|
||||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||||
if x_forwarded_for:
|
|
||||||
|
if x_forwarded_for and x_forwarded_for[0]:
|
||||||
login_ip = x_forwarded_for[0]
|
login_ip = x_forwarded_for[0]
|
||||||
else:
|
else:
|
||||||
login_ip = request.META.get("REMOTE_ADDR")
|
login_ip = request.META.get("REMOTE_ADDR")
|
||||||
|
|
|
@ -6,3 +6,6 @@ from django.apps import AppConfig
|
||||||
class UsersConfig(AppConfig):
|
class UsersConfig(AppConfig):
|
||||||
name = 'users'
|
name = 'users'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from . import signals_handler
|
||||||
|
super().ready()
|
||||||
|
|
|
@ -16,7 +16,8 @@ class AccessKey(models.Model):
|
||||||
default=uuid.uuid4, editable=False)
|
default=uuid.uuid4, editable=False)
|
||||||
secret = models.UUIDField(verbose_name='AccessKeySecret',
|
secret = models.UUIDField(verbose_name='AccessKeySecret',
|
||||||
default=uuid.uuid4, editable=False)
|
default=uuid.uuid4, editable=False)
|
||||||
user = models.ForeignKey(User, verbose_name='User', on_delete=models.CASCADE, related_name='access_key')
|
user = models.ForeignKey(User, verbose_name='User',
|
||||||
|
on_delete=models.CASCADE, related_name='access_key')
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return str(self.id)
|
return str(self.id)
|
||||||
|
|
|
@ -22,6 +22,7 @@ class UserGroup(NoDeleteModelMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
verbose_name = _("User group")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def initial(cls):
|
def initial(cls):
|
||||||
|
|
|
@ -151,6 +151,10 @@ class User(AbstractUser):
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.name:
|
if not self.name:
|
||||||
self.name = self.username
|
self.name = self.username
|
||||||
|
if self.username == 'admin':
|
||||||
|
self.role = 'Admin'
|
||||||
|
self.is_active = True
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -247,6 +251,7 @@ class User(AbstractUser):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['username']
|
ordering = ['username']
|
||||||
|
verbose_name = _("User")
|
||||||
|
|
||||||
#: Use this method initial user
|
#: Use this method initial user
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -1,21 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
from django.dispatch import Signal
|
||||||
#
|
|
||||||
|
|
||||||
from django.dispatch import Signal, receiver
|
|
||||||
from django.db.models.signals import post_save
|
|
||||||
|
|
||||||
from common.utils import get_logger
|
|
||||||
from .models import User
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
post_user_create = Signal(providing_args=('user',))
|
||||||
def on_user_created(sender, instance=None, created=False, **kwargs):
|
|
||||||
if created:
|
|
||||||
logger.debug("Receive user `{}` create signal".format(instance.name))
|
|
||||||
from .utils import send_user_created_mail
|
|
||||||
logger.info(" - Sending welcome mail ...".format(instance.name))
|
|
||||||
if instance.email:
|
|
||||||
send_user_created_mail(instance)
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from django.db.models.signals import post_save
|
||||||
|
|
||||||
|
from common.utils import get_logger
|
||||||
|
from .models import User
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=User)
|
||||||
|
def on_user_created(sender, instance=None, created=False, **kwargs):
|
||||||
|
if created:
|
||||||
|
logger.debug("Receive user `{}` create signal".format(instance.name))
|
||||||
|
from .utils import send_user_created_mail
|
||||||
|
logger.info(" - Sending welcome mail ...".format(instance.name))
|
||||||
|
if instance.email:
|
||||||
|
send_user_created_mail(instance)
|
|
@ -51,11 +51,8 @@
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-12">
|
||||||
Copyright Jumpserver.org
|
{% include '_copyright.html' %}
|
||||||
</div>
|
|
||||||
<div class="col-md-6 text-right">
|
|
||||||
<small>© 2014-2018</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,24 +22,27 @@
|
||||||
<div class="loginColumns animated fadeInDown">
|
<div class="loginColumns animated fadeInDown">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 class="font-bold">欢迎使用Jumpserver开源跳板机</h2>
|
<h2 class="font-bold">欢迎使用Jumpserver开源堡垒机</h2>
|
||||||
<p>
|
<p>
|
||||||
Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互联网企业高效 用户、资产、权限、审计 管理
|
全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
我们自五湖四海,我们对开源精神无比敬仰和崇拜,我们对完美、整洁、优雅 无止境的追求
|
使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
专注自动化运维,努力打造 易用、稳定、安全、自动化 的跳板机, 这是我们的不懈的追求和动力
|
采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<small>永远年轻,永远热泪盈眶 stay foolish stay hungry</small>
|
改变世界,从一点点开始。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="ibox-content">
|
<div class="ibox-content">
|
||||||
<div><img src="{% static 'img/logo.png' %}" width="82" height="82"> <span class="font-bold text-center" style="font-size: 32px; font-family: inherit">{% trans 'Login' %}</span></div>
|
<div>
|
||||||
|
<img src="{% static 'img/logo.png' %}" width="60" height="60">
|
||||||
|
<span class="font-bold text-center" style="font-size: 24px; font-family: inherit; margin-left: 20px">{% trans 'Login' %}</span>
|
||||||
|
</div>
|
||||||
<form class="m-t" role="form" method="post" action="">
|
<form class="m-t" role="form" method="post" action="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
|
@ -60,12 +63,16 @@
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Login' %}</button>
|
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Login' %}</button>
|
||||||
|
|
||||||
|
{% if demo_mode %}
|
||||||
|
<p class="text-muted font-bold" style="color: red">
|
||||||
|
Demo账号: admin 密码: admin
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<a href="{% url 'users:forgot-password' %}">
|
<a href="{% url 'users:forgot-password' %}">
|
||||||
<small>{% trans 'Forgot password' %}?</small>
|
<small>{% trans 'Forgot password' %}?</small>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p class="text-muted text-center">
|
|
||||||
</p>
|
|
||||||
</form>
|
</form>
|
||||||
<p class="m-t">
|
<p class="m-t">
|
||||||
</p>
|
</p>
|
||||||
|
@ -74,11 +81,8 @@
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-12">
|
||||||
Copyright 北京堆栈科技有限公司
|
{% include '_copyright.html' %}
|
||||||
</div>
|
|
||||||
<div class="col-md-6 text-right">
|
|
||||||
<small>© 2014-2018</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -70,11 +70,8 @@
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-12">
|
||||||
Copyright Jumpserver.org
|
{% include '_copyright.html' %}
|
||||||
</div>
|
|
||||||
<div class="col-md-6 text-right">
|
|
||||||
<small>© 2014-2018</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -92,8 +92,8 @@ class UserGroupGrantedAssetView(AdminUserRequiredMixin, DetailView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
'app': 'User',
|
'app': _('Users'),
|
||||||
'action': 'User group granted asset',
|
'action': _('User group granted asset'),
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import os
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.contrib.auth import login as auth_login, logout as auth_logout
|
from django.contrib.auth import login as auth_login, logout as auth_logout
|
||||||
|
@ -56,6 +57,7 @@ class UserLoginView(FormView):
|
||||||
return HttpResponse(_("Please enable cookies and try again."))
|
return HttpResponse(_("Please enable cookies and try again."))
|
||||||
auth_login(self.request, form.get_user())
|
auth_login(self.request, form.get_user())
|
||||||
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||||
|
|
||||||
if x_forwarded_for and x_forwarded_for[0]:
|
if x_forwarded_for and x_forwarded_for[0]:
|
||||||
login_ip = x_forwarded_for[0]
|
login_ip = x_forwarded_for[0]
|
||||||
else:
|
else:
|
||||||
|
@ -75,6 +77,13 @@ class UserLoginView(FormView):
|
||||||
self.redirect_field_name,
|
self.redirect_field_name,
|
||||||
self.request.GET.get(self.redirect_field_name, reverse('index')))
|
self.request.GET.get(self.redirect_field_name, reverse('index')))
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {
|
||||||
|
'demo_mode': os.environ.get("DEMO_MODE"),
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(never_cache, name='dispatch')
|
@method_decorator(never_cache, name='dispatch')
|
||||||
class UserLogoutView(TemplateView):
|
class UserLogoutView(TemplateView):
|
||||||
|
@ -237,7 +246,7 @@ class LoginLogListView(DatetimeSearchMixin, ListView):
|
||||||
if self.user:
|
if self.user:
|
||||||
queryset = queryset.filter(username=self.user)
|
queryset = queryset.filter(username=self.user)
|
||||||
if self.keyword:
|
if self.keyword:
|
||||||
queryset = self.queryset.filter(
|
queryset = queryset.filter(
|
||||||
Q(ip__contains=self.keyword) |
|
Q(ip__contains=self.keyword) |
|
||||||
Q(city__contains=self.keyword) |
|
Q(city__contains=self.keyword) |
|
||||||
Q(username__contains=self.keyword)
|
Q(username__contains=self.keyword)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import json
|
||||||
import uuid
|
import uuid
|
||||||
import csv
|
import csv
|
||||||
import codecs
|
import codecs
|
||||||
|
import chardet
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
@ -20,6 +21,7 @@ from django.utils.translation import ugettext as _
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
|
from django.db import transaction
|
||||||
from django.views.generic.edit import (
|
from django.views.generic.edit import (
|
||||||
CreateView, UpdateView, FormMixin, FormView
|
CreateView, UpdateView, FormMixin, FormView
|
||||||
)
|
)
|
||||||
|
@ -33,7 +35,7 @@ from common.utils import get_logger, get_object_or_none, is_uuid
|
||||||
from .. import forms
|
from .. import forms
|
||||||
from ..models import User, UserGroup
|
from ..models import User, UserGroup
|
||||||
from ..utils import AdminUserRequiredMixin
|
from ..utils import AdminUserRequiredMixin
|
||||||
from ..signals import on_user_created
|
from ..signals import post_user_create
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -212,8 +214,10 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||||
|
|
||||||
# todo: need be patch, method to long
|
# todo: need be patch, method to long
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
file = form.cleaned_data['file']
|
f = form.cleaned_data['file']
|
||||||
data = file.read().decode('utf-8').strip(codecs.BOM_UTF8.decode('utf-8'))
|
det_result = chardet.detect(f.read())
|
||||||
|
f.seek(0) # reset file seek index
|
||||||
|
data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
|
||||||
csv_file = StringIO(data)
|
csv_file = StringIO(data)
|
||||||
reader = csv.reader(csv_file)
|
reader = csv.reader(csv_file)
|
||||||
csv_data = [row for row in reader]
|
csv_data = [row for row in reader]
|
||||||
|
@ -252,15 +256,15 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
user_dict[k] = v
|
user_dict[k] = v
|
||||||
|
user = get_object_or_none(User, id=id_) if id_ and is_uuid(id_) else None
|
||||||
user = get_object_or_none(User, id=id_) if is_uuid(id_) else None
|
|
||||||
if not user:
|
if not user:
|
||||||
try:
|
try:
|
||||||
groups = user_dict.pop('groups')
|
with transaction.atomic():
|
||||||
user = User.objects.create(**user_dict)
|
groups = user_dict.pop('groups')
|
||||||
user.groups.set(groups)
|
user = User.objects.create(**user_dict)
|
||||||
created.append(user_dict['username'])
|
user.groups.set(groups)
|
||||||
on_user_created.send(self.__class__, user=user)
|
created.append(user_dict['username'])
|
||||||
|
post_user_create.send(self.__class__, user=user)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
failed.append('%s: %s' % (user_dict['username'], str(e)))
|
failed.append('%s: %s' % (user_dict['username'], str(e)))
|
||||||
else:
|
else:
|
||||||
|
@ -309,7 +313,6 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Users'),
|
|
||||||
'action': _('Profile'),
|
'action': _('Profile'),
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
|
@ -4,7 +4,7 @@
|
||||||
contain the root `toctree` directive.
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
Jumpserver 文档
|
Jumpserver 文档
|
||||||
====================
|
======================================
|
||||||
|
|
||||||
目录:
|
目录:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
简介
|
||||||
|
============
|
||||||
|
|
||||||
|
Jumpserver是混合云下更好用的堡垒机, 分布式架构设计无限扩展,轻松对接混合云资产,支持使用云存储(AWS S3, ES等)存储录像、命令
|
||||||
|
|
||||||
|
Jumpserver颠覆传统堡垒机, 无主机和并发数量限制,支持水平扩容,FIT2CLOUD提供完备的商业服务支持,用户无后顾之忧
|
||||||
|
|
||||||
|
Jumpserver拥有极致的用户体验, 极致UI体验,容器化的部署方式,部署过程方便快捷,可持续升级
|
||||||
|
|
||||||
|
|
||||||
|
组件说明
|
||||||
|
++++++++++++++++++++++++
|
||||||
|
|
||||||
|
Jumpserver
|
||||||
|
```````````
|
||||||
|
现指Jumpserver管理后台,是核心组件(Core), 使用 Django Class Based View 风格开发,支持Restful API。
|
||||||
|
|
||||||
|
`Github <https://github.com/jumpserver/jumpserver.git>`_
|
||||||
|
|
||||||
|
|
||||||
|
Coco
|
||||||
|
````````
|
||||||
|
实现了SSH Server 和 Web Terminal Server的组件,提供ssh和websocket接口, 使用 Paramiko 和 Flask 开发。
|
||||||
|
|
||||||
|
|
||||||
|
`Github <https://github.com/jumpserver/coco.git>`__
|
||||||
|
|
||||||
|
|
||||||
|
Luna
|
||||||
|
````````
|
||||||
|
现在是Web Terminal前端,计划前端页面都由该项目提供,Jumpserver只提供API,不再负责后台渲染html等。
|
||||||
|
|
||||||
|
`Github <https://github.com/jumpserver/luna.git>`__
|
||||||
|
|
||||||
|
|
||||||
|
Guacamole
|
||||||
|
```````````
|
||||||
|
Apache 跳板机项目,Jumpserver使用其组件实现RDP功能,Jumpserver并没有修改其代码而是添加了额外的插件,支持Jumpserver调用
|
||||||
|
|
||||||
|
|
||||||
|
Jumpserver-python-sdk
|
||||||
|
```````````````````````
|
||||||
|
Jumpserver API Python SDK,Coco目前使用该SDK与Jumpserver API交互
|
||||||
|
|
||||||
|
`Github <https://github.com/jumpserver/jumpserver-python-sdk.git>`__
|
||||||
|
|
||||||
|
|
||||||
|
组件架构图
|
||||||
|
++++++++++++++++++++++++
|
||||||
|
.. image:: _static/img/structure.png
|
||||||
|
:alt: 组件架构图
|
|
@ -1,7 +1,7 @@
|
||||||
用户使用文档
|
用户使用文档
|
||||||
===============
|
=============
|
||||||
|
|
||||||
这部分给您介绍Jumpserver的用户使用方法。
|
这部分给您介绍Jumpserver的用户管理模块的使用方法。
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
|
@ -56,8 +56,8 @@ uritemplate==3.0.0
|
||||||
urllib3==1.22
|
urllib3==1.22
|
||||||
vine==1.1.4
|
vine==1.1.4
|
||||||
gunicorn==19.7.1
|
gunicorn==19.7.1
|
||||||
https://github.com/celery/django-celery-beat/zipball/master#egg=django-celery-beat
|
#https://github.com/celery/django-celery-beat/zipball/master#egg=django-celery-beat
|
||||||
#django_celery_beat==1.1.0
|
django_celery_beat==1.1.1
|
||||||
ephem==3.7.6.0
|
ephem==3.7.6.0
|
||||||
python-gssapi==0.6.4
|
python-gssapi==0.6.4
|
||||||
jms-es-sdk
|
jms-es-sdk
|
||||||
|
|
Loading…
Reference in New Issue