Commit b9f82fd0 authored by ibuler's avatar ibuler

[Update] 优化命令记录列表

parent 1b44172b
......@@ -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)
if not cached:
self.update_cache()
cached = cache.get(self.asset_key)
return cached
def get_nodes_with_assets_from_cache(self):
cached = cache.get(self.node_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.node_key)
cached = cache.get(key)
return cached
def get_nodes_with_assets(self):
def get_resource(self, resource):
if self._is_using_cache():
return self.get_nodes_with_assets_from_cache()
return self.get_resource_from_cache(resource)
elif self._is_refresh_cache():
self.expire_cache()
return self.get_nodes_with_assets_from_cache()
data = self.get_resource_from_cache(resource)
return data
else:
return self.get_nodes_with_assets_without_cache()
return self.get_resource_without_cache(resource)
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_resource_without_cache(self, resource):
attr = 'get_{}_without_cache'.format(resource)
return getattr(self, attr)()
def get_nodes_with_assets(self):
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>
......
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