mirror of https://github.com/jumpserver/jumpserver
[Feature] 增加标签
parent
dad21cadb3
commit
5bbad01909
|
@ -17,15 +17,15 @@ from rest_framework import generics
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.db.models import Q
|
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.db.models import Q, Count
|
||||||
|
|
||||||
from common.mixins import CustomFilterMixin
|
from common.mixins import CustomFilterMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
|
from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
|
||||||
get_user_granted_assets
|
get_user_granted_assets
|
||||||
from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser
|
from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser, Label
|
||||||
from . import serializers
|
from . import serializers
|
||||||
from .tasks import update_asset_hardware_info_manual, test_admin_user_connectability_manual, \
|
from .tasks import update_asset_hardware_info_manual, test_admin_user_connectability_manual, \
|
||||||
test_asset_connectability_manual, push_system_user_to_cluster_assets_manual, \
|
test_asset_connectability_manual, push_system_user_to_cluster_assets_manual, \
|
||||||
|
@ -295,3 +295,17 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||||
system_user = self.get_object()
|
system_user = self.get_object()
|
||||||
test_system_user_connectability_manual.delay(system_user)
|
test_system_user_connectability_manual.delay(system_user)
|
||||||
return Response({"msg": "Task created"})
|
return Response({"msg": "Task created"})
|
||||||
|
|
||||||
|
|
||||||
|
class LabelViewSet(BulkModelViewSet):
|
||||||
|
queryset = Label.objects.annotate(asset_count=Count("assets"))\
|
||||||
|
.annotate(admin_user_count=Count("adminuser")) \
|
||||||
|
.annotate(system_user_count=Count("systemuser"))
|
||||||
|
permission_classes = (IsSuperUser,)
|
||||||
|
serializer_class = serializers.LabelSerializer
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
if request.query_params.get("distinct"):
|
||||||
|
self.serializer_class = serializers.LabelDistinctSerializer
|
||||||
|
self.queryset = self.queryset.values("name").distinct()
|
||||||
|
return super().list(request, *args, **kwargs)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from .user import AdminUser, SystemUser
|
from .user import AdminUser, SystemUser
|
||||||
|
from .label import Label
|
||||||
from .cluster import *
|
from .cluster import *
|
||||||
from .group import *
|
from .group import *
|
||||||
from .asset import *
|
from .asset import *
|
||||||
|
|
|
@ -88,6 +88,8 @@ class Asset(models.Model):
|
||||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
||||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
||||||
|
|
||||||
|
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
|
||||||
|
|
||||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class Label(models.Model):
|
||||||
|
SYSTEM_CATEGORY = "S"
|
||||||
|
USER_CATEGORY = "U"
|
||||||
|
CATEGORY_CHOICES = (
|
||||||
|
("S", _("System")),
|
||||||
|
("U", _("User"))
|
||||||
|
)
|
||||||
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
|
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||||
|
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||||
|
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, verbose_name=_("Category"))
|
||||||
|
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||||
|
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||||
|
date_created = models.DateTimeField(
|
||||||
|
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{}:{}".format(self.name, self.value)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "assets_label"
|
|
@ -30,6 +30,7 @@ class AssetUser(models.Model):
|
||||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||||
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||||
|
labels = models.ManyToManyField('assets.Label', blank=True, verbose_name=_("Labels"))
|
||||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
date_updated = models.DateTimeField(auto_now=True)
|
date_updated = models.DateTimeField(auto_now=True)
|
||||||
|
|
|
@ -4,7 +4,7 @@ from rest_framework import serializers
|
||||||
from rest_framework_bulk.serializers import BulkListSerializer
|
from rest_framework_bulk.serializers import BulkListSerializer
|
||||||
|
|
||||||
from common.mixins import BulkSerializerMixin
|
from common.mixins import BulkSerializerMixin
|
||||||
from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser
|
from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser, Label
|
||||||
from .const import ADMIN_USER_CONN_CACHE_KEY, SYSTEM_USER_CONN_CACHE_KEY
|
from .const import ADMIN_USER_CONN_CACHE_KEY, SYSTEM_USER_CONN_CACHE_KEY
|
||||||
|
|
||||||
|
|
||||||
|
@ -284,3 +284,44 @@ class MyAssetGroupGrantedSerializer(serializers.ModelSerializer):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_assets_amount(obj):
|
def get_assets_amount(obj):
|
||||||
return len(obj.assets_granted)
|
return len(obj.assets_granted)
|
||||||
|
|
||||||
|
|
||||||
|
class LabelSerializer(serializers.ModelSerializer):
|
||||||
|
asset_count = serializers.SerializerMethodField()
|
||||||
|
admin_user_count = serializers.SerializerMethodField()
|
||||||
|
system_user_count = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Label
|
||||||
|
fields = '__all__'
|
||||||
|
list_serializer_class = BulkListSerializer
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_asset_count(obj):
|
||||||
|
return obj.asset_count
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_admin_user_count(obj):
|
||||||
|
return obj.admin_user_count
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_system_user_count(obj):
|
||||||
|
return obj.system_user_count
|
||||||
|
|
||||||
|
def get_field_names(self, declared_fields, info):
|
||||||
|
fields = super().get_field_names(declared_fields, info)
|
||||||
|
fields.extend(['get_category_display'])
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
class LabelDistinctSerializer(serializers.ModelSerializer):
|
||||||
|
value = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Label
|
||||||
|
fields = ("name", "value")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_value(obj):
|
||||||
|
labels = Label.objects.filter(name=obj["name"])
|
||||||
|
return ', '.join([label.value for label in labels])
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
{% extends '_base_list.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
{% block table_search %}{% endblock %}
|
||||||
|
{% block table_container %}
|
||||||
|
<div class="uc pull-left m-r-5">
|
||||||
|
<a href="" class="btn btn-sm btn-primary"> {% trans "Create label" %} </a>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped table-bordered table-hover " id="label_list_table" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||||
|
</th>
|
||||||
|
<th class="text-center">{% trans 'Name' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Value' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Asset' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Admin user' %}</th>
|
||||||
|
<th class="text-center">{% trans 'System user' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
{% block content_bottom_left %}{% endblock %}
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
function initTable() {
|
||||||
|
var options = {
|
||||||
|
ele: $('#label_list_table'),
|
||||||
|
columnDefs: [
|
||||||
|
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||||
|
{# var detail_btn = '<a href="{% url "assets:label-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';#}
|
||||||
|
var detail_btn = '<a>' + cellData + '</a>';
|
||||||
|
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||||
|
}},
|
||||||
|
|
||||||
|
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||||
|
var update_btn = '<a href="{% url "assets:cluster-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_cluster_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
|
$(td).html(update_btn + del_btn)
|
||||||
|
}}],
|
||||||
|
ajax_url: '{% url "api-assets:label-list" %}?sort=name',
|
||||||
|
columns: [
|
||||||
|
{data: "id"}, {data: "name" }, {data: "value" },
|
||||||
|
{data: "asset_count" }, {data: "admin_user_count" },
|
||||||
|
{data: "system_user_count" }, {data: "id"}
|
||||||
|
],
|
||||||
|
op_html: $('#actions').html()
|
||||||
|
};
|
||||||
|
jumpserver.initDataTable(options);
|
||||||
|
}
|
||||||
|
$(document).ready(function(){
|
||||||
|
initTable();
|
||||||
|
})
|
||||||
|
.on('click', '.btn_cluster_delete', function () {
|
||||||
|
var $this = $(this);
|
||||||
|
var $data_table = $('#cluster_list_table').DataTable();
|
||||||
|
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||||
|
var uid = $this.data('uid');
|
||||||
|
var the_url = '{% url "api-assets:cluster-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||||
|
objectDelete($this, name, the_url);
|
||||||
|
setTimeout( function () {
|
||||||
|
$data_table.ajax.reload();
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ router.register(r'v1/assets', api.AssetViewSet, 'asset')
|
||||||
router.register(r'v1/clusters', api.ClusterViewSet, 'cluster')
|
router.register(r'v1/clusters', api.ClusterViewSet, 'cluster')
|
||||||
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
|
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
|
||||||
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
|
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
|
||||||
|
router.register(r'v1/labels', api.LabelViewSet, 'label')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||||
|
|
|
@ -53,5 +53,6 @@ urlpatterns = [
|
||||||
# url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/asset-group$', views.SystemUserAssetGroupView.as_view(),
|
# url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/asset-group$', views.SystemUserAssetGroupView.as_view(),
|
||||||
# name='system-user-asset-group'),
|
# name='system-user-asset-group'),
|
||||||
|
|
||||||
|
url(r'^label/$', views.LabelListView.as_view(), name='label-list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,5 @@ from .group import *
|
||||||
from .cluster import *
|
from .cluster import *
|
||||||
from .system_user import *
|
from .system_user import *
|
||||||
from .admin_user import *
|
from .admin_user import *
|
||||||
|
from .label import *
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from django.views.generic import ListView, TemplateView, CreateView, \
|
||||||
|
UpdateView, DeleteView, DetailView
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
from common.mixins import AdminUserRequiredMixin
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"LabelListView", "LabelCreateView", "LabelUpdateView",
|
||||||
|
"LabelDetailView", "LabelDeleteView",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LabelListView(AdminUserRequiredMixin, TemplateView):
|
||||||
|
template_name = 'assets/label_list.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {
|
||||||
|
'app': _('Assets'),
|
||||||
|
'action': _('Label list'),
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LabelDetailView(AdminUserRequiredMixin, DetailView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LabelDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||||
|
pass
|
|
@ -24,6 +24,7 @@
|
||||||
<li id="cluster"><a href="{% url 'assets:cluster-list' %}">{% trans 'Cluster' %}</a></li>
|
<li id="cluster"><a href="{% url 'assets:cluster-list' %}">{% trans 'Cluster' %}</a></li>
|
||||||
<li id="admin-user"><a href="{% url 'assets:admin-user-list' %}">{% trans 'Admin user' %}</a></li>
|
<li id="admin-user"><a href="{% url 'assets:admin-user-list' %}">{% trans 'Admin user' %}</a></li>
|
||||||
<li id="system-user"><a href="{% url 'assets:system-user-list' %}">{% trans 'System user' %}</a></li>
|
<li id="system-user"><a href="{% url 'assets:system-user-list' %}">{% trans 'System user' %}</a></li>
|
||||||
|
<li id="system-user"><a href="{% url 'assets:label-list' %}">{% trans 'Label' %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li id="perms">
|
<li id="perms">
|
||||||
|
|
Loading…
Reference in New Issue