mirror of https://github.com/jumpserver/jumpserver
[Feature] 标签管理功能
parent
f37b331630
commit
7433327d75
|
@ -2,7 +2,7 @@
|
|||
from django import forms
|
||||
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
|
||||
|
||||
|
||||
|
@ -10,20 +10,20 @@ logger = get_logger(__file__)
|
|||
|
||||
|
||||
class AssetCreateForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'port', 'type', 'comment',
|
||||
'cluster', 'groups', 'status', 'env', 'is_active',
|
||||
'admin_user'
|
||||
'admin_user', 'labels'
|
||||
|
||||
]
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}),
|
||||
'cluster': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select cluster')}),
|
||||
'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 = {
|
||||
'hostname': '* required',
|
||||
|
@ -40,6 +40,10 @@ class AssetCreateForm(forms.ModelForm):
|
|||
raise forms.ValidationError(_("You need set a admin user if cluster not have"))
|
||||
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 Meta:
|
||||
|
@ -47,7 +51,7 @@ class AssetUpdateForm(forms.ModelForm):
|
|||
fields = [
|
||||
'hostname', 'ip', 'port', 'groups', "cluster", 'is_active',
|
||||
'type', 'env', 'status', 'public_ip', 'remote_card_ip', 'cabinet_no',
|
||||
'cabinet_pos', 'number', 'comment', 'admin_user',
|
||||
'cabinet_pos', 'number', 'comment', 'admin_user', 'labels'
|
||||
]
|
||||
widgets = {
|
||||
'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"))
|
||||
return self.cleaned_data['admin_user']
|
||||
|
||||
def save(self, commit=True):
|
||||
print(self.cleaned_data)
|
||||
return super().save(commit=commit)
|
||||
|
||||
|
||||
class AssetBulkUpdateForm(forms.ModelForm):
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
required=True,
|
||||
help_text='* required',
|
||||
label=_('Select assets'),
|
||||
queryset=Asset.objects.all(),
|
||||
required=True, help_text='* required',
|
||||
label=_('Select assets'), queryset=Asset.objects.all(),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
|
@ -83,10 +89,7 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
|||
)
|
||||
)
|
||||
port = forms.IntegerField(
|
||||
label=_('Port'),
|
||||
required=False,
|
||||
min_value=1,
|
||||
max_value=65535,
|
||||
label=_('Port'), required=False, min_value=1, max_value=65535,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -96,7 +99,9 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
|||
'type', 'env',
|
||||
]
|
||||
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):
|
||||
|
@ -140,7 +145,7 @@ class AssetGroupForm(forms.ModelForm):
|
|||
|
||||
def save(self, commit=True):
|
||||
group = super().save(commit=commit)
|
||||
assets= self.cleaned_data['assets']
|
||||
assets = self.cleaned_data['assets']
|
||||
group.assets.set(assets)
|
||||
return group
|
||||
|
||||
|
@ -377,3 +382,22 @@ class SystemUserAuthForm(forms.Form):
|
|||
|
||||
class FileForm(forms.Form):
|
||||
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')
|
||||
)
|
||||
|
||||
@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):
|
||||
return "{}:{}".format(self.name, self.value)
|
||||
|
||||
class Meta:
|
||||
db_table = "assets_label"
|
||||
unique_together = ('name', 'value')
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load asset_tags %}
|
||||
{% load common_tags %}
|
||||
|
||||
{% block form %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
|
@ -28,12 +30,32 @@
|
|||
<h3>{% trans 'Group' %}</h3>
|
||||
{% 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>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
{% bootstrap_field form.is_active layout="horizontal" %}
|
||||
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load asset_tags %}
|
||||
{% load common_tags %}
|
||||
|
||||
{% block custom_head_css_js_create %}
|
||||
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
|
||||
|
@ -33,6 +35,27 @@
|
|||
<h3>{% trans 'Group' %}</h3>
|
||||
{% 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>
|
||||
<h3>{% trans 'Configuration' %}</h3>
|
||||
{% bootstrap_field form.number layout="horizontal" %}
|
||||
|
@ -62,14 +85,18 @@
|
|||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
allowClear: true
|
||||
});
|
||||
$("#tags").select2({
|
||||
tags: true,
|
||||
maximumSelectionLength: 8 //最多能够选择的个数
|
||||
});
|
||||
})
|
||||
function format(item) {
|
||||
var group = item.element.parentElement.label;
|
||||
return group + ':' + item.text;
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
allowClear: true
|
||||
});
|
||||
$(".labels").select2({
|
||||
allowClear: true,
|
||||
templateSelection: format
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% 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_container %}
|
||||
<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>
|
||||
<table class="table table-striped table-bordered table-hover " id="label_list_table" >
|
||||
<thead>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
|
@ -1,6 +1,12 @@
|
|||
from collections import defaultdict
|
||||
|
||||
from django import template
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
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'),
|
||||
|
||||
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 -*-
|
||||
#
|
||||
|
||||
from django.views.generic import ListView, TemplateView, CreateView, \
|
||||
from django.views.generic import TemplateView, CreateView, \
|
||||
UpdateView, DeleteView, DetailView
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg
|
||||
from ..forms import LabelForm
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
@ -28,7 +30,18 @@ class LabelListView(AdminUserRequiredMixin, TemplateView):
|
|||
|
||||
|
||||
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):
|
||||
|
|
|
@ -92,3 +92,8 @@ def is_bool_field(field):
|
|||
return True
|
||||
else:
|
||||
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="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:label-list' %}">{% trans 'Label' %}</a></li>
|
||||
<li id="label"><a href="{% url 'assets:label-list' %}">{% trans 'Label' %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li id="perms">
|
||||
|
@ -54,8 +54,6 @@
|
|||
<li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Command' %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
{#<li id="">#}
|
||||
{# <a href="#">#}
|
||||
{# <i class="fa fa-download"></i> <span class="nav-label">{% trans 'File' %}</span><span class="fa arrow"></span>#}
|
||||
|
|
Loading…
Reference in New Issue