mirror of https://github.com/jumpserver/jumpserver
[Update] 优化命令记录列表
parent
1b44172bc5
commit
b9f82fd0ac
|
@ -60,8 +60,8 @@ function initAssetUserTable() {
|
|||
},
|
||||
{
|
||||
targets: 6, createdCell: function (td, cellData) {
|
||||
var date = new Date(cellData);
|
||||
$(td).html(date.toLocaleString());
|
||||
var data = formatDateAsCN(cellData);
|
||||
$(td).html(data);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -15,27 +15,27 @@
|
|||
|
||||
{% block table_search %}
|
||||
<div class="" style="float: right">
|
||||
<div class=" btn-group">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class=" btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||
<span>{% trans "Update" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" btn-group">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class=" btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||
<span>{% trans "Update" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
|
|
|
@ -6,11 +6,12 @@ from django.dispatch import receiver
|
|||
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):
|
||||
from .signals import django_ready
|
||||
if 'migrate' not in sys.argv:
|
||||
django_ready.send(CommonConfig)
|
||||
connection_created.disconnect(on_db_connection_ready)
|
||||
|
||||
|
||||
class CommonConfig(AppConfig):
|
||||
|
|
|
@ -21,6 +21,7 @@ class Organization(models.Model):
|
|||
ROOT_NAME = 'ROOT'
|
||||
DEFAULT_ID = 'DEFAULT'
|
||||
DEFAULT_NAME = 'DEFAULT'
|
||||
_user_admin_orgs = None
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Organization")
|
||||
|
@ -92,6 +93,8 @@ class Organization(models.Model):
|
|||
|
||||
@classmethod
|
||||
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 = []
|
||||
if user.is_anonymous:
|
||||
return admin_orgs
|
||||
|
@ -100,6 +103,11 @@ class Organization(models.Model):
|
|||
admin_orgs.append(cls.default())
|
||||
elif user.is_org_admin:
|
||||
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
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -41,3 +41,8 @@ def on_org_user_changed(sender, instance=None, **kwargs):
|
|||
for user_group in user_groups:
|
||||
user_group.users.remove(user)
|
||||
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
|
||||
)
|
||||
nodes = util.get_nodes_with_assets()
|
||||
print(list(nodes.keys()))
|
||||
for node, assets in nodes.items():
|
||||
data = parse_node_to_tree_node(node)
|
||||
queryset.append(data)
|
||||
|
|
|
@ -180,7 +180,7 @@ class GenerateTree:
|
|||
return dict(nodes)
|
||||
|
||||
def get_nodes(self):
|
||||
return self.nodes.keys()
|
||||
return list(self.nodes.keys())
|
||||
|
||||
|
||||
def get_user_permissions(user, include_group=True):
|
||||
|
@ -256,9 +256,13 @@ class AssetPermissionCacheMixin:
|
|||
)
|
||||
|
||||
@property
|
||||
def node_key(self):
|
||||
def node_asset_key(self):
|
||||
return self.get_cache_key('NODES_WITH_ASSETS')
|
||||
|
||||
@property
|
||||
def node_key(self):
|
||||
return self.get_cache_key('NODES')
|
||||
|
||||
@property
|
||||
def asset_key(self):
|
||||
key = self.get_cache_key('ASSETS')
|
||||
|
@ -268,54 +272,47 @@ class AssetPermissionCacheMixin:
|
|||
def system_key(self):
|
||||
return self.get_cache_key('SYSTEM_USER')
|
||||
|
||||
def get_assets_from_cache(self):
|
||||
cached = cache.get(self.asset_key)
|
||||
def get_resource_from_cache(self, resource):
|
||||
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:
|
||||
self.update_cache()
|
||||
cached = cache.get(self.asset_key)
|
||||
cached = cache.get(key)
|
||||
return cached
|
||||
|
||||
def get_nodes_with_assets_from_cache(self):
|
||||
cached = cache.get(self.node_key)
|
||||
if not cached:
|
||||
self.update_cache()
|
||||
cached = cache.get(self.node_key)
|
||||
return cached
|
||||
def get_resource(self, resource):
|
||||
if self._is_using_cache():
|
||||
return self.get_resource_from_cache(resource)
|
||||
elif self._is_refresh_cache():
|
||||
self.expire_cache()
|
||||
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):
|
||||
if self._is_using_cache():
|
||||
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
|
||||
return self.get_resource("nodes_with_assets")
|
||||
|
||||
def get_assets(self):
|
||||
if self._is_using_cache():
|
||||
return self.get_assets_from_cache()
|
||||
elif self._is_refresh_cache():
|
||||
self.expire_cache()
|
||||
return self.get_assets_from_cache()
|
||||
else:
|
||||
self.expire_cache()
|
||||
return self.get_assets_without_cache()
|
||||
return self.get_resource("assets")
|
||||
|
||||
def get_nodes(self):
|
||||
return self.get_resource("nodes")
|
||||
|
||||
def get_system_users(self):
|
||||
if self._is_using_cache():
|
||||
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()
|
||||
return self.get_resource("system_users")
|
||||
|
||||
def get_meta_cache_key(self):
|
||||
cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_{filter_id}'
|
||||
|
@ -332,6 +329,17 @@ class AssetPermissionCacheMixin:
|
|||
# print("Meta id: {}".format(meta["id"]))
|
||||
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):
|
||||
key = self.get_meta_cache_key()
|
||||
meta = {
|
||||
|
@ -348,15 +356,6 @@ class AssetPermissionCacheMixin:
|
|||
key = cache_key.format(obj_id=self.obj_id)
|
||||
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):
|
||||
"""
|
||||
因为 获取用户的节点,资产,系统用户等都能会缓存,这里会清理所有与该对象有关的
|
||||
|
@ -378,15 +377,6 @@ class AssetPermissionCacheMixin:
|
|||
key = cls.CACHE_KEY_PREFIX + '*'
|
||||
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):
|
||||
get_permissions_map = {
|
||||
|
@ -396,8 +386,10 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
"Node": get_node_permissions,
|
||||
"SystemUser": get_system_user_permissions,
|
||||
}
|
||||
assets_prefetch = ('id', 'hostname', 'ip', "platform", "domain_id",
|
||||
"comment", "is_active", "os", "org_id")
|
||||
assets_only = (
|
||||
'id', 'hostname', 'ip', "platform", "domain_id",
|
||||
'comment', 'is_active', 'os', 'org_id'
|
||||
)
|
||||
|
||||
def __init__(self, obj, cache_policy='0'):
|
||||
self.object = obj
|
||||
|
@ -411,6 +403,8 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
self.change_org_if_need()
|
||||
self.nodes = None
|
||||
self._nodes = None
|
||||
self._assets_direct = None
|
||||
self._nodes_direct = None
|
||||
|
||||
@staticmethod
|
||||
def change_org_if_need():
|
||||
|
@ -438,6 +432,8 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
返回用户/组授权规则直接关联的节点
|
||||
:return: {node1: {system_user1: {'actions': set()},}}
|
||||
"""
|
||||
if self._nodes_direct:
|
||||
return self._nodes_direct
|
||||
nodes = defaultdict(lambda: defaultdict(int))
|
||||
for perm in self.permissions:
|
||||
actions = [perm.actions]
|
||||
|
@ -446,9 +442,10 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
for node, system_user, action in itertools.product(_nodes, system_users, actions):
|
||||
nodes[node][system_user] |= action
|
||||
self.tree.add_nodes(nodes)
|
||||
self._nodes_direct = nodes
|
||||
return nodes
|
||||
|
||||
def get_nodes(self):
|
||||
def get_nodes_without_cache(self):
|
||||
self.get_assets_direct()
|
||||
return self.tree.get_nodes()
|
||||
|
||||
|
@ -458,15 +455,18 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
返回用户授权规则直接关联的资产
|
||||
:return: {asset1: {system_user1: 1,}}
|
||||
"""
|
||||
if self._assets_direct:
|
||||
return self._assets_direct
|
||||
assets = defaultdict(lambda: defaultdict(int))
|
||||
for perm in self.permissions:
|
||||
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()
|
||||
iterable = itertools.product(_assets, system_users, actions)
|
||||
for asset, system_user, action in iterable:
|
||||
assets[asset][system_user] |= action
|
||||
self.tree.add_assets(assets)
|
||||
self._assets_direct = assets
|
||||
return assets
|
||||
|
||||
#@timeit
|
||||
|
@ -476,6 +476,7 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
"""
|
||||
if self._assets:
|
||||
return self._assets
|
||||
self.get_assets_direct()
|
||||
nodes = self.get_nodes_direct()
|
||||
pattern = set()
|
||||
for node in nodes:
|
||||
|
@ -484,7 +485,7 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
if pattern:
|
||||
assets = Asset.objects.filter(nodes__key__regex=pattern)\
|
||||
.prefetch_related('nodes', "protocols")\
|
||||
.only(*self.assets_prefetch)\
|
||||
.only(*self.assets_only)\
|
||||
.distinct()
|
||||
else:
|
||||
assets = []
|
||||
|
@ -501,9 +502,11 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
|||
:return:
|
||||
"""
|
||||
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()
|
||||
permissions = self.permissions.prefetch_related('system_users')
|
||||
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")
|
||||
def monkey_patch_settings(sender, **kwargs):
|
||||
logger.debug("Monkey patch settings")
|
||||
cache_key_prefix = '_SETTING_'
|
||||
custom_need_cache_settings = [
|
||||
'AUTHENTICATION_BACKENDS', 'TERMINAL_HOST_KEY',
|
||||
|
@ -77,7 +78,6 @@ def monkey_patch_settings(sender, **kwargs):
|
|||
@receiver(django_ready)
|
||||
def auto_generate_terminal_host_key(sender, **kwargs):
|
||||
try:
|
||||
print("Auto gen host key")
|
||||
if Setting.objects.filter(name='TERMINAL_HOST_KEY').exists():
|
||||
return
|
||||
private_key, public_key = ssh_key_gen()
|
||||
|
|
|
@ -648,8 +648,6 @@ jumpserver.initServerSideDataTable = function (options) {
|
|||
$.each(rows, function (id, row) {
|
||||
table.selected_rows.push(row);
|
||||
if (row.id && $.inArray(row.id, table.selected) === -1){
|
||||
console.log(table)
|
||||
console.log(table.selected);
|
||||
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)
|
||||
|
||||
|
||||
class CommandViewSet(viewsets.ViewSet):
|
||||
class CommandViewSet(viewsets.ModelViewSet):
|
||||
"""接受app发送来的command log, 格式如下
|
||||
{
|
||||
"user": "admin",
|
||||
|
@ -70,10 +70,14 @@ class CommandViewSet(viewsets.ViewSet):
|
|||
"""
|
||||
command_store = get_command_storage()
|
||||
serializer_class = SessionCommandSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsOrgAdminOrAppUser | IsAuditor,)
|
||||
filter_fields = ("asset", "system_user", "user", "input")
|
||||
|
||||
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):
|
||||
serializer = self.serializer_class(data=request.data, many=True)
|
||||
|
@ -88,12 +92,6 @@ class CommandViewSet(viewsets.ViewSet):
|
|||
logger.error(msg)
|
||||
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):
|
||||
serializer_class = serializers.ReplaySerializer
|
||||
|
|
|
@ -9,8 +9,12 @@ class CommandStore(CommandBase):
|
|||
self.storage_list = storage_list
|
||||
|
||||
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:
|
||||
queryset.extend(storage.filter(**kwargs))
|
||||
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">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
<style>
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
.toggle {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.detail-key {
|
||||
width: 70px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_left_head %}
|
||||
{% block table_pagination %}
|
||||
{% endblock %}
|
||||
|
||||
{% 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 %}
|
||||
{% 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>
|
||||
<tr>
|
||||
<th data-toggle="true">ID</th>
|
||||
<th></th>
|
||||
<th>{% trans 'Command' %}</th>
|
||||
<th>{% trans 'User' %}</th>
|
||||
<th>{% trans 'Asset' %}</th>
|
||||
<th>{% trans 'System user'%}</th>
|
||||
<th>{% trans 'Session' %}</th>
|
||||
<th>{% trans 'Datetime' %}</th>
|
||||
<th data-hide="all"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<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>
|
||||
</table>
|
||||
|
||||
<div id="actions" class="">
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="export">{% trans 'Export command' %}</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>
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</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 %}
|
||||
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.footable').footable();
|
||||
$('.select2').select2({
|
||||
|
@ -125,6 +81,7 @@ $(document).ready(function () {
|
|||
calendarWeeks: true,
|
||||
autoclose: true
|
||||
});
|
||||
initTable();
|
||||
})
|
||||
.on('click', '#btn_bulk_update', function(){
|
||||
var action = $('#slct_bulk_update').val();
|
||||
|
@ -137,7 +94,103 @@ $(document).ready(function () {
|
|||
var pathname = window.location.pathname + 'export/';
|
||||
var url = pathname + params;
|
||||
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>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from .const import USERS_CACHE_KEY, ASSETS_CACHE_KEY, SYSTEM_USER_CACHE_KEY
|
|||
|
||||
|
||||
def get_session_asset_list():
|
||||
return Asset.objects.values_list('hostname', flat=True)
|
||||
return Asset.objects.values_list()
|
||||
|
||||
|
||||
def get_session_user_list():
|
||||
|
|
|
@ -23,26 +23,13 @@ class CommandListView(DatetimeSearchMixin, PermissionsMixin, ListView):
|
|||
template_name = "terminal/command_list.html"
|
||||
context_object_name = 'command_list'
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
command = user = asset = system_user = ""
|
||||
date_from = date_to = None
|
||||
permission_classes = [IsOrgAdmin | IsAuditor]
|
||||
|
||||
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['date_from'] = self.date_from
|
||||
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)
|
||||
return queryset
|
||||
|
||||
|
@ -50,15 +37,8 @@ class CommandListView(DatetimeSearchMixin, PermissionsMixin, ListView):
|
|||
context = {
|
||||
'app': _('Sessions'),
|
||||
'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_to': self.date_to,
|
||||
'user': self.user,
|
||||
'asset': self.asset,
|
||||
'system_user': self.system_user,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -2,27 +2,27 @@
|
|||
{% load i18n static %}
|
||||
{% block table_search %}
|
||||
<div class="" style="float: right">
|
||||
<div class=" btn-group">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class=" btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||
<span>{% trans "Update" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" btn-group">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class=" btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||
<span>{% trans "Update" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% 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>
|
||||
|
|
Loading…
Reference in New Issue