mirror of https://github.com/jumpserver/jumpserver
[Feature] 标签管理功能
parent
f37b331630
commit
7433327d75
|
@ -2,7 +2,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models import Cluster, Asset, AssetGroup, AdminUser, SystemUser
|
from .models import Cluster, Asset, AssetGroup, AdminUser, SystemUser, Label
|
||||||
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, get_logger
|
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, get_logger
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,20 +10,20 @@ logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class AssetCreateForm(forms.ModelForm):
|
class AssetCreateForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
fields = [
|
fields = [
|
||||||
'hostname', 'ip', 'public_ip', 'port', 'type', 'comment',
|
'hostname', 'ip', 'public_ip', 'port', 'type', 'comment',
|
||||||
'cluster', 'groups', 'status', 'env', 'is_active',
|
'cluster', 'groups', 'status', 'env', 'is_active',
|
||||||
'admin_user'
|
'admin_user', 'labels'
|
||||||
|
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
|
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
|
||||||
'cluster': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select cluster')}),
|
'cluster': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select cluster')}),
|
||||||
'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select admin user')}),
|
'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select admin user')}),
|
||||||
'port': forms.TextInput()
|
'labels': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select labels')}),
|
||||||
|
'port': forms.TextInput(),
|
||||||
}
|
}
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'hostname': '* required',
|
'hostname': '* required',
|
||||||
|
@ -40,6 +40,10 @@ class AssetCreateForm(forms.ModelForm):
|
||||||
raise forms.ValidationError(_("You need set a admin user if cluster not have"))
|
raise forms.ValidationError(_("You need set a admin user if cluster not have"))
|
||||||
return self.cleaned_data['admin_user']
|
return self.cleaned_data['admin_user']
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
print(self.cleaned_data)
|
||||||
|
return super().save(commit=commit)
|
||||||
|
|
||||||
|
|
||||||
class AssetUpdateForm(forms.ModelForm):
|
class AssetUpdateForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -47,7 +51,7 @@ class AssetUpdateForm(forms.ModelForm):
|
||||||
fields = [
|
fields = [
|
||||||
'hostname', 'ip', 'port', 'groups', "cluster", 'is_active',
|
'hostname', 'ip', 'port', 'groups', "cluster", 'is_active',
|
||||||
'type', 'env', 'status', 'public_ip', 'remote_card_ip', 'cabinet_no',
|
'type', 'env', 'status', 'public_ip', 'remote_card_ip', 'cabinet_no',
|
||||||
'cabinet_pos', 'number', 'comment', 'admin_user',
|
'cabinet_pos', 'number', 'comment', 'admin_user', 'labels'
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
|
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
|
||||||
|
@ -68,13 +72,15 @@ class AssetUpdateForm(forms.ModelForm):
|
||||||
raise forms.ValidationError(_("You need set a admin user if cluster not have"))
|
raise forms.ValidationError(_("You need set a admin user if cluster not have"))
|
||||||
return self.cleaned_data['admin_user']
|
return self.cleaned_data['admin_user']
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
print(self.cleaned_data)
|
||||||
|
return super().save(commit=commit)
|
||||||
|
|
||||||
|
|
||||||
class AssetBulkUpdateForm(forms.ModelForm):
|
class AssetBulkUpdateForm(forms.ModelForm):
|
||||||
assets = forms.ModelMultipleChoiceField(
|
assets = forms.ModelMultipleChoiceField(
|
||||||
required=True,
|
required=True, help_text='* required',
|
||||||
help_text='* required',
|
label=_('Select assets'), queryset=Asset.objects.all(),
|
||||||
label=_('Select assets'),
|
|
||||||
queryset=Asset.objects.all(),
|
|
||||||
widget=forms.SelectMultiple(
|
widget=forms.SelectMultiple(
|
||||||
attrs={
|
attrs={
|
||||||
'class': 'select2',
|
'class': 'select2',
|
||||||
|
@ -83,10 +89,7 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
port = forms.IntegerField(
|
port = forms.IntegerField(
|
||||||
label=_('Port'),
|
label=_('Port'), required=False, min_value=1, max_value=65535,
|
||||||
required=False,
|
|
||||||
min_value=1,
|
|
||||||
max_value=65535,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -96,7 +99,9 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
||||||
'type', 'env',
|
'type', 'env',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
|
'groups': forms.SelectMultiple(
|
||||||
|
attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
|
@ -140,7 +145,7 @@ class AssetGroupForm(forms.ModelForm):
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
group = super().save(commit=commit)
|
group = super().save(commit=commit)
|
||||||
assets= self.cleaned_data['assets']
|
assets = self.cleaned_data['assets']
|
||||||
group.assets.set(assets)
|
group.assets.set(assets)
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
@ -377,3 +382,22 @@ class SystemUserAuthForm(forms.Form):
|
||||||
|
|
||||||
class FileForm(forms.Form):
|
class FileForm(forms.Form):
|
||||||
file = forms.FileField()
|
file = forms.FileField()
|
||||||
|
|
||||||
|
|
||||||
|
class LabelForm(forms.ModelForm):
|
||||||
|
assets = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Asset.objects.all(), label=_('Asset'), required=False,
|
||||||
|
widget=forms.SelectMultiple(
|
||||||
|
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Label
|
||||||
|
fields = ['name', 'value', 'assets']
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
label = super().save(commit=commit)
|
||||||
|
assets = self.cleaned_data['assets']
|
||||||
|
label.assets.set(assets)
|
||||||
|
return label
|
||||||
|
|
|
@ -23,8 +23,15 @@ class Label(models.Model):
|
||||||
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
|
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_queryset_group_by_name(cls):
|
||||||
|
names = cls.objects.values_list('name', flat=True)
|
||||||
|
for name in names:
|
||||||
|
yield name, cls.objects.filter(name=name)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}:{}".format(self.name, self.value)
|
return "{}:{}".format(self.name, self.value)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "assets_label"
|
db_table = "assets_label"
|
||||||
|
unique_together = ('name', 'value')
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load asset_tags %}
|
||||||
|
{% load common_tags %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<form action="" method="post" class="form-horizontal">
|
<form action="" method="post" class="form-horizontal">
|
||||||
|
@ -28,12 +30,32 @@
|
||||||
<h3>{% trans 'Group' %}</h3>
|
<h3>{% trans 'Group' %}</h3>
|
||||||
{% bootstrap_field form.groups layout="horizontal" %}
|
{% bootstrap_field form.groups layout="horizontal" %}
|
||||||
|
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
<h3>{% trans 'Labels' %}</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Labels' %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<select name="labels" class="select2" data-placeholder="Select labels" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
|
||||||
|
{% for name, labels in form.labels.field.queryset|group_labels %}
|
||||||
|
<optgroup label="{{ name }}">
|
||||||
|
{% for label in labels %}
|
||||||
|
{% if label in form.labels.initial %}
|
||||||
|
<option value="{{ label.id }}" selected>{{ label.name }}:{{ label.value }}</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="{{ label.id }}">{{ label.name }}:{{ label.value }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Other' %}</h3>
|
<h3>{% trans 'Other' %}</h3>
|
||||||
{% bootstrap_field form.comment layout="horizontal" %}
|
{% bootstrap_field form.comment layout="horizontal" %}
|
||||||
{% bootstrap_field form.is_active layout="horizontal" %}
|
{% bootstrap_field form.is_active layout="horizontal" %}
|
||||||
|
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-4 col-sm-offset-2">
|
<div class="col-sm-4 col-sm-offset-2">
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load asset_tags %}
|
||||||
|
{% load common_tags %}
|
||||||
|
|
||||||
{% block custom_head_css_js_create %}
|
{% block custom_head_css_js_create %}
|
||||||
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
|
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
|
||||||
|
@ -33,6 +35,27 @@
|
||||||
<h3>{% trans 'Group' %}</h3>
|
<h3>{% trans 'Group' %}</h3>
|
||||||
{% bootstrap_field form.groups layout="horizontal" %}
|
{% bootstrap_field form.groups layout="horizontal" %}
|
||||||
|
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
<h3>{% trans 'Labels' %}</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Labels' %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<select name="labels" class="select2 labels" data-placeholder="Select labels" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
|
||||||
|
{% for name, labels in form.labels.field.queryset|group_labels %}
|
||||||
|
<optgroup label="{{ name }}">
|
||||||
|
{% for label in labels %}
|
||||||
|
{% if label in form.labels.initial %}
|
||||||
|
<option value="{{ label.id }}" selected>{{ label.value }}</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="{{ label.id }}">{{ label.value }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
<h3>{% trans 'Configuration' %}</h3>
|
<h3>{% trans 'Configuration' %}</h3>
|
||||||
{% bootstrap_field form.number layout="horizontal" %}
|
{% bootstrap_field form.number layout="horizontal" %}
|
||||||
|
@ -62,14 +85,18 @@
|
||||||
|
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
function format(item) {
|
||||||
$('.select2').select2({
|
var group = item.element.parentElement.label;
|
||||||
allowClear: true
|
return group + ':' + item.text;
|
||||||
});
|
}
|
||||||
$("#tags").select2({
|
$(document).ready(function () {
|
||||||
tags: true,
|
$('.select2').select2({
|
||||||
maximumSelectionLength: 8 //最多能够选择的个数
|
allowClear: true
|
||||||
});
|
});
|
||||||
})
|
$(".labels").select2({
|
||||||
|
allowClear: true,
|
||||||
|
templateSelection: format
|
||||||
|
});
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -0,0 +1,31 @@
|
||||||
|
{% extends '_base_create_update.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
<form id="groupForm" method="post" class="form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_field form.name layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.value layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.assets layout="horizontal" %}
|
||||||
|
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-4 col-sm-offset-2">
|
||||||
|
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||||
|
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('.select2').select2({
|
||||||
|
closeOnSelect: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -3,7 +3,7 @@
|
||||||
{% block table_search %}{% endblock %}
|
{% block table_search %}{% endblock %}
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
<div class="uc pull-left m-r-5">
|
<div class="uc pull-left m-r-5">
|
||||||
<a href="" class="btn btn-sm btn-primary"> {% trans "Create label" %} </a>
|
<a href="{% url 'assets:label-create' %}" class="btn btn-sm btn-primary"> {% trans "Create label" %} </a>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-striped table-bordered table-hover " id="label_list_table" >
|
<table class="table table-striped table-bordered table-hover " id="label_list_table" >
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
|
@ -1,6 +1,12 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils import timezone
|
|
||||||
from django.conf import settings
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def group_labels(queryset):
|
||||||
|
grouped = defaultdict(list)
|
||||||
|
for label in queryset:
|
||||||
|
grouped[label.name].append(label)
|
||||||
|
return [(name, labels) for name, labels in grouped.items()]
|
||||||
|
|
|
@ -54,5 +54,6 @@ urlpatterns = [
|
||||||
# name='system-user-asset-group'),
|
# name='system-user-asset-group'),
|
||||||
|
|
||||||
url(r'^label/$', views.LabelListView.as_view(), name='label-list'),
|
url(r'^label/$', views.LabelListView.as_view(), name='label-list'),
|
||||||
|
url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.views.generic import ListView, TemplateView, CreateView, \
|
from django.views.generic import TemplateView, CreateView, \
|
||||||
UpdateView, DeleteView, DetailView
|
UpdateView, DeleteView, DetailView
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from common.mixins import AdminUserRequiredMixin
|
from common.mixins import AdminUserRequiredMixin
|
||||||
|
from common.const import create_success_msg
|
||||||
|
from ..forms import LabelForm
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
@ -28,7 +30,18 @@ class LabelListView(AdminUserRequiredMixin, TemplateView):
|
||||||
|
|
||||||
|
|
||||||
class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
||||||
pass
|
template_name = 'assets/label_create_update.html'
|
||||||
|
form_class = LabelForm
|
||||||
|
success_url = reverse_lazy('assets:label-list')
|
||||||
|
success_message = create_success_msg
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {
|
||||||
|
'app': _('Assets'),
|
||||||
|
'action': _('Create label'),
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
|
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||||
|
|
|
@ -92,3 +92,8 @@ def is_bool_field(field):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def to_dict(data):
|
||||||
|
return dict(data)
|
||||||
|
|
|
@ -24,7 +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>
|
<li id="label"><a href="{% url 'assets:label-list' %}">{% trans 'Label' %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li id="perms">
|
<li id="perms">
|
||||||
|
@ -54,8 +54,6 @@
|
||||||
<li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Command' %}</a></li>
|
<li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Command' %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
{#<li id="">#}
|
{#<li id="">#}
|
||||||
{# <a href="#">#}
|
{# <a href="#">#}
|
||||||
{# <i class="fa fa-download"></i> <span class="nav-label">{% trans 'File' %}</span><span class="fa arrow"></span>#}
|
{# <i class="fa fa-download"></i> <span class="nav-label">{% trans 'File' %}</span><span class="fa arrow"></span>#}
|
||||||
|
|
Loading…
Reference in New Issue