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 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 ..models import Node
|
||||
from .. import serializers
|
||||
|
@ -29,6 +29,7 @@ logger = get_logger(__file__)
|
|||
__all__ = [
|
||||
'NodeViewSet', 'NodeChildrenApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
|
||||
'NodeAddChildrenApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -75,6 +76,24 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|||
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):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
|
|
|
@ -15,7 +15,7 @@ class AssetCreateForm(forms.ModelForm):
|
|||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
||||
'nodes', 'is_active', 'admin_user', 'labels',
|
||||
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||
|
||||
]
|
||||
widgets = {
|
||||
|
@ -44,7 +44,7 @@ class AssetUpdateForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'port', 'nodes', 'is_active',
|
||||
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
|
||||
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
||||
]
|
||||
widgets = {
|
||||
|
|
|
@ -38,6 +38,14 @@ def default_node():
|
|||
|
||||
class Asset(models.Model):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
('Unix', 'Unix'),
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
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_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_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'))
|
||||
|
@ -87,6 +95,12 @@ class Asset(models.Model):
|
|||
return True, ''
|
||||
return False, warning
|
||||
|
||||
def is_unixlike(self):
|
||||
if self.platform not in ("Windows", "Other"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
if self.cpu_count:
|
||||
|
@ -99,6 +113,8 @@ class Asset(models.Model):
|
|||
|
||||
@property
|
||||
def is_connective(self):
|
||||
if not self.is_unixlike():
|
||||
return True
|
||||
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
|
||||
if val == 1:
|
||||
return True
|
||||
|
|
|
@ -61,6 +61,9 @@ class Node(models.Model):
|
|||
assets = Asset.objects.filter(nodes__id=self.id)
|
||||
return assets
|
||||
|
||||
def get_active_assets(self):
|
||||
return self.get_assets().filter(is_active=True)
|
||||
|
||||
def get_all_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_root():
|
||||
|
@ -70,6 +73,9 @@ class Node(models.Model):
|
|||
assets = Asset.objects.filter(nodes__in=nodes)
|
||||
return assets
|
||||
|
||||
def get_all_active_assets(self):
|
||||
return self.get_all_assets().filter(is_active=True)
|
||||
|
||||
def is_root(self):
|
||||
return self.key == '0'
|
||||
|
||||
|
@ -88,6 +94,10 @@ class Node(models.Model):
|
|||
else:
|
||||
return parent
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
self.key = parent.get_next_child_key()
|
||||
|
||||
@property
|
||||
def ancestor(self):
|
||||
if self.parent == self.__class__.root():
|
||||
|
|
|
@ -26,14 +26,14 @@ signer = get_signer()
|
|||
class AssetUser(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
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'))
|
||||
_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'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=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
|
||||
def password(self):
|
||||
|
@ -175,15 +175,12 @@ class AdminUser(AssetUser):
|
|||
return info
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = []
|
||||
for cluster in self.cluster_set.all():
|
||||
assets.extend(cluster.assets.all())
|
||||
assets.extend(self.asset_set.all())
|
||||
return list(set(assets))
|
||||
assets = self.asset_set.all()
|
||||
return assets
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
return len(self.get_related_assets())
|
||||
return self.get_related_assets().count()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
|
|
@ -65,4 +65,8 @@ class NodeAssetsSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = ['assets']
|
||||
fields = ['assets']
|
||||
|
||||
|
||||
class NodeAddChildrenSerializer(serializers.Serializer):
|
||||
nodes = serializers.ListField()
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{% trans 'Create system user' %}</h5>
|
||||
<h5>{{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
|
@ -81,6 +81,14 @@
|
|||
{% block custom_foot_js %}
|
||||
<script>
|
||||
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() {
|
||||
if ($(auto_generate_key).prop('checked')) {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
|
@ -88,9 +96,23 @@
|
|||
$('.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 () {
|
||||
$('.select2').select2();
|
||||
authFieldsDisplay();
|
||||
protocolChange();
|
||||
$(auto_generate_key).change(function () {
|
||||
authFieldsDisplay();
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{% trans 'Create admin user' %}</h5>
|
||||
<h5>{{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<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>
|
||||
|
@ -224,6 +230,9 @@ function editTreeNode() {
|
|||
if (!current_node){
|
||||
return
|
||||
}
|
||||
if (current_node.value) {
|
||||
current_node.name = current_node.value;
|
||||
}
|
||||
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() {
|
||||
var setting = {
|
||||
view: {
|
||||
|
@ -319,11 +364,24 @@ function initTree() {
|
|||
enable: true
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
enable: true,
|
||||
showRemoveBtn: false,
|
||||
showRenameBtn: false,
|
||||
drag: {
|
||||
isCopy: true,
|
||||
isMove: true
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
onRightClick: OnRightClick,
|
||||
beforeClick: beforeClick,
|
||||
onRename: onRename,
|
||||
onSelected: onSelected
|
||||
onSelected: onSelected,
|
||||
beforeDrag: beforeDrag,
|
||||
onDrag: onDrag,
|
||||
beforeDrop: beforeDrop,
|
||||
onDrop: onDrop
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -334,7 +392,8 @@ function initTree() {
|
|||
{#if (value["key"] === "0") {#}
|
||||
value["open"] = true;
|
||||
{# }#}
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')'
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||
value['value'] = value['value'];
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
|
@ -415,7 +474,7 @@ $(document).ready(function(){
|
|||
current_node = nodes[0];
|
||||
url += "?node_id=" + current_node.id;
|
||||
}
|
||||
window.open(url);
|
||||
window.open(url, '_self');
|
||||
})
|
||||
.on('click', '.btn_asset_delete', function () {
|
||||
var $this = $(this);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
|
|
|
@ -44,6 +44,7 @@ urlpatterns = [
|
|||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/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/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/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||
]
|
||||
|
|
|
@ -58,8 +58,7 @@ class UserAssetListView(LoginRequiredMixin, TemplateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset list'),
|
||||
'action': _('My assets'),
|
||||
'system_users': SystemUser.objects.all(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
|
@ -248,6 +247,7 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
|||
f = form.cleaned_data['file']
|
||||
det_result = chardet.detect(f.read())
|
||||
f.seek(0) # reset file seek index
|
||||
|
||||
file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
|
||||
csv_file = StringIO(file_data)
|
||||
reader = csv.reader(csv_file)
|
||||
|
|
|
@ -68,10 +68,10 @@ class BaseForm(forms.Form):
|
|||
class BasicSettingForm(BaseForm):
|
||||
SITE_URL = forms.URLField(
|
||||
label=_("Current SITE URL"),
|
||||
help_text="http://jumpserver.abc.com:8080"
|
||||
help_text="eg: http://jumpserver.abc.com:8080"
|
||||
)
|
||||
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")
|
||||
)
|
||||
EMAIL_SUBJECT_PREFIX = forms.CharField(
|
||||
|
@ -135,7 +135,7 @@ class LDAPSettingForm(BaseForm):
|
|||
AUTH_LDAP_START_TLS = forms.BooleanField(
|
||||
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):
|
||||
|
|
|
@ -99,9 +99,8 @@ class DatetimeSearchMixin:
|
|||
|
||||
if date_from_s:
|
||||
date_from = timezone.datetime.strptime(date_from_s, self.date_format)
|
||||
self.date_from = date_from.replace(
|
||||
tzinfo=timezone.get_current_timezone()
|
||||
)
|
||||
tz = timezone.get_current_timezone()
|
||||
self.date_from = tz.localize(date_from)
|
||||
else:
|
||||
self.date_from = timezone.now() - timezone.timedelta(7)
|
||||
|
||||
|
|
|
@ -73,17 +73,20 @@ def to_html(s):
|
|||
|
||||
@register.filter
|
||||
def time_util_with_seconds(date_from, date_to):
|
||||
if date_from and date_to:
|
||||
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:
|
||||
if not date_from:
|
||||
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
|
||||
|
|
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
|
||||
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE
|
||||
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE or 25
|
||||
DEFAULT_EXPIRED_YEARS = 70
|
||||
USER_GUIDE_URL = ""
|
||||
|
|
|
@ -4,16 +4,16 @@ from __future__ import unicode_literals
|
|||
from django.conf.urls import url, include
|
||||
from django.conf import settings
|
||||
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_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])
|
||||
urlpatterns = [
|
||||
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'^assets/', include('assets.urls.views_urls', namespace='assets')),
|
||||
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.db.models import Count
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
|
@ -45,7 +46,8 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||
return self.session_week.values('user').distinct().count()
|
||||
|
||||
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):
|
||||
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)
|
||||
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
|
||||
|
||||
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)
|
||||
return queryset
|
||||
|
||||
|
@ -118,9 +122,16 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
|
|||
user = get_object_or_404(User, id=user_id)
|
||||
|
||||
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 asset in v['assets']:
|
||||
asset.system_users_granted = v['system_users']
|
||||
asset.system_users_granted = assets[asset]
|
||||
node.assets_granted = v['assets']
|
||||
queryset.append(node)
|
||||
return queryset
|
||||
|
|
|
@ -12,7 +12,7 @@ class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = NodePermission
|
||||
fields = [
|
||||
'node', 'user_group', 'system_user',
|
||||
'id', 'node', 'user_group', 'system_user',
|
||||
'is_active', 'date_expired'
|
||||
]
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{% trans 'Create asset permission ' %}</h5>
|
||||
<h5>{{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
|
|
|
@ -215,16 +215,6 @@ $(document).ready(function(){
|
|||
initTable();
|
||||
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 () {
|
||||
var $this = $(this);
|
||||
var uid = $this.data('uid');
|
||||
|
@ -241,7 +231,7 @@ $(document).ready(function(){
|
|||
current_node = nodes[0];
|
||||
url += "?node_id=" + current_node.id;
|
||||
}
|
||||
window.open(url);
|
||||
window.open(url, '_self');
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
@ -56,7 +56,7 @@ class NodePermissionUtil:
|
|||
nodes_with_assets = dict()
|
||||
for node, system_users in nodes.items():
|
||||
nodes_with_assets[node] = {
|
||||
'assets': node.get_assets(),
|
||||
'assets': node.get_active_assets(),
|
||||
'system_users': system_users
|
||||
}
|
||||
return nodes_with_assets
|
||||
|
@ -87,7 +87,7 @@ class NodePermissionUtil:
|
|||
nodes_with_assets = dict()
|
||||
for node, system_users in nodes.items():
|
||||
nodes_with_assets[node] = {
|
||||
'assets': node.get_assets(),
|
||||
'assets': node.get_active_assets(),
|
||||
'system_users': system_users
|
||||
}
|
||||
return nodes_with_assets
|
||||
|
|
|
@ -427,3 +427,9 @@ div.dataTables_wrapper div.dataTables_filter {
|
|||
text-align: center;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.profile-dropdown li a {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3299,7 +3299,7 @@ body.tour-open .animated {
|
|||
border-bottom: 1px solid #e7eaec;
|
||||
}
|
||||
body {
|
||||
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-family: "open sans", "Helvetica Neue", "微软雅黑", Helvetica, Arial, sans-serif;
|
||||
background-color: #2f4050;
|
||||
font-size: 13px;
|
||||
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>#}
|
||||
{# </li>#}
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle count-info" data-toggle="dropdown" href="#">
|
||||
<span class="m-r-sm text-muted welcome-message">{% trans 'Help' %}</span>
|
||||
<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 '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>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
|
@ -28,9 +33,8 @@
|
|||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu animated fadeInRight m-t-xs">
|
||||
<li><a href="{% url 'users:user-profile' %}"><i class="fa fa-cogs"> </i><span> {% trans 'Profile' %}</span></a></li>
|
||||
<li class="divider"></li>
|
||||
<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>
|
||||
{% if request.user.is_superuser %}
|
||||
{% 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>
|
||||
|
@ -57,11 +61,11 @@
|
|||
<li>
|
||||
<a href="">{% trans 'Dashboard' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
{% if app %}
|
||||
<li>
|
||||
<a>{{ app }}</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if action %}
|
||||
<li class="active">
|
||||
<strong>{{ action }}</strong>
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
{% load i18n %}
|
||||
<li id="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>
|
||||
</li>
|
||||
<li id="users">
|
||||
<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>
|
||||
<ul class="nav nav-second-level active">
|
||||
<li id="user"><a href="{% url 'users:user-list' %}">{% trans 'User list' %}</a></li>
|
||||
|
@ -16,7 +17,7 @@
|
|||
</li>
|
||||
<li id="assets">
|
||||
<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>
|
||||
<ul class="nav nav-second-level">
|
||||
<li id="asset"><a href="{% url 'assets:asset-list' %}">{% trans 'Asset list' %}</a></li>
|
||||
|
@ -26,7 +27,7 @@
|
|||
</ul>
|
||||
</li>
|
||||
<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">
|
||||
<li id="asset-permission">
|
||||
<a href="{% url 'perms:asset-permission-list' %}">{% trans 'Asset permission' %}</a>
|
||||
|
@ -35,18 +36,23 @@
|
|||
</li>
|
||||
<li id="terminal">
|
||||
<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>
|
||||
<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-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>
|
||||
<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>
|
||||
</ul>
|
||||
</li>
|
||||
<li id="ops">
|
||||
<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>
|
||||
<ul class="nav nav-second-level">
|
||||
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
{% load i18n %}
|
||||
<li id="assets">
|
||||
<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>
|
||||
</li>
|
||||
<li id="users">
|
||||
<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>
|
||||
</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>
|
||||
</a>
|
||||
</li>
|
|
@ -3,7 +3,7 @@
|
|||
<li class="nav-header">
|
||||
<div class="dropdown profile-element">
|
||||
<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 class="clearfix"></div>
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
Copyright Jumpserver.org
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<small>2014-2018</small>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</div>
|
||||
<div class="ibox-content">
|
||||
<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>
|
||||
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
<div class="ibox-content">
|
||||
<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>
|
||||
|
@ -36,7 +36,7 @@
|
|||
</div>
|
||||
<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>
|
||||
<small>Online user</small>
|
||||
<small>Online users</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,7 +57,7 @@
|
|||
<div class="row">
|
||||
<div class="col-sm-2 border-bottom white-bg dashboard-header" style="margin-left:15px;height: 346px">
|
||||
<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">
|
||||
{% for data in user_visit_count_top_five %}
|
||||
<li class="list-group-item fist-item">
|
||||
|
|
|
@ -25,18 +25,22 @@ def get_all_replay_storage():
|
|||
|
||||
|
||||
class TerminalForm(forms.ModelForm):
|
||||
command_storage = forms.ChoiceField(choices=get_all_command_storage(),
|
||||
label=_("Command storage"))
|
||||
replay_storage = forms.ChoiceField(choices=get_all_replay_storage(),
|
||||
label=_("Replay storage"))
|
||||
command_storage = forms.ChoiceField(
|
||||
choices=get_all_command_storage(),
|
||||
label=_("Command storage")
|
||||
)
|
||||
replay_storage = forms.ChoiceField(
|
||||
choices=get_all_replay_storage(),
|
||||
label=_("Replay storage")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
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 = {
|
||||
'ssh_port': _("Coco ssh 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.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
from users.models import User
|
||||
|
@ -127,6 +128,7 @@ class Session(models.Model):
|
|||
has_replay = models.BooleanField(default=False, verbose_name=_("Replay"))
|
||||
has_command = models.BooleanField(default=False, verbose_name=_("Command"))
|
||||
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_end = models.DateTimeField(verbose_name=_("Date end"), null=True)
|
||||
|
||||
|
|
|
@ -1,20 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import datetime
|
||||
|
||||
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
|
||||
RUNNING = False
|
||||
|
||||
|
||||
# Todo: 定期清理上报history
|
||||
@shared_task
|
||||
def clean_terminal_history():
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
@register_as_period_task(interval=3600)
|
||||
@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 'Command' %}</th>
|
||||
<th class="text-center">{% trans 'Date start' %}</th>
|
||||
{# <th class="text-center">{% trans 'Date last active' %}</th>#}
|
||||
<th class="text-center">{% trans 'Duration' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
{% endblock %}
|
||||
|
@ -94,6 +95,7 @@
|
|||
<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_last_active }}</td>#}
|
||||
<td class="text-center">{{ session.date_start|time_util_with_seconds:session.date_end }}</td>
|
||||
<td>
|
||||
{% if session.is_finished %}
|
||||
|
@ -107,6 +109,21 @@
|
|||
{% endfor %}
|
||||
{% 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 %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.conf import settings
|
|||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
|
||||
from ..models import Command
|
||||
from .. import utils
|
||||
from ..backends import get_multi_command_store
|
||||
|
@ -15,7 +15,7 @@ __all__ = ['CommandListView']
|
|||
common_storage = get_multi_command_store()
|
||||
|
||||
|
||||
class CommandListView(DatetimeSearchMixin, ListView):
|
||||
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
|
||||
model = Command
|
||||
template_name = "terminal/command_list.html"
|
||||
context_object_name = 'command_list'
|
||||
|
|
|
@ -97,7 +97,7 @@ class SessionOfflineListView(SessionListView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class SessionDetailView(SingleObjectMixin, ListView):
|
||||
class SessionDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView):
|
||||
template_name = 'terminal/session_detail.html'
|
||||
model = Session
|
||||
object = None
|
||||
|
|
|
@ -145,7 +145,8 @@ class UserAuthApi(APIView):
|
|||
|
||||
if not login_ip:
|
||||
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]
|
||||
else:
|
||||
login_ip = request.META.get("REMOTE_ADDR")
|
||||
|
|
|
@ -6,3 +6,6 @@ from django.apps import AppConfig
|
|||
class UsersConfig(AppConfig):
|
||||
name = 'users'
|
||||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
super().ready()
|
||||
|
|
|
@ -16,7 +16,8 @@ class AccessKey(models.Model):
|
|||
default=uuid.uuid4, editable=False)
|
||||
secret = models.UUIDField(verbose_name='AccessKeySecret',
|
||||
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):
|
||||
return str(self.id)
|
||||
|
|
|
@ -22,6 +22,7 @@ class UserGroup(NoDeleteModelMixin):
|
|||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
verbose_name = _("User group")
|
||||
|
||||
@classmethod
|
||||
def initial(cls):
|
||||
|
|
|
@ -151,6 +151,10 @@ class User(AbstractUser):
|
|||
def save(self, *args, **kwargs):
|
||||
if not self.name:
|
||||
self.name = self.username
|
||||
if self.username == 'admin':
|
||||
self.role = 'Admin'
|
||||
self.is_active = True
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
|
@ -247,6 +251,7 @@ class User(AbstractUser):
|
|||
|
||||
class Meta:
|
||||
ordering = ['username']
|
||||
verbose_name = _("User")
|
||||
|
||||
#: Use this method initial user
|
||||
@classmethod
|
||||
|
|
|
@ -1,21 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
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__)
|
||||
from django.dispatch import Signal
|
||||
|
||||
|
||||
@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)
|
||||
post_user_create = Signal(providing_args=('user',))
|
||||
|
||||
|
|
|
@ -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>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
Copyright Jumpserver.org
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<small>© 2014-2018</small>
|
||||
<div class="col-md-12">
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -22,24 +22,27 @@
|
|||
<div class="loginColumns animated fadeInDown">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2 class="font-bold">欢迎使用Jumpserver开源跳板机</h2>
|
||||
<h2 class="font-bold">欢迎使用Jumpserver开源堡垒机</h2>
|
||||
<p>
|
||||
Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互联网企业高效 用户、资产、权限、审计 管理
|
||||
全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
|
||||
</p>
|
||||
<p>
|
||||
我们自五湖四海,我们对开源精神无比敬仰和崇拜,我们对完美、整洁、优雅 无止境的追求
|
||||
使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
|
||||
</p>
|
||||
<p>
|
||||
专注自动化运维,努力打造 易用、稳定、安全、自动化 的跳板机, 这是我们的不懈的追求和动力
|
||||
采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。
|
||||
</p>
|
||||
<p>
|
||||
<small>永远年轻,永远热泪盈眶 stay foolish stay hungry</small>
|
||||
改变世界,从一点点开始。
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<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="">
|
||||
{% csrf_token %}
|
||||
{% if form.errors %}
|
||||
|
@ -60,12 +63,16 @@
|
|||
</div>
|
||||
<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' %}">
|
||||
<small>{% trans 'Forgot password' %}?</small>
|
||||
</a>
|
||||
|
||||
<p class="text-muted text-center">
|
||||
</p>
|
||||
</form>
|
||||
<p class="m-t">
|
||||
</p>
|
||||
|
@ -74,11 +81,8 @@
|
|||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
Copyright 北京堆栈科技有限公司
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<small>© 2014-2018</small>
|
||||
<div class="col-md-12">
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -70,11 +70,8 @@
|
|||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
Copyright Jumpserver.org
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<small>© 2014-2018</small>
|
||||
<div class="col-md-12">
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -92,8 +92,8 @@ class UserGroupGrantedAssetView(AdminUserRequiredMixin, DetailView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'User',
|
||||
'action': 'User group granted asset',
|
||||
'app': _('Users'),
|
||||
'action': _('User group granted asset'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
from django import forms
|
||||
from django.shortcuts import render
|
||||
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."))
|
||||
auth_login(self.request, form.get_user())
|
||||
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||
|
||||
if x_forwarded_for and x_forwarded_for[0]:
|
||||
login_ip = x_forwarded_for[0]
|
||||
else:
|
||||
|
@ -75,6 +77,13 @@ class UserLoginView(FormView):
|
|||
self.redirect_field_name,
|
||||
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')
|
||||
class UserLogoutView(TemplateView):
|
||||
|
@ -237,7 +246,7 @@ class LoginLogListView(DatetimeSearchMixin, ListView):
|
|||
if self.user:
|
||||
queryset = queryset.filter(username=self.user)
|
||||
if self.keyword:
|
||||
queryset = self.queryset.filter(
|
||||
queryset = queryset.filter(
|
||||
Q(ip__contains=self.keyword) |
|
||||
Q(city__contains=self.keyword) |
|
||||
Q(username__contains=self.keyword)
|
||||
|
|
|
@ -6,6 +6,7 @@ import json
|
|||
import uuid
|
||||
import csv
|
||||
import codecs
|
||||
import chardet
|
||||
from io import StringIO
|
||||
|
||||
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.views import View
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.db import transaction
|
||||
from django.views.generic.edit import (
|
||||
CreateView, UpdateView, FormMixin, FormView
|
||||
)
|
||||
|
@ -33,7 +35,7 @@ from common.utils import get_logger, get_object_or_none, is_uuid
|
|||
from .. import forms
|
||||
from ..models import User, UserGroup
|
||||
from ..utils import AdminUserRequiredMixin
|
||||
from ..signals import on_user_created
|
||||
from ..signals import post_user_create
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -212,8 +214,10 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
|||
|
||||
# todo: need be patch, method to long
|
||||
def form_valid(self, form):
|
||||
file = form.cleaned_data['file']
|
||||
data = file.read().decode('utf-8').strip(codecs.BOM_UTF8.decode('utf-8'))
|
||||
f = form.cleaned_data['file']
|
||||
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)
|
||||
reader = csv.reader(csv_file)
|
||||
csv_data = [row for row in reader]
|
||||
|
@ -252,15 +256,15 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
|||
else:
|
||||
continue
|
||||
user_dict[k] = v
|
||||
|
||||
user = get_object_or_none(User, id=id_) if is_uuid(id_) else None
|
||||
user = get_object_or_none(User, id=id_) if id_ and is_uuid(id_) else None
|
||||
if not user:
|
||||
try:
|
||||
groups = user_dict.pop('groups')
|
||||
user = User.objects.create(**user_dict)
|
||||
user.groups.set(groups)
|
||||
created.append(user_dict['username'])
|
||||
on_user_created.send(self.__class__, user=user)
|
||||
with transaction.atomic():
|
||||
groups = user_dict.pop('groups')
|
||||
user = User.objects.create(**user_dict)
|
||||
user.groups.set(groups)
|
||||
created.append(user_dict['username'])
|
||||
post_user_create.send(self.__class__, user=user)
|
||||
except Exception as e:
|
||||
failed.append('%s: %s' % (user_dict['username'], str(e)))
|
||||
else:
|
||||
|
@ -309,7 +313,6 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Users'),
|
||||
'action': _('Profile'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
|
@ -4,7 +4,7 @@
|
|||
contain the root `toctree` directive.
|
||||
|
||||
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::
|
||||
:maxdepth: 1
|
||||
|
|
|
@ -56,8 +56,8 @@ uritemplate==3.0.0
|
|||
urllib3==1.22
|
||||
vine==1.1.4
|
||||
gunicorn==19.7.1
|
||||
https://github.com/celery/django-celery-beat/zipball/master#egg=django-celery-beat
|
||||
#django_celery_beat==1.1.0
|
||||
#https://github.com/celery/django-celery-beat/zipball/master#egg=django-celery-beat
|
||||
django_celery_beat==1.1.1
|
||||
ephem==3.7.6.0
|
||||
python-gssapi==0.6.4
|
||||
jms-es-sdk
|
||||
|
|
Loading…
Reference in New Issue