Unverified Commit 674731f7 authored by BaiJiangJie's avatar BaiJiangJie Committed by GitHub

Merge pull request #3294 from jumpserver/dev

Dev
parents 8387c8db 1a82b858
...@@ -21,5 +21,6 @@ VOLUME /opt/jumpserver/logs ...@@ -21,5 +21,6 @@ VOLUME /opt/jumpserver/logs
ENV LANG=zh_CN.UTF-8 ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8 ENV LC_ALL=zh_CN.UTF-8
EXPOSE 8070
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT ["./entrypoint.sh"] ENTRYPOINT ["./entrypoint.sh"]
{% load i18n %} {% load i18n %}
<style> <style>
.btn-group>.btn+.dropdown-toggle { .btn-group>.btn+.dropdown-toggle {
padding-right: 4px; padding-right: 4px;
padding-left: 4px; padding-left: 4px;
} }
table.dataTable tbody tr.selected a {
color: rgb(103, 106, 108);;
}
</style> </style>
<table class="table table-striped table-bordered table-hover" id="asset_user_list_table" style="width: 100%"> <table class="table table-striped table-bordered table-hover" id="asset_user_list_table" style="width: 100%">
<thead> <thead>
...@@ -137,8 +142,7 @@ $(document).ready(function(){ ...@@ -137,8 +142,7 @@ $(document).ready(function(){
} }
var success = function (data) { var success = function (data) {
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
requestApi({ requestApi({
url: the_url, url: the_url,
...@@ -149,4 +153,4 @@ $(document).ready(function(){ ...@@ -149,4 +153,4 @@ $(document).ready(function(){
}) })
</script> </script>
\ No newline at end of file
...@@ -85,8 +85,7 @@ $(document).ready(function () { ...@@ -85,8 +85,7 @@ $(document).ready(function () {
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}"; var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
var success = function (data) { var success = function (data) {
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
requestApi({ requestApi({
url: the_url, url: the_url,
......
...@@ -140,8 +140,7 @@ function replaceNodeAssetsAdminUser(nodes) { ...@@ -140,8 +140,7 @@ function replaceNodeAssetsAdminUser(nodes) {
jumpserver.nodes_selected = {}; jumpserver.nodes_selected = {};
$(document).ready(function () { $(document).ready(function () {
var url = "{% url 'api-assets:node-list' %}"; nodesSelect2Init(".nodes-select2")
nodesSelect2Init(".nodes-select2", url)
.on('select2:select', function(evt) { .on('select2:select', function(evt) {
var data = evt.params.data; var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text; jumpserver.nodes_selected[data.id] = data.text;
......
...@@ -81,8 +81,7 @@ $(document).ready(function () { ...@@ -81,8 +81,7 @@ $(document).ready(function () {
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}"; var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}";
var success = function (data) { var success = function (data) {
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
requestApi({ requestApi({
url: the_url, url: the_url,
...@@ -92,4 +91,4 @@ $(document).ready(function () { ...@@ -92,4 +91,4 @@ $(document).ready(function () {
}); });
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -110,8 +110,7 @@ $(document).ready(function () { ...@@ -110,8 +110,7 @@ $(document).ready(function () {
$('.select2').select2({ $('.select2').select2({
allowClear: true allowClear: true
}); });
var url = "{% url 'api-assets:node-list' %}"; nodesSelect2Init(".nodes-select2");
nodesSelect2Init(".nodes-select2", url);
$(".labels").select2({ $(".labels").select2({
allowClear: true, allowClear: true,
templateSelection: format templateSelection: format
......
...@@ -276,8 +276,7 @@ function refreshAssetHardware() { ...@@ -276,8 +276,7 @@ function refreshAssetHardware() {
var success = function(data) { var success = function(data) {
console.log(data); console.log(data);
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600')
}; };
requestApi({ requestApi({
url: the_url, url: the_url,
...@@ -288,8 +287,7 @@ function refreshAssetHardware() { ...@@ -288,8 +287,7 @@ function refreshAssetHardware() {
$(document).ready(function () { $(document).ready(function () {
var url = "{% url 'api-assets:node-list' %}"; nodesSelect2Init(".nodes-select2")
nodesSelect2Init(".nodes-select2", url)
.on('select2:select', function(evt) { .on('select2:select', function(evt) {
var data = evt.params.data; var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text; jumpserver.nodes_selected[data.id] = data.text;
...@@ -355,8 +353,7 @@ $(document).ready(function () { ...@@ -355,8 +353,7 @@ $(document).ready(function () {
var success = function(data) { var success = function(data) {
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600')
}; };
requestApi({ requestApi({
......
...@@ -523,8 +523,7 @@ $(document).ready(function(){ ...@@ -523,8 +523,7 @@ $(document).ready(function(){
function success(data) { function success(data) {
rMenu.css({"visibility" : "hidden"}); rMenu.css({"visibility" : "hidden"});
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600')
} }
requestApi({ requestApi({
url: the_url, url: the_url,
...@@ -538,8 +537,7 @@ $(document).ready(function(){ ...@@ -538,8 +537,7 @@ $(document).ready(function(){
function success(data) { function success(data) {
rMenu.css({"visibility" : "hidden"}); rMenu.css({"visibility" : "hidden"});
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600')
} }
requestApi({ requestApi({
url: the_url, url: the_url,
...@@ -552,4 +550,4 @@ $(document).ready(function(){ ...@@ -552,4 +550,4 @@ $(document).ready(function(){
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -153,8 +153,7 @@ jumpserver.nodes_selected = {}; ...@@ -153,8 +153,7 @@ jumpserver.nodes_selected = {};
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2() $('.select2').select2()
var url = "{% url 'api-assets:node-list' %}"; nodesSelect2Init(".nodes-select2")
nodesSelect2Init(".nodes-select2", url)
.on('select2:select', function(evt) { .on('select2:select', function(evt) {
var data = evt.params.data; var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text; jumpserver.nodes_selected[data.id] = data.text;
...@@ -202,8 +201,7 @@ $(document).ready(function () { ...@@ -202,8 +201,7 @@ $(document).ready(function () {
}; };
var success = function (data) { var success = function (data) {
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
requestApi({ requestApi({
url: the_url, url: the_url,
...@@ -219,8 +217,7 @@ $(document).ready(function () { ...@@ -219,8 +217,7 @@ $(document).ready(function () {
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id); the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
var success = function (data) { var success = function (data) {
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
var error = function (data) { var error = function (data) {
alert(data) alert(data)
...@@ -239,8 +236,7 @@ $(document).ready(function () { ...@@ -239,8 +236,7 @@ $(document).ready(function () {
}; };
var success = function (data) { var success = function (data) {
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
requestApi({ requestApi({
url: the_url, url: the_url,
......
...@@ -251,8 +251,7 @@ $(document).ready(function () { ...@@ -251,8 +251,7 @@ $(document).ready(function () {
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}"; var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
var success = function (data) { var success = function (data) {
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
requestApi({ requestApi({
url: the_url, url: the_url,
...@@ -265,8 +264,7 @@ $(document).ready(function () { ...@@ -265,8 +264,7 @@ $(document).ready(function () {
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}"; var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
var success = function (data) { var success = function (data) {
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600')
}; };
requestApi({ requestApi({
url: the_url, url: the_url,
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
from functools import reduce
from treelib import Tree from treelib import Tree
from treelib.exceptions import NodeIDAbsentError
from collections import defaultdict from collections import defaultdict
from copy import deepcopy from copy import deepcopy
import threading
from django.db.models import Q
from common.utils import get_object_or_none, get_logger, timeit from common.utils import get_object_or_none, get_logger, timeit
from .models import SystemUser, Label, Asset from .models import SystemUser, Asset
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -31,6 +29,7 @@ class TreeService(Tree): ...@@ -31,6 +29,7 @@ class TreeService(Tree):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.nodes_assets_map = defaultdict(set) self.nodes_assets_map = defaultdict(set)
self.all_nodes_assets_map = {} self.all_nodes_assets_map = {}
self._invalid_assets = frozenset()
@classmethod @classmethod
@timeit @timeit
...@@ -47,25 +46,34 @@ class TreeService(Tree): ...@@ -47,25 +46,34 @@ class TreeService(Tree):
key = node["key"] key = node["key"]
value = node["value"] value = node["value"]
parent_key = ":".join(key.split(":")[:-1]) parent_key = ":".join(key.split(":")[:-1])
tree.create_node( tree.safe_create_node(
tag=value, identifier=key, tag=value, identifier=key,
parent=parent_key, parent=parent_key,
) )
tree.init_assets() tree.init_assets()
return tree return tree
@timeit
def init_assets(self): def init_assets(self):
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
self.all_nodes_assets_map = {} self.all_nodes_assets_map = {}
self.nodes_assets_map = defaultdict(set) self.nodes_assets_map = defaultdict(set)
logger.debug('Init tree assets')
with tmp_to_root_org(): with tmp_to_root_org():
queryset = Asset.objects.all().values_list('id', 'nodes__key') queryset = Asset.objects.all().values_list('id', 'nodes__key')
invalid_assets = Asset.objects.filter(is_active=False)\
.values_list('id', flat=True)
self._invalid_assets = frozenset(invalid_assets)
for asset_id, key in queryset: for asset_id, key in queryset:
if not key: if not key:
continue continue
self.nodes_assets_map[key].add(asset_id) self.nodes_assets_map[key].add(asset_id)
def safe_create_node(self, **kwargs):
parent = kwargs.get("parent")
if not self.contains(parent):
kwargs['parent'] = self.root
self.create_node(**kwargs)
def all_children_ids(self, nid, with_self=True): def all_children_ids(self, nid, with_self=True):
children_ids = self.expand_tree(nid) children_ids = self.expand_tree(nid)
if not with_self: if not with_self:
...@@ -93,6 +101,11 @@ class TreeService(Tree): ...@@ -93,6 +101,11 @@ class TreeService(Tree):
children = self.all_children(nid, with_self=False) children = self.all_children(nid, with_self=False)
return ancestors + [self[nid]] + children return ancestors + [self[nid]] + children
@staticmethod
def is_parent(child, parent):
parent_id = child.bpointer
return parent_id == parent.identifier
def root_node(self): def root_node(self):
return self.get_node(self.root) return self.get_node(self.root)
...@@ -108,12 +121,15 @@ class TreeService(Tree): ...@@ -108,12 +121,15 @@ class TreeService(Tree):
parent = self.copy_node(parent) parent = self.copy_node(parent)
return parent return parent
def set_assets(self, nid, assets):
self.nodes_assets_map[nid] = set(assets)
def assets(self, nid): def assets(self, nid):
assets = self.nodes_assets_map[nid] assets = self.nodes_assets_map[nid]
return assets return assets
def set_assets(self, nid, assets): def valid_assets(self, nid):
self.nodes_assets_map[nid] = assets return set(self.assets(nid)) - set(self._invalid_assets)
def all_assets(self, nid): def all_assets(self, nid):
assets = self.all_nodes_assets_map.get(nid) assets = self.all_nodes_assets_map.get(nid)
...@@ -123,41 +139,55 @@ class TreeService(Tree): ...@@ -123,41 +139,55 @@ class TreeService(Tree):
children = self.children(nid) children = self.children(nid)
for child in children: for child in children:
assets.update(self.all_assets(child.identifier)) assets.update(self.all_assets(child.identifier))
self.all_nodes_assets_map[nid] = assets
return assets return assets
def all_valid_assets(self, nid):
return set(self.all_assets(nid)) - set(self._invalid_assets)
def assets_amount(self, nid): def assets_amount(self, nid):
return len(self.all_assets(nid)) return len(self.all_assets(nid))
def valid_assets_amount(self, nid):
return len(self.all_valid_assets(nid))
@staticmethod @staticmethod
def copy_node(node): def copy_node(node):
new_node = deepcopy(node) new_node = deepcopy(node)
new_node.fpointer = None new_node.fpointer = None
return new_node return new_node
def safe_add_ancestors(self, ancestors): def safe_add_ancestors(self, node, ancestors):
# 如果祖先节点为1个,那么添加该节点, 父节点是root node # 如果没有祖先节点,那么添加该节点, 父节点是root node
if len(ancestors) == 1: if len(ancestors) == 0:
node = ancestors[0]
parent = self.root_node() parent = self.root_node()
else: else:
node, ancestors = ancestors[0], ancestors[1:] parent = ancestors[0]
parent_id = ancestors[0].identifier
# 如果父节点不存在, 则先添加父节点
if not self.contains(parent_id):
self.safe_add_ancestors(ancestors)
parent = self.get_node(parent_id)
# 如果当前节点已再树中,则移动当前节点到父节点中 # 如果当前节点已再树中,则移动当前节点到父节点中
# 这个是由于 当前节点放到了二级节点中 # 这个是由于 当前节点放到了二级节点中
if not self.contains(parent.identifier):
# logger.debug('Add parent: {}'.format(parent.identifier))
self.safe_add_ancestors(parent, ancestors[1:])
if self.contains(node.identifier): if self.contains(node.identifier):
# msg = 'Move node to parent: {} => {}'.format(
# node.identifier, parent.identifier
# )
# logger.debug(msg)
self.move_node(node.identifier, parent.identifier) self.move_node(node.identifier, parent.identifier)
else: else:
# logger.debug('Add node: {}'.format(node.identifier))
self.add_node(node, parent) self.add_node(node, parent)
# #
# def __getstate__(self): # def __getstate__(self):
# self.mutex = None # self.mutex = None
# return self.__dict__ # return self.__dict__
# #
# def __setstate__(self, state):
# self.__dict__ = state def __setstate__(self, state):
# self.mutex = threading.Lock() self.__dict__ = state
if '_invalid_assets' not in state:
self._invalid_assets = frozenset()
# self.mutex = threading.Lock()
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
</style> </style>
<div class="alert alert-info help-message" style="margin-left: 0px; margin-right: 0px;"> <div class="alert alert-info help-message" style="margin-left: 0px; margin-right: 0px;">
{% trans 'Using api key sign api header, every requests header difference'%}, <a href="https://tools.ietf.org/html/draft-cavage-http-signatures-08">{% trans 'docs' %} </a> {% trans 'Using api key sign api header, every requests header difference'%}
<a href="https://jumpserver.readthedocs.io/zh/master/api_auth.html#access-key" target="_blank">{% trans 'docs' %} </a>
</div> </div>
<div class="uc pull-left m-r-0 m-t-10"> <div class="uc pull-left m-r-0 m-t-10">
<button class="btn btn-primary btn-sm" id="create-btn" href="#"> {% trans "Create" %} </button> <button class="btn btn-primary btn-sm" id="create-btn" href="#"> {% trans "Create" %} </button>
......
...@@ -83,6 +83,8 @@ class LogTailApi(generics.RetrieveAPIView): ...@@ -83,6 +83,8 @@ class LogTailApi(generics.RetrieveAPIView):
return Response({"data": data, 'end': end, 'mark': new_mark}) return Response({"data": data, 'end': end, 'mark': new_mark})
class ResourcesIDCacheApi(APIView): class ResourcesIDCacheApi(APIView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
spm = str(uuid.uuid4()) spm = str(uuid.uuid4())
......
...@@ -194,7 +194,7 @@ def timeit(func): ...@@ -194,7 +194,7 @@ def timeit(func):
now = time.time() now = time.time()
result = func(*args, **kwargs) result = func(*args, **kwargs)
using = (time.time() - now) * 1000 using = (time.time() - now) * 1000
msg = "Call {} end, using: {:.1f}ms".format(func.__name__, using) msg = "End call {}, using: {:.1f}ms".format(func.__name__, using)
logger.debug(msg) logger.debug(msg)
return result return result
return wrapper return wrapper
......
import os
import django
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
django.setup()
application = get_default_application()
...@@ -335,6 +335,7 @@ defaults = { ...@@ -335,6 +335,7 @@ defaults = {
'REDIS_DB_CELERY': 3, 'REDIS_DB_CELERY': 3,
'REDIS_DB_CACHE': 4, 'REDIS_DB_CACHE': 4,
'REDIS_DB_SESSION': 5, 'REDIS_DB_SESSION': 5,
'REDIS_DB_WS': 6,
'CAPTCHA_TEST_MODE': None, 'CAPTCHA_TEST_MODE': None,
'TOKEN_EXPIRATION': 3600 * 24, 'TOKEN_EXPIRATION': 3600 * 24,
'DISPLAY_PER_PAGE': 25, 'DISPLAY_PER_PAGE': 25,
...@@ -375,6 +376,7 @@ defaults = { ...@@ -375,6 +376,7 @@ defaults = {
'RADIUS_SECRET': '', 'RADIUS_SECRET': '',
'HTTP_BIND_HOST': '0.0.0.0', 'HTTP_BIND_HOST': '0.0.0.0',
'HTTP_LISTEN_PORT': 8080, 'HTTP_LISTEN_PORT': 8080,
'WS_LISTEN_PORT': 8070,
'LOGIN_LOG_KEEP_DAYS': 90, 'LOGIN_LOG_KEEP_DAYS': 90,
'ASSETS_PERM_CACHE_TIME': 3600*24, 'ASSETS_PERM_CACHE_TIME': 3600*24,
'SECURITY_MFA_VERIFY_TTL': 3600, 'SECURITY_MFA_VERIFY_TTL': 3600,
...@@ -383,7 +385,9 @@ defaults = { ...@@ -383,7 +385,9 @@ defaults = {
'SYSLOG_FACILITY': 'user', 'SYSLOG_FACILITY': 'user',
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False, 'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False,
'WINDOWS_SSH_DEFAULT_SHELL': 'cmd', 'WINDOWS_SSH_DEFAULT_SHELL': 'cmd',
'FLOWER_URL': "127.0.0.1:5555" 'FLOWER_URL': "127.0.0.1:5555",
'AUTH_LDAP_SEARCH_PAGED_SIZE': 1000,
'DEFAULT_ORG_SHOW_ALL_USERS': True,
} }
......
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from ops.urls.ws_urls import urlpatterns as ops_urlpatterns
urlpatterns = []
urlpatterns += ops_urlpatterns
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(
URLRouter(urlpatterns)
),
})
...@@ -74,6 +74,7 @@ INSTALLED_APPS = [ ...@@ -74,6 +74,7 @@ INSTALLED_APPS = [
'rest_framework', 'rest_framework',
'rest_framework_swagger', 'rest_framework_swagger',
'drf_yasg', 'drf_yasg',
'channels',
'django_filters', 'django_filters',
'bootstrap3', 'bootstrap3',
'captcha', 'captcha',
...@@ -140,7 +141,8 @@ TEMPLATES = [ ...@@ -140,7 +141,8 @@ TEMPLATES = [
}, },
] ]
# WSGI_APPLICATION = 'jumpserver.wsgi.applications' WSGI_APPLICATION = 'jumpserver.wsgi.application'
ASGI_APPLICATION = 'jumpserver.routing.application'
LOGIN_REDIRECT_URL = reverse_lazy('index') LOGIN_REDIRECT_URL = reverse_lazy('index')
LOGIN_URL = reverse_lazy('authentication:login') LOGIN_URL = reverse_lazy('authentication:login')
...@@ -422,6 +424,7 @@ OTP_VALID_WINDOW = CONFIG.OTP_VALID_WINDOW ...@@ -422,6 +424,7 @@ OTP_VALID_WINDOW = CONFIG.OTP_VALID_WINDOW
# Auth LDAP settings # Auth LDAP settings
AUTH_LDAP = False AUTH_LDAP = False
AUTH_LDAP_SEARCH_PAGED_SIZE = CONFIG.AUTH_LDAP_SEARCH_PAGED_SIZE
AUTH_LDAP_SERVER_URI = 'ldap://localhost:389' AUTH_LDAP_SERVER_URI = 'ldap://localhost:389'
AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org' AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org'
AUTH_LDAP_BIND_PASSWORD = '' AUTH_LDAP_BIND_PASSWORD = ''
...@@ -621,6 +624,24 @@ ASSETS_PERM_CACHE_TIME = CONFIG.ASSETS_PERM_CACHE_TIME ...@@ -621,6 +624,24 @@ ASSETS_PERM_CACHE_TIME = CONFIG.ASSETS_PERM_CACHE_TIME
# Asset user auth external backend, default AuthBook backend # Asset user auth external backend, default AuthBook backend
BACKEND_ASSET_USER_AUTH_VAULT = False BACKEND_ASSET_USER_AUTH_VAULT = False
DEFAULT_ORG_SHOW_ALL_USERS = CONFIG.DEFAULT_ORG_SHOW_ALL_USERS
PERM_SINGLE_ASSET_TO_UNGROUP_NODE = CONFIG.PERM_SINGLE_ASSET_TO_UNGROUP_NODE PERM_SINGLE_ASSET_TO_UNGROUP_NODE = CONFIG.PERM_SINGLE_ASSET_TO_UNGROUP_NODE
WINDOWS_SSH_DEFAULT_SHELL = CONFIG.WINDOWS_SSH_DEFAULT_SHELL WINDOWS_SSH_DEFAULT_SHELL = CONFIG.WINDOWS_SSH_DEFAULT_SHELL
FLOWER_URL = CONFIG.FLOWER_URL FLOWER_URL = CONFIG.FLOWER_URL
# Django channels support websocket
CHANNEL_REDIS = "redis://:{}@{}:{}/{}".format(
CONFIG.REDIS_PASSWORD, CONFIG.REDIS_HOST, CONFIG.REDIS_PORT,
CONFIG.REDIS_DB_WS,
)
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [CHANNEL_REDIS],
},
},
}
...@@ -66,6 +66,7 @@ urlpatterns = [ ...@@ -66,6 +66,7 @@ urlpatterns = [
re_path('api/(?P<app>\w+)/(?P<version>v\d)/.*', views.redirect_format_api), re_path('api/(?P<app>\w+)/(?P<version>v\d)/.*', views.redirect_format_api),
path('api/health/', views.HealthCheckView.as_view(), name="health"), path('api/health/', views.HealthCheckView.as_view(), name="health"),
path('luna/', views.LunaView.as_view(), name='luna-view'), path('luna/', views.LunaView.as_view(), name='luna-view'),
re_path('ws/.*', views.WsView.as_view(), name='ws-view'),
path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'), path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'),
path('settings/', include('settings.urls.view_urls', namespace='settings')), path('settings/', include('settings.urls.view_urls', namespace='settings')),
......
...@@ -226,4 +226,11 @@ class HealthCheckView(APIView): ...@@ -226,4 +226,11 @@ class HealthCheckView(APIView):
return JsonResponse({"status": 1, "time": int(time.time())}) return JsonResponse({"status": 1, "time": int(time.time())})
class WsView(APIView):
ws_port = settings.CONFIG.HTTP_LISTEN_PORT + 1
def get(self, request):
msg = _("Websocket server run on port: {}, you should proxy it on nginx"
.format(self.ws_port))
return JsonResponse({"msg": msg})
...@@ -6135,7 +6135,7 @@ msgstr "管理员" ...@@ -6135,7 +6135,7 @@ msgstr "管理员"
#: xpack/plugins/orgs/forms.py:42 #: xpack/plugins/orgs/forms.py:42
msgid "Select auditor" msgid "Select auditor"
msgstr "选择审计员" msgstr "选择审计员"
#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26 #: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26
#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:60 #: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:60
......
...@@ -196,8 +196,7 @@ $(document).ready(function () { ...@@ -196,8 +196,7 @@ $(document).ready(function () {
alert("没有运行历史"); alert("没有运行历史");
return return
} }
var url = '{% url 'ops:celery-task-log' pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', history_pk); showCeleryTaskLog(history_pk);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}) })
</script> </script>
{% endblock %} {% endblock %}
......
...@@ -145,8 +145,8 @@ ...@@ -145,8 +145,8 @@
<script> <script>
$(document).ready(function () { $(document).ready(function () {
}).on('click', '.celery-task-log', function () { }).on('click', '.celery-task-log', function () {
var url = '{% url 'ops:celery-task-log' pk=object.pk %}'; var taskId = "{{ object.pk }}";
window.open(url, '', 'width=800,height=600,left=400,top=400') showCeleryTaskLog(taskId);
}) })
</script> </script>
......
...@@ -20,50 +20,13 @@ ...@@ -20,50 +20,13 @@
</div> </div>
<script> <script>
var rowHeight = 18; var scheme = document.location.protocol === "https:" ? "wss" : "ws";
var colWidth = 10; var port = document.location.port ? ":" + document.location.port : "";
var mark = ''; var url = "/ws/ops/tasks/" + "{{ task_id }}" + "/log/";
var url = "{% url 'api-ops:celery-task-log' pk=task_id %}"; var wsURL = scheme + "://" + document.location.hostname + port + url;
var term; var term;
var end = false; var ws;
var error = false;
var interval = 200;
var success = true;
function calWinSize() {
var t = $('#marker');
{#rowHeight = 1.00 * t.height();#}
{#colWidth = 1.00 * t.width() / 6;#}
}
function resize() {
{#var rows = Math.floor(window.innerHeight / rowHeight) - 1;#}
{#var cols = Math.floor(window.innerWidth / colWidth) - 2;#}
{#term.resize(cols, rows);#}
}
function requestAndWrite() {
if (!end && success) {
success = false;
$.ajax({
url: url + '?mark=' + mark,
method: "GET",
contentType: "application/json; charset=utf-8"
}).done(function(data, textStatue, jqXHR) {
success = true;
if (jqXHR.status === 203) {
error = true;
term.write('.');
interval = 500;
}
if (jqXHR.status === 200){
term.write(data.data);
mark = data.mark;
if (data.end){
end = true
}
}
})
}
}
$(document).ready(function () { $(document).ready(function () {
term = new Terminal({ term = new Terminal({
cursorBlink: false, cursorBlink: false,
...@@ -74,18 +37,14 @@ ...@@ -74,18 +37,14 @@
disableStdin: true disableStdin: true
}); });
term.open(document.getElementById('term')); term.open(document.getElementById('term'));
term.resize(90, 32); term.resize(120, 30);
resize(); ws = new WebSocket(wsURL);
term.on('data', function (data) { ws.onmessage = function(e) {
{#term.write(data.replace('\r', '\r\n'))#} var data = JSON.parse(e.data);
term.write(data); term.write(data.message);
});
window.onresize = function () {
resize()
}; };
{#$('.terminal').detach().appendTo('#term');#} ws.onerror = function (e) {
setInterval(function () { term.write("Connect websocket server error")
requestAndWrite() }
}, interval)
}); });
</script> </script>
...@@ -130,8 +130,7 @@ $(document).ready(function () { ...@@ -130,8 +130,7 @@ $(document).ready(function () {
alert("没有运行历史"); alert("没有运行历史");
return return
} }
var url = '{% url 'ops:celery-task-log' pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', history_pk); showCeleryTaskLog(history_pk);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}) })
</script> </script>
{% endblock %} {% endblock %}
...@@ -174,8 +174,7 @@ $(document).ready(function () { ...@@ -174,8 +174,7 @@ $(document).ready(function () {
alert("没有运行历史"); alert("没有运行历史");
return return
} }
var url = '{% url 'ops:celery-task-log' pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', history_pk); showCeleryTaskLog(history_pk);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}) })
</script> </script>
......
...@@ -155,8 +155,7 @@ $(document).ready(function () { ...@@ -155,8 +155,7 @@ $(document).ready(function () {
alert("没有运行历史"); alert("没有运行历史");
return return
} }
var url = '{% url 'ops:celery-task-log' pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', history_pk); showCeleryTaskLog(history_pk);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}) })
</script> </script>
......
...@@ -98,8 +98,7 @@ $(document).ready(function () { ...@@ -98,8 +98,7 @@ $(document).ready(function () {
}; };
var success = function(data) { var success = function(data) {
var task_id = data.task; var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); showCeleryTaskLog(task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
}; };
requestApi({ requestApi({
url: the_url, url: the_url,
......
from django.urls import path
from .. import ws
app_name = 'ops'
urlpatterns = [
path('ws/ops/tasks/<uuid:task_id>/log/', ws.CeleryLogWebsocket, name='task-log-ws'),
]
import time
import threading
from .celery.utils import get_celery_task_log_path
from channels.generic.websocket import JsonWebsocketConsumer
class CeleryLogWebsocket(JsonWebsocketConsumer):
task = ''
task_log_f = None
disconnected = False
def connect(self):
task_id = self.scope['url_route']['kwargs']['task_id']
log_path = get_celery_task_log_path(task_id)
try:
self.task_log_f = open(log_path)
except OSError:
self.send({'message': "Task {} log not found".format(task_id)})
self.disconnect(None)
return
self.accept()
self.send_log_to_client()
def disconnect(self, close_code):
self.disconnected = True
if self.task_log_f and not self.task_log_f.closed:
self.task_log_f.close()
self.close()
def send_log_to_client(self):
def func():
while not self.disconnected:
data = self.task_log_f.read(4096)
if data:
data = data.replace('\n', '\r\n')
self.send_json({'message': data})
time.sleep(0.2)
thread = threading.Thread(target=func)
thread.start()
import uuid import uuid
from django.conf import settings
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 _
...@@ -75,7 +76,10 @@ class Organization(models.Model): ...@@ -75,7 +76,10 @@ class Organization(models.Model):
from users.models import User from users.models import User
if self.is_real(): if self.is_real():
return self.users.all() return self.users.all()
return User.objects.filter(role=User.ROLE_USER) users = User.objects.filter(role=User.ROLE_USER)
if self.is_default() and not settings.DEFAULT_ORG_SHOW_ALL_USERS:
users = users.filter(related_user_orgs__isnull=True)
return users
def get_org_admins(self): def get_org_admins(self):
from users.models import User from users.models import User
...@@ -130,7 +134,7 @@ class Organization(models.Model): ...@@ -130,7 +134,7 @@ class Organization(models.Model):
return admin_orgs return admin_orgs
@classmethod @classmethod
def get_user_user_orgs(self, user): def get_user_user_orgs(cls, user):
user_orgs = [] user_orgs = []
if user.is_anonymous: if user.is_anonymous:
return user_orgs return user_orgs
......
...@@ -11,8 +11,17 @@ def get_org_from_request(request): ...@@ -11,8 +11,17 @@ def get_org_from_request(request):
oid = request.session.get("oid") oid = request.session.get("oid")
if not oid: if not oid:
oid = request.META.get("HTTP_X_JMS_ORG") oid = request.META.get("HTTP_X_JMS_ORG")
request_params_oid = request.GET.get("oid")
if request_params_oid:
oid = request.GET.get("oid")
if not oid: if not oid:
oid = Organization.DEFAULT_ID oid = Organization.DEFAULT_ID
if oid.lower() == "default":
oid = Organization.DEFAULT_ID
elif oid.lower() == "root":
oid = Organization.ROOT_ID
org = Organization.get_instance(oid) org = Organization.get_instance(oid)
return org return org
......
...@@ -33,7 +33,7 @@ class UserNodeTreeMixin: ...@@ -33,7 +33,7 @@ class UserNodeTreeMixin:
_queryset = [] _queryset = []
for node in nodes: for node in nodes:
assets_amount = self.tree.assets_amount(node.key) assets_amount = self.tree.valid_assets_amount(node.key)
if assets_amount == 0 and node.key != Node.empty_key: if assets_amount == 0 and node.key != Node.empty_key:
continue continue
node.assets_amount = assets_amount node.assets_amount = assets_amount
......
...@@ -38,20 +38,21 @@ class AssetPermissionForm(OrgModelForm): ...@@ -38,20 +38,21 @@ class AssetPermissionForm(OrgModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
users_field = self.fields.get('users')
users_field.queryset = current_org.get_org_members(exclude=('Auditor',))
if self.data: if self.data:
return return
# 前端渲染优化, 防止过多资产 # 前端渲染优化, 防止过多资产
users_field = self.fields.get('users')
assets_field = self.fields['assets'] assets_field = self.fields['assets']
nodes_field = self.fields['nodes'] nodes_field = self.fields['nodes']
if self.instance: if self.instance:
assets_field.queryset = self.instance.assets.all() assets_field.queryset = self.instance.assets.all()
nodes_field.queryset = self.instance.nodes.all() nodes_field.queryset = self.instance.nodes.all()
users_field.queryset = self.instance.users.all()
else: else:
assets_field.queryset = Asset.objects.none() assets_field.queryset = Asset.objects.none()
nodes_field.queryset = Node.objects.none() nodes_field.queryset = Node.objects.none()
users_field.queryset = []
def set_nodes_initial(self, nodes): def set_nodes_initial(self, nodes):
field = self.fields['nodes'] field = self.fields['nodes']
...@@ -70,7 +71,7 @@ class AssetPermissionForm(OrgModelForm): ...@@ -70,7 +71,7 @@ class AssetPermissionForm(OrgModelForm):
) )
widgets = { widgets = {
'users': forms.SelectMultiple( 'users': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _("User")} attrs={'class': 'users-select2', 'data-placeholder': _("User")}
), ),
'user_groups': forms.SelectMultiple( 'user_groups': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _("User group")} attrs={'class': 'select2', 'data-placeholder': _("User group")}
......
...@@ -17,9 +17,12 @@ __all__ = [ ...@@ -17,9 +17,12 @@ __all__ = [
class RemoteAppPermissionCreateUpdateForm(OrgModelForm): class RemoteAppPermissionCreateUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
users_field = self.fields.get('users') users_field = self.fields.get('users')
if hasattr(users_field, 'queryset'): if self.instance:
users_field.queryset = current_org.get_org_members(exclude=('Auditor',)) users_field.queryset = self.instance.users.all()
else:
users_field.queryset = []
class Meta: class Meta:
model = RemoteAppPermission model = RemoteAppPermission
...@@ -28,7 +31,7 @@ class RemoteAppPermissionCreateUpdateForm(OrgModelForm): ...@@ -28,7 +31,7 @@ class RemoteAppPermissionCreateUpdateForm(OrgModelForm):
) )
widgets = { widgets = {
'users': forms.SelectMultiple( 'users': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('User')} attrs={'class': 'users-select2', 'data-placeholder': _('User')}
), ),
'user_groups': forms.SelectMultiple( 'user_groups': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('User group')} attrs={'class': 'select2', 'data-placeholder': _('User group')}
......
...@@ -204,8 +204,7 @@ $(document).ready(function () { ...@@ -204,8 +204,7 @@ $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();
table = initAssetTable(); table = initAssetTable();
var nodeListUrl = "{% url 'api-assets:node-list' %}"; nodesSelect2Init(".nodes-select2");
nodesSelect2Init(".nodes-select2", nodeListUrl);
$("#id_assets").parent().find(".select2-selection").on('click', function (e) { $("#id_assets").parent().find(".select2-selection").on('click', function (e) {
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){ if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
......
...@@ -116,8 +116,8 @@ $(document).ready(function () { ...@@ -116,8 +116,8 @@ $(document).ready(function () {
$('.select2').select2({ $('.select2').select2({
closeOnSelect: false closeOnSelect: false
}); });
var url = "{% url 'api-assets:node-list' %}"; nodesSelect2Init(".nodes-select2");
nodesSelect2Init(".nodes-select2", url); usersSelect2Init(".users-select2");
$('#date_start').daterangepicker(dateOptions); $('#date_start').daterangepicker(dateOptions);
$('#date_expired').daterangepicker(dateOptions); $('#date_expired').daterangepicker(dateOptions);
...@@ -154,4 +154,4 @@ $(document).ready(function () { ...@@ -154,4 +154,4 @@ $(document).ready(function () {
formSubmit(props); formSubmit(props);
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -114,6 +114,7 @@ $(document).ready(function () { ...@@ -114,6 +114,7 @@ $(document).ready(function () {
$('.select2').select2({ $('.select2').select2({
closeOnSelect: false closeOnSelect: false
}); });
usersSelect2Init('.users-select2');
$('#date_start').daterangepicker(dateOptions); $('#date_start').daterangepicker(dateOptions);
$('#date_expired').daterangepicker(dateOptions); $('#date_expired').daterangepicker(dateOptions);
}) })
...@@ -141,4 +142,4 @@ $(document).ready(function () { ...@@ -141,4 +142,4 @@ $(document).ready(function () {
formSubmit(props); formSubmit(props);
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
# coding: utf-8 # coding: utf-8
import time
import pickle import pickle
from collections import defaultdict from collections import defaultdict
from functools import reduce from functools import reduce
from hashlib import md5
import json
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Q from django.db.models import Q
...@@ -94,6 +91,7 @@ class AssetPermissionUtilCacheMixin: ...@@ -94,6 +91,7 @@ class AssetPermissionUtilCacheMixin:
user_tree = pickle.loads(data) user_tree = pickle.loads(data)
return user_tree return user_tree
@timeit
def get_user_tree_from_cache_if_need(self): def get_user_tree_from_cache_if_need(self):
if not self.user_tree_cache_enable: if not self.user_tree_cache_enable:
return None return None
...@@ -278,9 +276,9 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): ...@@ -278,9 +276,9 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
) )
if not ancestors: if not ancestors:
continue continue
parent_id = ancestors[0].identifier user_tree.safe_add_ancestors(child, ancestors)
user_tree.safe_add_ancestors(ancestors) # parent_id = ancestors[0].identifier
user_tree.move_node(child.identifier, parent_id) # user_tree.move_node(child.identifier, parent_id)
@staticmethod @staticmethod
def add_empty_node_if_need(user_tree): def add_empty_node_if_need(user_tree):
...@@ -315,6 +313,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): ...@@ -315,6 +313,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self.set_user_tree_to_local(user_tree) self.set_user_tree_to_local(user_tree)
return user_tree return user_tree
user_tree = TreeService() user_tree = TreeService()
user_tree._invalid_assets = self.full_tree._invalid_assets
full_tree_root = self.full_tree.root_node() full_tree_root = self.full_tree.root_node()
user_tree.create_node( user_tree.create_node(
tag=full_tree_root.tag, tag=full_tree_root.tag,
......
...@@ -26,6 +26,8 @@ class LDAPUtil: ...@@ -26,6 +26,8 @@ class LDAPUtil:
password=None, use_ssl=None, search_ougroup=None, password=None, use_ssl=None, search_ougroup=None,
search_filter=None, attr_map=None, auth_ldap=None): search_filter=None, attr_map=None, auth_ldap=None):
# config # config
self.paged_size = settings.AUTH_LDAP_SEARCH_PAGED_SIZE
if use_settings_config: if use_settings_config:
self._load_config_from_settings() self._load_config_from_settings()
else: else:
...@@ -79,23 +81,45 @@ class LDAPUtil: ...@@ -79,23 +81,45 @@ class LDAPUtil:
user_item[attr] = value user_item[attr] = value
return user_item return user_item
def _search_user_items_ou(self, search_ou, cookie=None):
ok = self.connection.search(
search_ou, self.search_filter % ({"user": "*"}),
attributes=list(self.attr_map.values()),
paged_size=self.paged_size, paged_cookie=cookie
)
if not ok:
error = _("Search no entry matched in ou {}".format(search_ou))
raise LDAPOUGroupException(error)
user_items = []
for entry in self.connection.entries:
user_item = self._ldap_entry_to_user_item(entry)
user = self.get_user_by_username(user_item['username'])
user_item['existing'] = bool(user)
if user_item in user_items:
continue
user_items.append(user_item)
return user_items
def _cookie(self):
if self.paged_size is None:
cookie = None
else:
cookie = self.connection.result['controls']['1.2.840.113556.1.4.319']['value']['cookie']
return cookie
def search_user_items(self): def search_user_items(self):
user_items = [] user_items = []
logger.info("Search user items")
for search_ou in str(self.search_ougroup).split("|"): for search_ou in str(self.search_ougroup).split("|"):
ok = self.connection.search( logger.info("Search user search ou: {}".format(search_ou))
search_ou, self.search_filter % ({"user": "*"}), _user_items = self._search_user_items_ou(search_ou)
attributes=list(self.attr_map.values()) user_items.extend(_user_items)
) while self._cookie():
if not ok: logger.info("Page Search user search ou: {}".format(search_ou))
error = _("Search no entry matched in ou {}".format(search_ou)) _user_items = self._search_user_items_ou(search_ou, self._cookie())
raise LDAPOUGroupException(error) user_items.extend(_user_items)
for entry in self.connection.entries: logger.info("Search user items end")
user_item = self._ldap_entry_to_user_item(entry)
user = self.get_user_by_username(user_item['username'])
user_item['existing'] = bool(user)
if user_item in user_items:
continue
user_items.append(user_item)
return user_items return user_items
def search_filter_user_items(self, username_list): def search_filter_user_items(self, username_list):
......
...@@ -88,7 +88,7 @@ table.dataTable tbody td.selected a, ...@@ -88,7 +88,7 @@ table.dataTable tbody td.selected a,
table.dataTable tbody tr.selected td i.text-navy, table.dataTable tbody tr.selected td i.text-navy,
table.dataTable tbody th.selected td i.text-navy, table.dataTable tbody th.selected td i.text-navy,
table.dataTable tbody td.selected td i.text-navy { table.dataTable tbody td.selected td i.text-navy {
color: white !important; color: white;
} }
.m-0 { .m-0 {
...@@ -473,4 +473,4 @@ span.select2-selection__placeholder { ...@@ -473,4 +473,4 @@ span.select2-selection__placeholder {
.p-r-5 { .p-r-5 {
padding-right: 5px; padding-right: 5px;
} }
\ No newline at end of file
...@@ -1177,6 +1177,9 @@ function readFile(ref) { ...@@ -1177,6 +1177,9 @@ function readFile(ref) {
} }
function nodesSelect2Init(selector, url) { function nodesSelect2Init(selector, url) {
if (!url) {
url = '/api/v1/assets/nodes/'
}
return $(selector).select2({ return $(selector).select2({
closeOnSelect: false, closeOnSelect: false,
ajax: { ajax: {
...@@ -1201,3 +1204,37 @@ function nodesSelect2Init(selector, url) { ...@@ -1201,3 +1204,37 @@ function nodesSelect2Init(selector, url) {
}) })
} }
function usersSelect2Init(selector, url) {
if (!url) {
url = '/api/v1/users/users/'
}
return $(selector).select2({
closeOnSelect: false,
ajax: {
url: url,
data: function (params) {
var page = params.page || 1;
var query = {
search: params.term,
offset: (page - 1) * 10,
limit: 10
};
return query
},
processResults: function (data) {
var results = $.map(data.results, function (v, i) {
var display = v.name + '(' + v.username +')';
return {id: v.id, text: display}
});
var more = !!data.next;
return {results: results, pagination: {"more": more}}
}
},
})
}
function showCeleryTaskLog(taskId) {
var url = '/ops/celery/task/taskId/log/'.replace('taskId', taskId);
window.open(url, '', 'width=900,height=600')
}
...@@ -12,7 +12,7 @@ from rest_framework_bulk import BulkModelViewSet ...@@ -12,7 +12,7 @@ from rest_framework_bulk import BulkModelViewSet
from common.permissions import ( from common.permissions import (
IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser, IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser,
CanUpdateDeleteUser, CanUpdateDeleteUser, IsSuperUser
) )
from common.mixins import CommonApiMixin from common.mixins import CommonApiMixin
from common.utils import get_logger from common.utils import get_logger
...@@ -52,12 +52,15 @@ class UserViewSet(CommonApiMixin, BulkModelViewSet): ...@@ -52,12 +52,15 @@ class UserViewSet(CommonApiMixin, BulkModelViewSet):
self.send_created_signal(users) self.send_created_signal(users)
def get_queryset(self): def get_queryset(self):
queryset = current_org.get_org_members().prefetch_related('groups') queryset = current_org.get_org_members()\
.prefetch_related('groups')
return queryset return queryset
def get_permissions(self): def get_permissions(self):
if self.action in ["retrieve", "list"]: if self.action in ["retrieve", "list"]:
self.permission_classes = (IsOrgAdminOrAppUser,) self.permission_classes = (IsOrgAdminOrAppUser,)
if self.request.query_params.get('all'):
self.permission_classes = (IsSuperUser,)
return super().get_permissions() return super().get_permissions()
def perform_bulk_destroy(self, objects): def perform_bulk_destroy(self, objects):
...@@ -176,4 +179,4 @@ class UserResetOTPApi(generics.RetrieveAPIView): ...@@ -176,4 +179,4 @@ class UserResetOTPApi(generics.RetrieveAPIView):
user.otp_secret_key = '' user.otp_secret_key = ''
user.save() user.save()
logout(request) logout(request)
return Response({"msg": "success"}) return Response({"msg": "success"})
\ No newline at end of file
...@@ -308,11 +308,11 @@ class UserBulkUpdateForm(OrgModelForm): ...@@ -308,11 +308,11 @@ class UserBulkUpdateForm(OrgModelForm):
class UserGroupForm(OrgModelForm): class UserGroupForm(OrgModelForm):
users = forms.ModelMultipleChoiceField( users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(), queryset=User.objects.none(),
label=_("User"), label=_("User"),
widget=forms.SelectMultiple( widget=forms.SelectMultiple(
attrs={ attrs={
'class': 'select2', 'class': 'users-select2',
'data-placeholder': _('Select users') 'data-placeholder': _('Select users')
} }
), ),
...@@ -329,8 +329,10 @@ class UserGroupForm(OrgModelForm): ...@@ -329,8 +329,10 @@ class UserGroupForm(OrgModelForm):
if 'initial' not in kwargs: if 'initial' not in kwargs:
return return
users_field = self.fields.get('users') users_field = self.fields.get('users')
if hasattr(users_field, 'queryset'): if instance:
users_field.queryset = current_org.get_org_members(exclude=('Auditor',)) users_field.queryset = instance.users.all()
else:
users_field.queryset = User.objects.none()
def save(self, commit=True): def save(self, commit=True):
group = super().save(commit=commit) group = super().save(commit=commit)
......
...@@ -103,6 +103,7 @@ function onSelected(event, treeNode) { ...@@ -103,6 +103,7 @@ function onSelected(event, treeNode) {
function initTree(refresh) { function initTree(refresh) {
var asyncUrl = setUrlParam(treeUrl, 'cache_policy', '1');
var setting = { var setting = {
view: { view: {
dblClickExpand: false, dblClickExpand: false,
...@@ -115,7 +116,7 @@ function initTree(refresh) { ...@@ -115,7 +116,7 @@ function initTree(refresh) {
}, },
async: { async: {
enable: true, enable: true,
url: treeUrl, url: asyncUrl,
autoParam: ["id=key", "name=n", "level=lv"], autoParam: ["id=key", "name=n", "level=lv"],
type: 'get' type: 'get'
}, },
......
...@@ -46,6 +46,7 @@ $(document).ready(function () { ...@@ -46,6 +46,7 @@ $(document).ready(function () {
$('.select2').select2({ $('.select2').select2({
closeOnSelect: false closeOnSelect: false
}); });
usersSelect2Init('.users-select2')
}) })
.on("submit", "form", function (evt) { .on("submit", "form", function (evt) {
evt.preventDefault(); evt.preventDefault();
......
...@@ -44,6 +44,7 @@ DB_NAME: jumpserver ...@@ -44,6 +44,7 @@ DB_NAME: jumpserver
# 运行时绑定端口 # 运行时绑定端口
HTTP_BIND_HOST: 0.0.0.0 HTTP_BIND_HOST: 0.0.0.0
HTTP_LISTEN_PORT: 8080 HTTP_LISTEN_PORT: 8080
WS_LISTEN_PORT: 8070
# Use Redis as broker for celery and web socket # Use Redis as broker for celery and web socket
# Redis配置 # Redis配置
......
...@@ -47,6 +47,7 @@ LOG_DIR = os.path.join(BASE_DIR, 'logs') ...@@ -47,6 +47,7 @@ LOG_DIR = os.path.join(BASE_DIR, 'logs')
TMP_DIR = os.path.join(BASE_DIR, 'tmp') TMP_DIR = os.path.join(BASE_DIR, 'tmp')
HTTP_HOST = CONFIG.HTTP_BIND_HOST or '127.0.0.1' HTTP_HOST = CONFIG.HTTP_BIND_HOST or '127.0.0.1'
HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080 HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080
WS_PORT = CONFIG.WS_LISTEN_PORT or 8082
DEBUG = CONFIG.DEBUG or False DEBUG = CONFIG.DEBUG or False
LOG_LEVEL = CONFIG.LOG_LEVEL or 'INFO' LOG_LEVEL = CONFIG.LOG_LEVEL or 'INFO'
...@@ -201,12 +202,15 @@ def is_running(s, unlink=True): ...@@ -201,12 +202,15 @@ def is_running(s, unlink=True):
def parse_service(s): def parse_service(s):
all_services = [ all_services = [
'gunicorn', 'celery_ansible', 'celery_default', 'beat', 'flower' 'gunicorn', 'celery_ansible', 'celery_default',
'beat', 'flower', 'daphne',
] ]
if s == 'all': if s == 'all':
return all_services return all_services
elif s == "web": elif s == "web":
return ['gunicorn', 'flower'] return ['gunicorn', 'flower', 'daphne']
elif s == 'ws':
return ['daphne']
elif s == "task": elif s == "task":
return ["celery_ansible", "celery_default", "beat"] return ["celery_ansible", "celery_default", "beat"]
elif s == 'gunicorn': elif s == 'gunicorn':
...@@ -225,10 +229,8 @@ def parse_service(s): ...@@ -225,10 +229,8 @@ def parse_service(s):
def get_start_gunicorn_kwargs(): def get_start_gunicorn_kwargs():
print("\n- Start Gunicorn WSGI HTTP Server") print("\n- Start Gunicorn WSGI HTTP Server")
prepare() prepare()
service = 'gunicorn'
bind = '{}:{}'.format(HTTP_HOST, HTTP_PORT) bind = '{}:{}'.format(HTTP_HOST, HTTP_PORT)
log_format = '%(h)s %(t)s "%(r)s" %(s)s %(b)s ' log_format = '%(h)s %(t)s "%(r)s" %(s)s %(b)s '
pid_file = get_pid_file_path(service)
cmd = [ cmd = [
'gunicorn', 'jumpserver.wsgi', 'gunicorn', 'jumpserver.wsgi',
...@@ -238,7 +240,6 @@ def get_start_gunicorn_kwargs(): ...@@ -238,7 +240,6 @@ def get_start_gunicorn_kwargs():
'-w', str(WORKERS), '-w', str(WORKERS),
'--max-requests', '4096', '--max-requests', '4096',
'--access-logformat', log_format, '--access-logformat', log_format,
'-p', pid_file,
'--access-logfile', '-' '--access-logfile', '-'
] ]
...@@ -247,6 +248,16 @@ def get_start_gunicorn_kwargs(): ...@@ -247,6 +248,16 @@ def get_start_gunicorn_kwargs():
return {'cmd': cmd, 'cwd': APPS_DIR} return {'cmd': cmd, 'cwd': APPS_DIR}
def get_start_daphne_kwargs():
print("\n- Start Daphne ASGI WS Server")
cmd = [
'daphne', 'jumpserver.asgi:application',
'-b', HTTP_HOST,
'-p', str(WS_PORT),
]
return {'cmd': cmd, 'cwd': APPS_DIR}
def get_start_celery_ansible_kwargs(): def get_start_celery_ansible_kwargs():
print("\n- Start Celery as Distributed Task Queue: Ansible") print("\n- Start Celery as Distributed Task Queue: Ansible")
return get_start_worker_kwargs('ansible', 4) return get_start_worker_kwargs('ansible', 4)
...@@ -362,6 +373,7 @@ def start_service(s): ...@@ -362,6 +373,7 @@ def start_service(s):
"celery_default": get_start_celery_default_kwargs, "celery_default": get_start_celery_default_kwargs,
"beat": get_start_beat_kwargs, "beat": get_start_beat_kwargs,
"flower": get_start_flower_kwargs, "flower": get_start_flower_kwargs,
"daphne": get_start_daphne_kwargs,
} }
kwargs = services_kwargs.get(s)() kwargs = services_kwargs.get(s)()
......
...@@ -59,7 +59,7 @@ pytz==2018.3 ...@@ -59,7 +59,7 @@ pytz==2018.3
PyYAML==5.1 PyYAML==5.1
redis==2.10.6 redis==2.10.6
requests==2.22.0 requests==2.22.0
jms-storage==0.0.23 jms-storage==0.0.25
s3transfer==0.1.13 s3transfer==0.1.13
simplejson==3.13.2 simplejson==3.13.2
six==1.11.0 six==1.11.0
...@@ -86,3 +86,6 @@ httpsig==1.3.0 ...@@ -86,3 +86,6 @@ httpsig==1.3.0
treelib==1.5.3 treelib==1.5.3
django-proxy==1.2.1 django-proxy==1.2.1
flower==0.9.3 flower==0.9.3
channels-redis==2.4.0
channels==2.3.0
daphne==2.3.0
libtiff-devel libjpeg-devel libzip-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel sshpass openldap-devel mariadb-devel mysql-devel libffi-devel openssh-clients telnet openldap-clients gcc krb5-devel libtiff-devel libjpeg-devel libzip-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel sshpass openldap-devel mariadb-devel mysql-devel libffi-devel openssh-clients telnet openldap-clients
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment