[Feature] 添加资产树

pull/1044/head
ibuler 2018-01-31 10:46:26 +08:00
parent 460fa8e8a9
commit 3603b33a42
13 changed files with 258 additions and 12 deletions

View File

@ -14,6 +14,7 @@
# limitations under the License.
from rest_framework import generics
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
@ -25,7 +26,7 @@ from common.mixins import CustomFilterMixin
from common.utils import get_logger
from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
get_user_granted_assets
from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser, Label
from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser, Label, Node
from . import serializers
from .tasks import update_asset_hardware_info_manual, test_admin_user_connectability_manual, \
test_asset_connectability_manual, push_system_user_to_cluster_assets_manual, \
@ -308,3 +309,23 @@ class LabelViewSet(BulkModelViewSet):
self.serializer_class = serializers.LabelDistinctSerializer
self.queryset = self.queryset.values("name").distinct()
return super().list(request, *args, **kwargs)
class TreeViewApi(APIView):
def get_queryset(self):
return Node.objects.all()
def get(self, request):
data = []
for node in self.get_queryset():
if node.id == "0":
parent = "#"
else:
parent = ":".join(node.id.split(":")[:-1])
data.append({
"id": node.id,
"parent": parent,
"text": node.name
})
return Response(data)

View File

@ -51,7 +51,7 @@ class Node(models.Model):
return assets
@classmethod
def root(cls):
def get_root_node(cls):
obj, created = cls.objects.get_or_create(
id='0', defaults={"id": '0', 'name': "ROOT"}
)

View File

@ -0,0 +1,166 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/jstree/style.min.css' %}" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content">
<div class="file-manager">
<h5>Tree View</h5>
<div id="jstree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight">
<div class="mail-box-header">
<div class="uc pull-left m-r-5"><a href="{% url "assets:asset-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset" %} </a></div>
<div class="btn-group" style="float: right">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels">
{% for label in labels %}
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
{% endfor %}
</ul>
</div>
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Port' %}</th>
<th class="text-center">{% trans 'Cluster' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/plugins/jstree/jstree.min.js' %}"></script>
<script>
function initTable() {
var options = {
ele: $('#asset_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
$(td).html(rowData.cluster_name)
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
$(td).html(rowData.hardware_info)
}},
{targets: 6, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 7, createdCell: function (td, cellData) {
if (cellData == 'Unknown'){
$(td).html('<i class="fa fa-circle text-warning"></i>')
} else if (!cellData) {
$(td).html('<i class="fa fa-circle text-danger"></i>')
} else {
$(td).html('<i class="fa fa-circle text-navy"></i>')
}
}},
{targets: 8, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:asset-list" %}',
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "cluster"},
{data: "cpu_cores"}, {data: "is_active", orderable: false },
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
],
op_html: $('#actions').html()
};
return jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
$('#jstree').jstree({
'core' : {
'check_callback' : true,
'data': {
'url': '{% url "api-assets:tree-view" %}',
'data': function (node) {
return {'id': node.id}
}
}
},
'plugins' : [ 'types', 'dnd'],
"checkbox" : {
"keep_selected_style" : true
},
'types' : {
'default' : {
'icon' : 'fa fa-folder'
},
'html' : {
'icon' : 'fa fa-file-code-o'
},
'svg' : {
'icon' : 'fa fa-file-picture-o'
},
'css' : {
'icon' : 'fa fa-file-code-o'
},
'img' : {
'icon' : 'fa fa-file-image-o'
},
'js' : {
'icon' : 'fa fa-file-text-o'
}
}
});
})
</script>
{% endblock %}

View File

@ -42,6 +42,7 @@ urlpatterns = [
api.SystemUserPushApi.as_view(), name='system-user-push'),
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
url(r'^v1/tree/$', api.TreeViewApi.as_view(), name='tree-view')
]
urlpatterns += router.urls

View File

@ -57,5 +57,7 @@ urlpatterns = [
url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'),
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'),
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.LabelDeleteView.as_view(), name='label-delete'),
url(r'^tree/$', views.TreeView.as_view(), name='tree-view'),
]

View File

@ -5,4 +5,5 @@ from .cluster import *
from .system_user import *
from .admin_user import *
from .label import *
from .tree import *

23
apps/assets/views/tree.py Normal file
View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
#
from django.views.generic import TemplateView
from django.utils.translation import ugettext_lazy as _
from common.mixins import AdminUserRequiredMixin
__all__ = ['TreeView']
class TreeView(AdminUserRequiredMixin, TemplateView):
template_name = 'assets/tree.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('TreeView view'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -340,9 +340,41 @@ div.dataTables_wrapper div.dataTables_filter {
border: none;
}
.popover{
max-width: 100%; /* Max Width of the popover (depending on the container!) */
padding-left: 20px;
padding-right: 20px;
.popover-title {
padding: 8px 14px;
margin: 0;
font-size: 14px;
background-color: #f7f7f7;
border-bottom: 1px solid #ebebeb;
border-radius: 5px 5px 0 0;
}
.popover{
padding: 9px 14px;
position: absolute;
top: 0;
left: 0;
z-index: 1060;
display: none;
max-width: 276px;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 1.42857143;
text-decoration: none;
text-shadow: none;
text-transform: none;
letter-spacing: normal;
word-break: normal;
word-spacing: normal;
word-wrap: normal;
white-space: normal;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ccc;
border-radius: 6px;
box-shadow: 0 5px 10px rgba(0,0,0,.2);
line-break: auto;
}

View File

@ -316,7 +316,7 @@ jumpserver.initDataTable = function (options) {
$('[data-toggle="popover"]').popover({
html: true,
placement: 'bottom',
trigger: 'hover',
// trigger: 'hover',
container: 'body'
});
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -570,7 +570,7 @@ seriesTypes.heatmap = extendClass(seriesTypes.scatter, merge(colorSeriesMixin, {
y1 = Math.round(yAxis.translate(point.y - yPad, 0, 1, 0, 1)),
y2 = Math.round(yAxis.translate(point.y + yPad, 0, 1, 0, 1));
// Set plotX and plotY for use in K-D-Tree and more
// Set plotX and plotY for use in K-D-TreeView and more
point.plotX = (x1 + x2) / 2;
point.plotY = (y1 + y2) / 2;