mirror of https://github.com/jumpserver/jumpserver
[Update] 优化命令记录列表
parent
1b44172bc5
commit
b9f82fd0ac
|
@ -60,8 +60,8 @@ function initAssetUserTable() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
targets: 6, createdCell: function (td, cellData) {
|
targets: 6, createdCell: function (td, cellData) {
|
||||||
var date = new Date(cellData);
|
var data = formatDateAsCN(cellData);
|
||||||
$(td).html(date.toLocaleString());
|
$(td).html(data);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,27 +15,27 @@
|
||||||
|
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
<div class="" style="float: right">
|
<div class="" style="float: right">
|
||||||
<div class=" btn-group">
|
<div class=" btn-group">
|
||||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li>
|
<li>
|
||||||
<a class=" btn_export" tabindex="0">
|
<a class=" btn_export" tabindex="0">
|
||||||
<span>{% trans "Export" %}</span>
|
<span>{% trans "Export" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||||
<span>{% trans "Import" %}</span>
|
<span>{% trans "Import" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||||
<span>{% trans "Update" %}</span>
|
<span>{% trans "Update" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
|
|
|
@ -6,11 +6,12 @@ from django.dispatch import receiver
|
||||||
from django.db.backends.signals import connection_created
|
from django.db.backends.signals import connection_created
|
||||||
|
|
||||||
|
|
||||||
@receiver(connection_created, dispatch_uid="my_unique_identifier")
|
@receiver(connection_created)
|
||||||
def on_db_connection_ready(sender, **kwargs):
|
def on_db_connection_ready(sender, **kwargs):
|
||||||
from .signals import django_ready
|
from .signals import django_ready
|
||||||
if 'migrate' not in sys.argv:
|
if 'migrate' not in sys.argv:
|
||||||
django_ready.send(CommonConfig)
|
django_ready.send(CommonConfig)
|
||||||
|
connection_created.disconnect(on_db_connection_ready)
|
||||||
|
|
||||||
|
|
||||||
class CommonConfig(AppConfig):
|
class CommonConfig(AppConfig):
|
||||||
|
|
|
@ -21,6 +21,7 @@ class Organization(models.Model):
|
||||||
ROOT_NAME = 'ROOT'
|
ROOT_NAME = 'ROOT'
|
||||||
DEFAULT_ID = 'DEFAULT'
|
DEFAULT_ID = 'DEFAULT'
|
||||||
DEFAULT_NAME = 'DEFAULT'
|
DEFAULT_NAME = 'DEFAULT'
|
||||||
|
_user_admin_orgs = None
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Organization")
|
verbose_name = _("Organization")
|
||||||
|
@ -92,6 +93,8 @@ class Organization(models.Model):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_admin_orgs(cls, user):
|
def get_user_admin_orgs(cls, user):
|
||||||
|
if cls._user_admin_orgs and user.id in cls._user_admin_orgs:
|
||||||
|
return cls._user_admin_orgs[user.id]
|
||||||
admin_orgs = []
|
admin_orgs = []
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return admin_orgs
|
return admin_orgs
|
||||||
|
@ -100,6 +103,11 @@ class Organization(models.Model):
|
||||||
admin_orgs.append(cls.default())
|
admin_orgs.append(cls.default())
|
||||||
elif user.is_org_admin:
|
elif user.is_org_admin:
|
||||||
admin_orgs = user.admin_orgs.all()
|
admin_orgs = user.admin_orgs.all()
|
||||||
|
|
||||||
|
if cls._user_admin_orgs is None:
|
||||||
|
cls._user_admin_orgs = {user.id: admin_orgs}
|
||||||
|
else:
|
||||||
|
cls._user_admin_orgs[user.id] = admin_orgs
|
||||||
return admin_orgs
|
return admin_orgs
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -41,3 +41,8 @@ def on_org_user_changed(sender, instance=None, **kwargs):
|
||||||
for user_group in user_groups:
|
for user_group in user_groups:
|
||||||
user_group.users.remove(user)
|
user_group.users.remove(user)
|
||||||
set_current_org(old_org)
|
set_current_org(old_org)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=Organization.admins.through)
|
||||||
|
def on_org_admin_change(sender, **kwargs):
|
||||||
|
Organization._user_admin_orgs = None
|
||||||
|
|
|
@ -263,6 +263,7 @@ class UserGrantedNodesWithAssetsAsTreeApi(UserPermissionCacheMixin, ListAPIView)
|
||||||
system_users=self.system_user_id
|
system_users=self.system_user_id
|
||||||
)
|
)
|
||||||
nodes = util.get_nodes_with_assets()
|
nodes = util.get_nodes_with_assets()
|
||||||
|
print(list(nodes.keys()))
|
||||||
for node, assets in nodes.items():
|
for node, assets in nodes.items():
|
||||||
data = parse_node_to_tree_node(node)
|
data = parse_node_to_tree_node(node)
|
||||||
queryset.append(data)
|
queryset.append(data)
|
||||||
|
|
|
@ -180,7 +180,7 @@ class GenerateTree:
|
||||||
return dict(nodes)
|
return dict(nodes)
|
||||||
|
|
||||||
def get_nodes(self):
|
def get_nodes(self):
|
||||||
return self.nodes.keys()
|
return list(self.nodes.keys())
|
||||||
|
|
||||||
|
|
||||||
def get_user_permissions(user, include_group=True):
|
def get_user_permissions(user, include_group=True):
|
||||||
|
@ -256,9 +256,13 @@ class AssetPermissionCacheMixin:
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def node_key(self):
|
def node_asset_key(self):
|
||||||
return self.get_cache_key('NODES_WITH_ASSETS')
|
return self.get_cache_key('NODES_WITH_ASSETS')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node_key(self):
|
||||||
|
return self.get_cache_key('NODES')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def asset_key(self):
|
def asset_key(self):
|
||||||
key = self.get_cache_key('ASSETS')
|
key = self.get_cache_key('ASSETS')
|
||||||
|
@ -268,54 +272,47 @@ class AssetPermissionCacheMixin:
|
||||||
def system_key(self):
|
def system_key(self):
|
||||||
return self.get_cache_key('SYSTEM_USER')
|
return self.get_cache_key('SYSTEM_USER')
|
||||||
|
|
||||||
def get_assets_from_cache(self):
|
def get_resource_from_cache(self, resource):
|
||||||
cached = cache.get(self.asset_key)
|
key_map = {
|
||||||
|
"assets": self.asset_key,
|
||||||
|
"nodes": self.node_key,
|
||||||
|
"nodes_with_assets": self.node_asset_key,
|
||||||
|
"system_users": self.system_key
|
||||||
|
}
|
||||||
|
key = key_map.get(resource)
|
||||||
|
if not key:
|
||||||
|
raise ValueError("Not a valid resource: {}".format(resource))
|
||||||
|
cached = cache.get(key)
|
||||||
if not cached:
|
if not cached:
|
||||||
self.update_cache()
|
self.update_cache()
|
||||||
cached = cache.get(self.asset_key)
|
cached = cache.get(key)
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
def get_nodes_with_assets_from_cache(self):
|
def get_resource(self, resource):
|
||||||
cached = cache.get(self.node_key)
|
if self._is_using_cache():
|
||||||
if not cached:
|
return self.get_resource_from_cache(resource)
|
||||||
self.update_cache()
|
elif self._is_refresh_cache():
|
||||||
cached = cache.get(self.node_key)
|
self.expire_cache()
|
||||||
return cached
|
data = self.get_resource_from_cache(resource)
|
||||||
|
return data
|
||||||
|
else:
|
||||||
|
return self.get_resource_without_cache(resource)
|
||||||
|
|
||||||
|
def get_resource_without_cache(self, resource):
|
||||||
|
attr = 'get_{}_without_cache'.format(resource)
|
||||||
|
return getattr(self, attr)()
|
||||||
|
|
||||||
def get_nodes_with_assets(self):
|
def get_nodes_with_assets(self):
|
||||||
if self._is_using_cache():
|
return self.get_resource("nodes_with_assets")
|
||||||
return self.get_nodes_with_assets_from_cache()
|
|
||||||
elif self._is_refresh_cache():
|
|
||||||
self.expire_cache()
|
|
||||||
return self.get_nodes_with_assets_from_cache()
|
|
||||||
else:
|
|
||||||
return self.get_nodes_with_assets_without_cache()
|
|
||||||
|
|
||||||
def get_system_user_from_cache(self):
|
|
||||||
cached = cache.get(self.system_key)
|
|
||||||
if not cached:
|
|
||||||
self.update_cache()
|
|
||||||
cached = cache.get(self.system_key)
|
|
||||||
return cached
|
|
||||||
|
|
||||||
def get_assets(self):
|
def get_assets(self):
|
||||||
if self._is_using_cache():
|
return self.get_resource("assets")
|
||||||
return self.get_assets_from_cache()
|
|
||||||
elif self._is_refresh_cache():
|
def get_nodes(self):
|
||||||
self.expire_cache()
|
return self.get_resource("nodes")
|
||||||
return self.get_assets_from_cache()
|
|
||||||
else:
|
|
||||||
self.expire_cache()
|
|
||||||
return self.get_assets_without_cache()
|
|
||||||
|
|
||||||
def get_system_users(self):
|
def get_system_users(self):
|
||||||
if self._is_using_cache():
|
return self.get_resource("system_users")
|
||||||
return self.get_system_user_from_cache()
|
|
||||||
elif self._is_refresh_cache():
|
|
||||||
self.expire_cache()
|
|
||||||
return self.get_system_user_from_cache()
|
|
||||||
else:
|
|
||||||
return self.get_system_user_without_cache()
|
|
||||||
|
|
||||||
def get_meta_cache_key(self):
|
def get_meta_cache_key(self):
|
||||||
cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_{filter_id}'
|
cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_{filter_id}'
|
||||||
|
@ -332,6 +329,17 @@ class AssetPermissionCacheMixin:
|
||||||
# print("Meta id: {}".format(meta["id"]))
|
# print("Meta id: {}".format(meta["id"]))
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
|
def update_cache(self):
|
||||||
|
assets = self.get_resource_without_cache("assets")
|
||||||
|
nodes_with_assets = self.get_resource_without_cache("nodes_with_assets")
|
||||||
|
system_users = self.get_resource_without_cache("system_users")
|
||||||
|
nodes = self.get_resource_without_cache("nodes")
|
||||||
|
cache.set(self.asset_key, assets, self.CACHE_TIME)
|
||||||
|
cache.set(self.node_asset_key, nodes_with_assets, self.CACHE_TIME)
|
||||||
|
cache.set(self.system_key, system_users, self.CACHE_TIME)
|
||||||
|
cache.set(self.node_key, nodes, self.CACHE_TIME)
|
||||||
|
self.set_meta_to_cache()
|
||||||
|
|
||||||
def set_meta_to_cache(self):
|
def set_meta_to_cache(self):
|
||||||
key = self.get_meta_cache_key()
|
key = self.get_meta_cache_key()
|
||||||
meta = {
|
meta = {
|
||||||
|
@ -348,15 +356,6 @@ class AssetPermissionCacheMixin:
|
||||||
key = cache_key.format(obj_id=self.obj_id)
|
key = cache_key.format(obj_id=self.obj_id)
|
||||||
cache.delete_pattern(key)
|
cache.delete_pattern(key)
|
||||||
|
|
||||||
def update_cache(self):
|
|
||||||
assets = self.get_assets_without_cache()
|
|
||||||
nodes = self.get_nodes_with_assets_without_cache()
|
|
||||||
system_users = self.get_system_user_without_cache()
|
|
||||||
cache.set(self.asset_key, assets, self.CACHE_TIME)
|
|
||||||
cache.set(self.node_key, nodes, self.CACHE_TIME)
|
|
||||||
cache.set(self.system_key, system_users, self.CACHE_TIME)
|
|
||||||
self.set_meta_to_cache()
|
|
||||||
|
|
||||||
def expire_cache(self):
|
def expire_cache(self):
|
||||||
"""
|
"""
|
||||||
因为 获取用户的节点,资产,系统用户等都能会缓存,这里会清理所有与该对象有关的
|
因为 获取用户的节点,资产,系统用户等都能会缓存,这里会清理所有与该对象有关的
|
||||||
|
@ -378,15 +377,6 @@ class AssetPermissionCacheMixin:
|
||||||
key = cls.CACHE_KEY_PREFIX + '*'
|
key = cls.CACHE_KEY_PREFIX + '*'
|
||||||
cache.delete_pattern(key)
|
cache.delete_pattern(key)
|
||||||
|
|
||||||
def get_assets_without_cache(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def get_nodes_with_assets_without_cache(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def get_system_user_without_cache(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionUtil(AssetPermissionCacheMixin):
|
class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
get_permissions_map = {
|
get_permissions_map = {
|
||||||
|
@ -396,8 +386,10 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
"Node": get_node_permissions,
|
"Node": get_node_permissions,
|
||||||
"SystemUser": get_system_user_permissions,
|
"SystemUser": get_system_user_permissions,
|
||||||
}
|
}
|
||||||
assets_prefetch = ('id', 'hostname', 'ip', "platform", "domain_id",
|
assets_only = (
|
||||||
"comment", "is_active", "os", "org_id")
|
'id', 'hostname', 'ip', "platform", "domain_id",
|
||||||
|
'comment', 'is_active', 'os', 'org_id'
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, obj, cache_policy='0'):
|
def __init__(self, obj, cache_policy='0'):
|
||||||
self.object = obj
|
self.object = obj
|
||||||
|
@ -411,6 +403,8 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
self.change_org_if_need()
|
self.change_org_if_need()
|
||||||
self.nodes = None
|
self.nodes = None
|
||||||
self._nodes = None
|
self._nodes = None
|
||||||
|
self._assets_direct = None
|
||||||
|
self._nodes_direct = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def change_org_if_need():
|
def change_org_if_need():
|
||||||
|
@ -438,6 +432,8 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
返回用户/组授权规则直接关联的节点
|
返回用户/组授权规则直接关联的节点
|
||||||
:return: {node1: {system_user1: {'actions': set()},}}
|
:return: {node1: {system_user1: {'actions': set()},}}
|
||||||
"""
|
"""
|
||||||
|
if self._nodes_direct:
|
||||||
|
return self._nodes_direct
|
||||||
nodes = defaultdict(lambda: defaultdict(int))
|
nodes = defaultdict(lambda: defaultdict(int))
|
||||||
for perm in self.permissions:
|
for perm in self.permissions:
|
||||||
actions = [perm.actions]
|
actions = [perm.actions]
|
||||||
|
@ -446,9 +442,10 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
for node, system_user, action in itertools.product(_nodes, system_users, actions):
|
for node, system_user, action in itertools.product(_nodes, system_users, actions):
|
||||||
nodes[node][system_user] |= action
|
nodes[node][system_user] |= action
|
||||||
self.tree.add_nodes(nodes)
|
self.tree.add_nodes(nodes)
|
||||||
|
self._nodes_direct = nodes
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
def get_nodes(self):
|
def get_nodes_without_cache(self):
|
||||||
self.get_assets_direct()
|
self.get_assets_direct()
|
||||||
return self.tree.get_nodes()
|
return self.tree.get_nodes()
|
||||||
|
|
||||||
|
@ -458,15 +455,18 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
返回用户授权规则直接关联的资产
|
返回用户授权规则直接关联的资产
|
||||||
:return: {asset1: {system_user1: 1,}}
|
:return: {asset1: {system_user1: 1,}}
|
||||||
"""
|
"""
|
||||||
|
if self._assets_direct:
|
||||||
|
return self._assets_direct
|
||||||
assets = defaultdict(lambda: defaultdict(int))
|
assets = defaultdict(lambda: defaultdict(int))
|
||||||
for perm in self.permissions:
|
for perm in self.permissions:
|
||||||
actions = [perm.actions]
|
actions = [perm.actions]
|
||||||
_assets = perm.assets.all().prefetch_related(*self.assets_prefetch)
|
_assets = perm.assets.all().only(*self.assets_only)
|
||||||
system_users = perm.system_users.all()
|
system_users = perm.system_users.all()
|
||||||
iterable = itertools.product(_assets, system_users, actions)
|
iterable = itertools.product(_assets, system_users, actions)
|
||||||
for asset, system_user, action in iterable:
|
for asset, system_user, action in iterable:
|
||||||
assets[asset][system_user] |= action
|
assets[asset][system_user] |= action
|
||||||
self.tree.add_assets(assets)
|
self.tree.add_assets(assets)
|
||||||
|
self._assets_direct = assets
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
#@timeit
|
#@timeit
|
||||||
|
@ -476,6 +476,7 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
"""
|
"""
|
||||||
if self._assets:
|
if self._assets:
|
||||||
return self._assets
|
return self._assets
|
||||||
|
self.get_assets_direct()
|
||||||
nodes = self.get_nodes_direct()
|
nodes = self.get_nodes_direct()
|
||||||
pattern = set()
|
pattern = set()
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
|
@ -484,7 +485,7 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
if pattern:
|
if pattern:
|
||||||
assets = Asset.objects.filter(nodes__key__regex=pattern)\
|
assets = Asset.objects.filter(nodes__key__regex=pattern)\
|
||||||
.prefetch_related('nodes', "protocols")\
|
.prefetch_related('nodes', "protocols")\
|
||||||
.only(*self.assets_prefetch)\
|
.only(*self.assets_only)\
|
||||||
.distinct()
|
.distinct()
|
||||||
else:
|
else:
|
||||||
assets = []
|
assets = []
|
||||||
|
@ -501,9 +502,11 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.get_assets_without_cache()
|
self.get_assets_without_cache()
|
||||||
return self.tree.get_nodes_with_assets()
|
nodes_assets = self.tree.get_nodes_with_assets()
|
||||||
|
print(nodes_assets.keys())
|
||||||
|
return nodes_assets
|
||||||
|
|
||||||
def get_system_user_without_cache(self):
|
def get_system_users_without_cache(self):
|
||||||
system_users = set()
|
system_users = set()
|
||||||
permissions = self.permissions.prefetch_related('system_users')
|
permissions = self.permissions.prefetch_related('system_users')
|
||||||
for perm in permissions:
|
for perm in permissions:
|
||||||
|
|
|
@ -24,6 +24,7 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
|
||||||
|
|
||||||
@receiver(django_ready, dispatch_uid="my_unique_identifier")
|
@receiver(django_ready, dispatch_uid="my_unique_identifier")
|
||||||
def monkey_patch_settings(sender, **kwargs):
|
def monkey_patch_settings(sender, **kwargs):
|
||||||
|
logger.debug("Monkey patch settings")
|
||||||
cache_key_prefix = '_SETTING_'
|
cache_key_prefix = '_SETTING_'
|
||||||
custom_need_cache_settings = [
|
custom_need_cache_settings = [
|
||||||
'AUTHENTICATION_BACKENDS', 'TERMINAL_HOST_KEY',
|
'AUTHENTICATION_BACKENDS', 'TERMINAL_HOST_KEY',
|
||||||
|
@ -77,7 +78,6 @@ def monkey_patch_settings(sender, **kwargs):
|
||||||
@receiver(django_ready)
|
@receiver(django_ready)
|
||||||
def auto_generate_terminal_host_key(sender, **kwargs):
|
def auto_generate_terminal_host_key(sender, **kwargs):
|
||||||
try:
|
try:
|
||||||
print("Auto gen host key")
|
|
||||||
if Setting.objects.filter(name='TERMINAL_HOST_KEY').exists():
|
if Setting.objects.filter(name='TERMINAL_HOST_KEY').exists():
|
||||||
return
|
return
|
||||||
private_key, public_key = ssh_key_gen()
|
private_key, public_key = ssh_key_gen()
|
||||||
|
|
|
@ -648,8 +648,6 @@ jumpserver.initServerSideDataTable = function (options) {
|
||||||
$.each(rows, function (id, row) {
|
$.each(rows, function (id, row) {
|
||||||
table.selected_rows.push(row);
|
table.selected_rows.push(row);
|
||||||
if (row.id && $.inArray(row.id, table.selected) === -1){
|
if (row.id && $.inArray(row.id, table.selected) === -1){
|
||||||
console.log(table)
|
|
||||||
console.log(table.selected);
|
|
||||||
table.selected.push(row.id)
|
table.selected.push(row.id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1096,3 +1094,8 @@ function objectAttrsIsBool(obj, attrs) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDateAsCN(d) {
|
||||||
|
var date = new Date(d);
|
||||||
|
return date.toISOString().replace("T", " ").replace(/\..*/, "");
|
||||||
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ class SessionViewSet(BulkModelViewSet):
|
||||||
return super().perform_create(serializer)
|
return super().perform_create(serializer)
|
||||||
|
|
||||||
|
|
||||||
class CommandViewSet(viewsets.ViewSet):
|
class CommandViewSet(viewsets.ModelViewSet):
|
||||||
"""接受app发送来的command log, 格式如下
|
"""接受app发送来的command log, 格式如下
|
||||||
{
|
{
|
||||||
"user": "admin",
|
"user": "admin",
|
||||||
|
@ -70,10 +70,14 @@ class CommandViewSet(viewsets.ViewSet):
|
||||||
"""
|
"""
|
||||||
command_store = get_command_storage()
|
command_store = get_command_storage()
|
||||||
serializer_class = SessionCommandSerializer
|
serializer_class = SessionCommandSerializer
|
||||||
|
pagination_class = LimitOffsetPagination
|
||||||
permission_classes = (IsOrgAdminOrAppUser | IsAuditor,)
|
permission_classes = (IsOrgAdminOrAppUser | IsAuditor,)
|
||||||
|
filter_fields = ("asset", "system_user", "user", "input")
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.command_store.filter(**dict(self.request.query_params))
|
multi_command_storage = get_multi_command_storage()
|
||||||
|
queryset = multi_command_storage.filter()
|
||||||
|
return queryset
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
serializer = self.serializer_class(data=request.data, many=True)
|
serializer = self.serializer_class(data=request.data, many=True)
|
||||||
|
@ -88,12 +92,6 @@ class CommandViewSet(viewsets.ViewSet):
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
return Response({"msg": msg}, status=401)
|
return Response({"msg": msg}, status=401)
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
|
||||||
multi_command_storage = get_multi_command_storage()
|
|
||||||
queryset = multi_command_storage.filter()
|
|
||||||
serializer = self.serializer_class(queryset, many=True)
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
|
|
||||||
class SessionReplayViewSet(viewsets.ViewSet):
|
class SessionReplayViewSet(viewsets.ViewSet):
|
||||||
serializer_class = serializers.ReplaySerializer
|
serializer_class = serializers.ReplaySerializer
|
||||||
|
|
|
@ -9,8 +9,12 @@ class CommandStore(CommandBase):
|
||||||
self.storage_list = storage_list
|
self.storage_list = storage_list
|
||||||
|
|
||||||
def filter(self, **kwargs):
|
def filter(self, **kwargs):
|
||||||
queryset = []
|
if len(self.storage_list) == 1:
|
||||||
|
storage = list(self.storage_list)[0]
|
||||||
|
queryset = storage.filter(**kwargs)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
queryset = []
|
||||||
for storage in self.storage_list:
|
for storage in self.storage_list:
|
||||||
queryset.extend(storage.filter(**kwargs))
|
queryset.extend(storage.filter(**kwargs))
|
||||||
return sorted(queryset, key=lambda command: command.timestamp, reverse=True)
|
return sorted(queryset, key=lambda command: command.timestamp, reverse=True)
|
||||||
|
|
|
@ -8,109 +8,65 @@
|
||||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||||
<style>
|
<style>
|
||||||
#search_btn {
|
.toggle {
|
||||||
margin-bottom: 0;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-key {
|
||||||
|
width: 70px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_left_head %}
|
{% block table_pagination %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
<form id="search_form" method="get" action="" class="pull-right form-inline" style="padding-bottom: 8px">
|
|
||||||
<div class="form-group" id="date">
|
|
||||||
<div class="input-daterange input-group" id="datepicker">
|
|
||||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
|
||||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from|date:'Y-m-d' }}">
|
|
||||||
<span class="input-group-addon">to</span>
|
|
||||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to|date:'Y-m-d' }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<select class="select2 form-control" name="user">
|
|
||||||
<option value="">{% trans 'User' %}</option>
|
|
||||||
{% for u in user_list %}
|
|
||||||
<option value="{{ u }}" {% if u == user %} selected {% endif %}>{{ u }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<select class="select2 form-control" name="asset">
|
|
||||||
<option value="">{% trans 'Asset' %}</option>
|
|
||||||
{% for a in asset_list %}
|
|
||||||
<option value="{{ a }}" {% if a == asset %} selected {% endif %}>{{ a }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<select class="select2 form-control" name="system_user">
|
|
||||||
<option value="">{% trans 'System user' %}</option>
|
|
||||||
{% for s in system_user_list %}
|
|
||||||
<option value="{{ s }}" {% if s == system_user %} selected {% endif %}>{{ s }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" class="form-control input-sm" name="command" placeholder="{% trans 'Command' %}" value="{{ command }}">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="input-group-btn">
|
|
||||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
|
||||||
{% trans 'Search' %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
<table class="footable table table-stripped table-bordered toggle-arrow-tiny" data-page="false" >
|
<table class="table table-striped table-bordered table-hover" id="command_table" data-page="false" >
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th data-toggle="true">ID</th>
|
<th></th>
|
||||||
<th>{% trans 'Command' %}</th>
|
<th>{% trans 'Command' %}</th>
|
||||||
<th>{% trans 'User' %}</th>
|
<th>{% trans 'User' %}</th>
|
||||||
<th>{% trans 'Asset' %}</th>
|
<th>{% trans 'Asset' %}</th>
|
||||||
<th>{% trans 'System user'%}</th>
|
<th>{% trans 'System user'%}</th>
|
||||||
<th>{% trans 'Session' %}</th>
|
<th>{% trans 'Session' %}</th>
|
||||||
<th>{% trans 'Datetime' %}</th>
|
<th>{% trans 'Datetime' %}</th>
|
||||||
<th data-hide="all"></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for command in command_list %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ forloop.counter }}</td>
|
|
||||||
<td>{{ command.input }}</td>
|
|
||||||
<td>{{ command.user }}</td>
|
|
||||||
<td>{{ command.asset }}</td>
|
|
||||||
<td>{{ command.system_user }}</td>
|
|
||||||
<td><a href="{% url 'terminal:session-detail' pk=command.session %}">{% trans "Goto" %}</a></td>
|
|
||||||
<td>{{ command.timestamp|ts_to_date }}</td>
|
|
||||||
<td><pre style="border: none; background: none">{{ command.output }}</pre></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div id="actions" class="">
|
<div id="actions" class="hide">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||||
<option value="export">{% trans 'Export command' %}</option>
|
<option value="export">{% trans 'Export command' %}</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
<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">
|
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||||
{% trans 'Submit' %}
|
{% trans 'Submit' %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
|
<ul class="dropdown-menu search-help">
|
||||||
|
<li><a class="search-item" data-value="user">{% trans 'User' %}</a></li>
|
||||||
|
<li><a class="search-item" data-value="asset">{% trans 'Asset' %}</a></li>
|
||||||
|
<li><a class="search-item" data-value="system_user">{% trans 'System user' %}</a></li>
|
||||||
|
<li><a class="search-item" data-value="command">{% trans 'Command' %}</a></li>
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
|
{% block content_bottom_left %}{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
|
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
|
||||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.footable').footable();
|
$('.footable').footable();
|
||||||
$('.select2').select2({
|
$('.select2').select2({
|
||||||
|
@ -125,6 +81,7 @@ $(document).ready(function () {
|
||||||
calendarWeeks: true,
|
calendarWeeks: true,
|
||||||
autoclose: true
|
autoclose: true
|
||||||
});
|
});
|
||||||
|
initTable();
|
||||||
})
|
})
|
||||||
.on('click', '#btn_bulk_update', function(){
|
.on('click', '#btn_bulk_update', function(){
|
||||||
var action = $('#slct_bulk_update').val();
|
var action = $('#slct_bulk_update').val();
|
||||||
|
@ -137,7 +94,103 @@ $(document).ready(function () {
|
||||||
var pathname = window.location.pathname + 'export/';
|
var pathname = window.location.pathname + 'export/';
|
||||||
var url = pathname + params;
|
var url = pathname + params;
|
||||||
window.open(url);
|
window.open(url);
|
||||||
});
|
}).on("click", '#command_table_filter input', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
var offset1 = $('#command_table_filter input').offset();
|
||||||
|
var x = offset1.left;
|
||||||
|
var y = offset1.top;
|
||||||
|
console.log(x, y)
|
||||||
|
var offset = $(".search-help").parent().offset();
|
||||||
|
x -= offset.left;
|
||||||
|
y -= offset.top;
|
||||||
|
x += 18;
|
||||||
|
y += 80;
|
||||||
|
console.log(x, y)
|
||||||
|
$('.search-help').css({"top":y+"px", "left":x+"px", "position": "absolute"});
|
||||||
|
$('.dropdown-menu.search-help').show();
|
||||||
|
})
|
||||||
|
.on('click', '.search-item', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
var keyword = $("#command_table_filter input");
|
||||||
|
var value = $(this).data('value');
|
||||||
|
var old_value = keyword.val();
|
||||||
|
var new_value = old_value + ' ' + value + ':';
|
||||||
|
keyword.val(new_value.trim());
|
||||||
|
$('.dropdown-menu.search-help').hide();
|
||||||
|
keyword.focus()
|
||||||
|
})
|
||||||
|
.on("click", "document", function () {
|
||||||
|
$('.dropdown-menu.search-help').hide();
|
||||||
|
})
|
||||||
|
.on('click', '.toggle', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var detailRows = [];
|
||||||
|
var tr = $(this).closest('tr');
|
||||||
|
var row = table.row(tr);
|
||||||
|
var idx = $.inArray(tr.attr('id'), detailRows);
|
||||||
|
|
||||||
|
if (row.child.isShown()) {
|
||||||
|
tr.removeClass('details');
|
||||||
|
$(this).children('i:first-child').removeClass('fa-angle-down').addClass('fa-angle-right');
|
||||||
|
row.child.hide();
|
||||||
|
|
||||||
|
// Remove from the 'open' array
|
||||||
|
detailRows.splice(idx, 1);
|
||||||
|
} else {
|
||||||
|
tr.addClass('details');
|
||||||
|
$(this).children('i:first-child').removeClass('fa-angle-right').addClass('fa-angle-down');
|
||||||
|
row.child(format(row.data())).show();
|
||||||
|
// Add to the 'open' array
|
||||||
|
if (idx === -1) {
|
||||||
|
detailRows.push(tr.attr('id'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).on('click', 'body', function (e) {
|
||||||
|
$('.dropdown-menu.search-help').hide()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function format(d) {
|
||||||
|
var output = $("<pre style='border: none; background: none'></pre>");
|
||||||
|
output.append(d.output);
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function initTable() {
|
||||||
|
var options = {
|
||||||
|
ele: $('#command_table'),
|
||||||
|
columnDefs: [
|
||||||
|
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||||
|
$(td).addClass("toggle");
|
||||||
|
$(td).html("<i class='fa fa-angle-right'></i>");
|
||||||
|
}},
|
||||||
|
{targets: 5, createdCell: function (td, cellData) {
|
||||||
|
var data = '<a href="{% url "terminal:session-detail" pk=DEFAULT_PK %}">{% trans "Goto" %}</a>'
|
||||||
|
.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
|
$(td).html(data);
|
||||||
|
}},
|
||||||
|
{targets: 6, createdCell: function (td, cellData) {
|
||||||
|
var data = formatDateAsCN(cellData*1000);
|
||||||
|
$(td).html(data);
|
||||||
|
}},
|
||||||
|
],
|
||||||
|
toggle: true,
|
||||||
|
ajax_url: '{% url "api-terminal:command-list" %}',
|
||||||
|
columns: [
|
||||||
|
{data: "id"}, {data: "input", orderable: false}, {data: "user", orderable: false},
|
||||||
|
{data: "asset"}, {data: "system_user"},
|
||||||
|
{data: "session"}, {data: "timestamp", width: "160px"},
|
||||||
|
],
|
||||||
|
select: {},
|
||||||
|
op_html: $('#actions').html()
|
||||||
|
};
|
||||||
|
table = jumpserver.initServerSideDataTable(options);
|
||||||
|
return table
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from .const import USERS_CACHE_KEY, ASSETS_CACHE_KEY, SYSTEM_USER_CACHE_KEY
|
||||||
|
|
||||||
|
|
||||||
def get_session_asset_list():
|
def get_session_asset_list():
|
||||||
return Asset.objects.values_list('hostname', flat=True)
|
return Asset.objects.values_list()
|
||||||
|
|
||||||
|
|
||||||
def get_session_user_list():
|
def get_session_user_list():
|
||||||
|
|
|
@ -23,26 +23,13 @@ class CommandListView(DatetimeSearchMixin, PermissionsMixin, ListView):
|
||||||
template_name = "terminal/command_list.html"
|
template_name = "terminal/command_list.html"
|
||||||
context_object_name = 'command_list'
|
context_object_name = 'command_list'
|
||||||
paginate_by = settings.DISPLAY_PER_PAGE
|
paginate_by = settings.DISPLAY_PER_PAGE
|
||||||
command = user = asset = system_user = ""
|
|
||||||
date_from = date_to = None
|
date_from = date_to = None
|
||||||
permission_classes = [IsOrgAdmin | IsAuditor]
|
permission_classes = [IsOrgAdmin | IsAuditor]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.command = self.request.GET.get('command', '')
|
|
||||||
self.user = self.request.GET.get("user", '')
|
|
||||||
self.asset = self.request.GET.get('asset', '')
|
|
||||||
self.system_user = self.request.GET.get('system_user', '')
|
|
||||||
filter_kwargs = dict()
|
filter_kwargs = dict()
|
||||||
filter_kwargs['date_from'] = self.date_from
|
filter_kwargs['date_from'] = self.date_from
|
||||||
filter_kwargs['date_to'] = self.date_to
|
filter_kwargs['date_to'] = self.date_to
|
||||||
if self.user:
|
|
||||||
filter_kwargs['user'] = self.user
|
|
||||||
if self.asset:
|
|
||||||
filter_kwargs['asset'] = self.asset
|
|
||||||
if self.system_user:
|
|
||||||
filter_kwargs['system_user'] = self.system_user
|
|
||||||
if self.command:
|
|
||||||
filter_kwargs['input'] = self.command
|
|
||||||
queryset = common_storage.filter(**filter_kwargs)
|
queryset = common_storage.filter(**filter_kwargs)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -50,15 +37,8 @@ class CommandListView(DatetimeSearchMixin, PermissionsMixin, ListView):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Sessions'),
|
'app': _('Sessions'),
|
||||||
'action': _('Command list'),
|
'action': _('Command list'),
|
||||||
'user_list': utils.get_session_user_list(),
|
|
||||||
'asset_list': utils.get_session_asset_list(),
|
|
||||||
'system_user_list': utils.get_session_system_user_list(),
|
|
||||||
'command': self.command,
|
|
||||||
'date_from': self.date_from,
|
'date_from': self.date_from,
|
||||||
'date_to': self.date_to,
|
'date_to': self.date_to,
|
||||||
'user': self.user,
|
|
||||||
'asset': self.asset,
|
|
||||||
'system_user': self.system_user,
|
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
|
@ -2,27 +2,27 @@
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% block table_search %}
|
{% block table_search %}
|
||||||
<div class="" style="float: right">
|
<div class="" style="float: right">
|
||||||
<div class=" btn-group">
|
<div class=" btn-group">
|
||||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li>
|
<li>
|
||||||
<a class=" btn_export" tabindex="0">
|
<a class=" btn_export" tabindex="0">
|
||||||
<span>{% trans "Export" %}</span>
|
<span>{% trans "Export" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||||
<span>{% trans "Import" %}</span>
|
<span>{% trans "Import" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||||
<span>{% trans "Update" %}</span>
|
<span>{% trans "Update" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
<div class="pull-left m-r-5"><a href="{% url 'users:user-group-create' %}" class="btn btn-sm btn-primary ">{% trans "Create user group" %}</a></div>
|
<div class="pull-left m-r-5"><a href="{% url 'users:user-group-create' %}" class="btn btn-sm btn-primary ">{% trans "Create user group" %}</a></div>
|
||||||
|
|
Loading…
Reference in New Issue