Commit a5b874e2 authored by ibuler's avatar ibuler

[Update] 改密支持windows

parent 75fb37d2
......@@ -16,7 +16,7 @@ from django.urls import reverse_lazy
from django.core.cache import cache
from django.db.models import Q
from common.mixins import IDInCacheFilterMixin
from common.mixins import IDInCacheFilterMixin, ApiMessageMixin
from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
......@@ -36,7 +36,7 @@ __all__ = [
]
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, BulkModelViewSet):
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, ApiMessageMixin, BulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
......@@ -47,6 +47,7 @@ class AssetViewSet(IDInCacheFilterMixin, LabelFilter, BulkModelViewSet):
serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser,)
success_message = _("%(hostname)s was %(action)s successfully")
def set_assets_node(self, assets):
if not isinstance(assets, list):
......
......@@ -6,21 +6,39 @@ from django.utils.translation import gettext_lazy as _
from common.utils import get_logger
from orgs.mixins import OrgModelForm
from ..models import Asset, AdminUser
from ..models import Asset, AdminUser, Protocol
logger = get_logger(__file__)
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
__all__ = [
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm',
'ProtocolForm'
]
class ProtocolForm(forms.ModelForm):
class Meta:
model = Protocol
fields = ['name', 'port']
widgets = {
'name': forms.Select(attrs={
'class': 'form-control protocol-name'
}),
'port': forms.TextInput(attrs={
'class': 'form-control protocol-port'
}),
}
class AssetCreateForm(OrgModelForm):
PROTOCOL_CHOICES = Protocol.PROTOCOL_CHOICES
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'public_ip', 'port', 'comment',
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain', 'protocol',
'domain',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
......@@ -32,7 +50,6 @@ class AssetCreateForm(OrgModelForm):
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'port': forms.TextInput(),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
......@@ -54,9 +71,9 @@ class AssetUpdateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain', 'protocol',
'domain',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
......@@ -68,7 +85,6 @@ class AssetUpdateForm(OrgModelForm):
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'port': forms.TextInput(),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
......@@ -101,8 +117,8 @@ class AssetBulkUpdateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
'assets', 'port', 'admin_user', 'labels', 'platform',
'protocol', 'domain',
'assets', 'admin_user', 'labels', 'platform',
'domain',
]
widgets = {
'labels': forms.SelectMultiple(
......
......@@ -15,4 +15,9 @@ class Migration(migrations.Migration):
name='ip',
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
),
migrations.AlterField(
model_name='asset',
name='public_ip',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Public IP'),
),
]
# Generated by Django 2.1.7 on 2019-05-22 02:58
import django.core.validators
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0027_auto_20190521_1703'),
]
operations = [
migrations.CreateModel(
name='Protocol',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Name')),
('port', models.IntegerField(default=22, validators=[django.core.validators.MaxValueValidator(65535), django.core.validators.MinValueValidator(1)], verbose_name='Port')),
],
),
migrations.AddField(
model_name='asset',
name='protocols',
field=models.ManyToManyField(to='assets.Protocol',
verbose_name='Protocol'),
),
]
# Generated by Django 2.1.7 on 2019-05-22 03:14
from django.db import migrations
def migrate_assets_protocol(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
db_alias = schema_editor.connection.alias
assets = asset_model.objects.using(db_alias).all()
for asset in assets:
asset.protocols.create(name=asset.protocol, port=asset.port)
class Migration(migrations.Migration):
dependencies = [
('assets', '0028_protocol'),
]
operations = [
migrations.RunPython(migrate_assets_protocol),
]
......@@ -8,4 +8,3 @@ from .asset import *
from .cmd_filter import *
from .utils import *
from .authbook import *
from applications.models.remote_app import *
......@@ -12,11 +12,12 @@ from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from django.core.validators import MinValueValidator, MaxValueValidator
from .user import AdminUser, SystemUser
from orgs.mixins import OrgModelMixin, OrgManager
__all__ = ['Asset']
__all__ = ['Asset', 'Protocol']
logger = logging.getLogger(__name__)
......@@ -47,6 +48,29 @@ class AssetQuerySet(models.QuerySet):
return self.active()
class Protocol(models.Model):
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet'
PROTOCOL_VNC = 'vnc'
PROTOCOL_CHOICES = (
(PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, 'rdp'),
(PROTOCOL_TELNET, 'telnet (beta)'),
(PROTOCOL_VNC, 'vnc'),
)
PORT_VALIDATORS = [MaxValueValidator(65535), MinValueValidator(1)]
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=16, choices=PROTOCOL_CHOICES,
default=PROTOCOL_SSH, verbose_name=_("Name"))
port = models.IntegerField(default=22, verbose_name=_("Port"),
validators=PORT_VALIDATORS)
def __str__(self):
return "{}:{}".format(self.name, self.port)
class Asset(OrgModelMixin):
# Important
PLATFORM_CHOICES = (
......@@ -59,22 +83,15 @@ class Asset(OrgModelMixin):
('Other', 'Other'),
)
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet'
PROTOCOL_VNC = 'vnc'
PROTOCOL_CHOICES = (
(PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, 'rdp'),
(PROTOCOL_TELNET, 'telnet (beta)'),
(PROTOCOL_VNC, 'vnc'),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
protocol = models.CharField(max_length=128, default=Protocol.PROTOCOL_SSH,
choices=Protocol.PROTOCOL_CHOICES,
verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocol"))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
......@@ -84,7 +101,7 @@ class Asset(OrgModelMixin):
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
# Collect
......
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from rest_framework.validators import ValidationError
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgResourceSerializerMixin
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import Asset
from common.validators import ProjectUniqueValidator
from ..models import Asset, Protocol
from .system_user import AssetSystemUserSerializer
__all__ = [
......@@ -16,25 +18,32 @@ __all__ = [
]
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResourceSerializerMixin):
class ProtocolSerializer(serializers.ModelSerializer):
class Meta:
model = Protocol
fields = ["name", "port"]
class AssetSerializer(BulkSerializerMixin, OrgResourceSerializerMixin, serializers.ModelSerializer):
protocols = ProtocolSerializer(many=True)
"""
资产的数据结构
"""
class Meta:
model = Asset
list_serializer_class = AdaptedBulkListSerializer
# validators = [] # 解决批量导入时unique_together字段校验失败
fields = [
'id', 'org_id', 'org_name', 'ip', 'hostname', 'protocol', 'port',
'platform', 'is_active', 'public_ip', 'domain', 'admin_user',
'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
'protocols', 'platform', 'is_active', 'public_ip', 'domain',
'admin_user', 'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
'hostname_raw', 'comment', 'created_by', 'date_created',
'hardware_info', 'connectivity'
]
read_only_fields = (
'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
'os', 'os_version', 'os_arch', 'hostname_raw',
'created_by', 'date_created',
......@@ -43,7 +52,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResou
'hardware_info': {'label': _('Hardware info')},
'connectivity': {'label': _('Connectivity')},
'org_name': {'label': _('Org name')}
}
@classmethod
......@@ -53,18 +61,64 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResou
.select_related('admin_user')
return queryset
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend([
'hardware_info', 'connectivity', 'org_name'
])
return fields
@staticmethod
def validate_protocols(attr):
protocols_name = [i.get("name", "ssh") for i in attr]
errors = [{} for i in protocols_name]
for i, name in enumerate(protocols_name):
if name in protocols_name[:i]:
errors[i] = {"name": _("Protocol duplicate: {}").format(name)}
if any(errors):
raise ValidationError(errors)
return attr
def create(self, validated_data):
protocols_data = validated_data.pop("protocols")
# 兼容老的api
protocol = validated_data.get("protocol")
port = validated_data.get("port")
if not protocols_data and protocol and port:
protocols_data = [{"name": protocol, "port": port}]
if not protocol and not port and protocols_data:
validated_data["protocol"] = protocols_data[0]["name"]
validated_data["port"] = protocols_data[0]["port"]
protocols_serializer = ProtocolSerializer(data=protocols_data, many=True)
protocols_serializer.is_valid(raise_exception=True)
protocols = protocols_serializer.save()
instance = super().create(validated_data)
instance.protocols.set(protocols)
return instance
def update(self, instance, validated_data):
protocols_data = validated_data.pop("protocols")
# 兼容老的api
protocol = validated_data.get("protocol")
port = validated_data.get("port")
if not protocols_data and protocol and port:
protocols_data = [{"name": protocol, "port": port}]
if not protocol and not port and protocols_data:
validated_data["protocol"] = protocols_data[0]["name"]
validated_data["port"] = protocols_data[0]["port"]
protocols_serializer = ProtocolSerializer(data=protocols_data, many=True)
protocols_serializer.is_valid(raise_exception=True)
protocols = protocols_serializer.save()
instance = super().update(instance, validated_data)
instance.protocols.all().delete()
instance.protocols.set(protocols)
return instance
class AssetAsNodeSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol']
fields = ['id', 'hostname', 'ip', 'platform', 'protocols']
class AssetGrantedSerializer(serializers.ModelSerializer):
......@@ -78,9 +132,9 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = (
"id", "hostname", "ip", "port", "system_users_granted",
"id", "hostname", "ip", "system_users_granted",
"is_active", "system_users_join", "os", 'domain',
"platform", "comment", "protocol", "org_id", "org_name",
"platform", "comment", "protocols", "org_id", "org_name",
)
@staticmethod
......
......@@ -5,6 +5,7 @@ from django.db.models.signals import post_save, m2m_changed, post_delete
from django.dispatch import receiver
from common.utils import get_logger
from common.decorator import on_transaction_commit
from .models import Asset, SystemUser, Node, AuthBook
from .tasks import (
update_assets_hardware_info_util,
......@@ -32,9 +33,12 @@ def set_asset_root_node(asset):
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
@on_transaction_commit
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
if created:
logger.info("Asset `{}` create signal received".format(instance))
# 获取资产硬件信息
update_asset_hardware_info_on_created(instance)
test_asset_conn_on_created(instance)
......
......@@ -16,12 +16,24 @@
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Protocols' %}</h3>
<div class="protocols">
{% for fm in formset.forms %}
<div class="form-group">
<div class="col-md-2 col-md-offset-2" style="text-align: right">{{ fm.name }}</div>
<div class="col-md-6">{{ fm.port }}</div>
<div class="col-md-1" style="padding: 6px 0">
<a class="btn btn-danger btn-xs btn-protocol btn-del"><span class="fa fa-minus"></span> </a>
<a class="btn btn-primary btn-xs btn-protocol btn-add" style="display: none"><span class="fa fa-plus"></span></a>
</div>
</div>
{% endfor %}
</div>
<div class="hr-line-dashed"></div>
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
......@@ -55,6 +67,8 @@
{% endif %}
</div>
</div>
{% block extra %}
{% endblock %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
......@@ -73,11 +87,25 @@
{% block custom_foot_js %}
<script>
var instanceId = "{{ object.id }}";
var protocolLen = 0;
function format(item) {
var group = item.element.parentElement.label;
return group + ':' + item.text;
}
function protocolBtnShow() {
$(".btn-protocol.btn-add").hide();
$(".btn-protocol.btn-add:last").show();
var btnDel = $(".btn-protocol.btn-del");
if (btnDel.length === 1) {
btnDel.addClass("disabled")
} else {
btnDel.removeClass("disabled")
}
}
$(document).ready(function () {
$('.select2').select2({
allowClear: true
......@@ -89,20 +117,130 @@ $(document).ready(function () {
$('#id_nodes.select2').select2({
closeOnSelect: false
});
$("#id_protocol").change(function (){
var protocol = $("#id_protocol option:selected").text();
var port = 22;
if(protocol === 'rdp'){
port = 3389;
}
else if(protocol === 'telnet (beta)'){
port = 23;
protocolBtnShow()
})
.on("change", "#id_platform", function () {
if (instanceId !== "") {
return
}
var platform = $(this).val();
var protocolRef = $(".protocols").find("select").first()
var protocol = protocolRef.val();
var protocolShould = "";
if (platform.startsWith("Windows")){
protocolShould = "rdp"
} else {
protocolShould = "ssh"
}
if (protocol !== protocolShould) {
protocolRef.val(protocolShould);
protocolRef.trigger("change")
}
})
.on("click", ".btn-protocol.btn-del", function () {
$(this).parent().parent().remove();
protocolBtnShow()
})
.on("click", ".btn-protocol.btn-add", function () {
var protocol = "";
var protocolsRef = $(".protocols");
var firstProtocolForm = protocolsRef.children().first();
var newProtocolForm = firstProtocolForm.clone();
var protocolChoices = $.map($(firstProtocolForm.find('select option')), function (option) {
return option.value
});
var protocolsSet = $.map(protocolsRef.find('select option:selected'), function(option) {
return option.value
});
for (var i=0;i<protocolChoices.length;i++) {
var p = protocolChoices[i];
if (protocolsSet.indexOf(p) === -1) {
protocol = p;
break
}
else if(protocol === 'vnc'){
}
if (protocol === "") {
return
}
if (protocolLen === 0) {
protocolLen = protocolsRef.length;
}
var selectName = "form-" + protocolLen + "-name";
var selectId = "id_" + selectName;
var portName = "form-" + protocolLen + "-port";
var portId = "id_" + portName;
newProtocolForm.find("select").prop("name", selectName).prop("id", selectId);
newProtocolForm.find("input").prop("name", portName).prop("id", portId);
newProtocolForm.find("option[value='" + protocol + "']").attr("selected", true);
protocolsRef.append(newProtocolForm);
protocolLen += 1;
$("#" + selectId).trigger("change");
protocolBtnShow()
})
.on("change", ".protocol-name", function () {
var name = $(this).val();
var port = 22;
switch (name) {
case "ssh":
port = 22;
break;
case "rdp":
port = 3389;
break;
case "telnet":
port = 21;
break;
case "vnc":
port = 5901;
}
$("#id_port").val(port);
});
break;
default:
port = 22;
break
}
$(this).parent().parent().find(".protocol-port").val(port);
})
</script>
{% block form_submit %}
<script>
$(document).ready(function () {
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-assets:asset-list' %}';
var redirect_to = '{% url "assets:asset-list" %}';
var form = $("form");
var protocols = {};
var data = form.serializeObject();
$.each(data, function (k, v) {
if (k.startsWith("form")){
delete data[k];
var _k = k.split("-");
var formName = _k.slice(0, 2).join("-");
var key = _k[_k.length-1];
if (!protocols[formName]) {
protocols[formName] = {}
}
protocols[formName][key] = v
}
});
protocols = $.map(protocols, function ( v) {
return v
});
data["protocols"] = protocols;
if (typeof data["nodes"] == "string") {
data["nodes"] = [data["nodes"]]
}
var props = {
url: the_url,
data: data,
method: "POST",
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
</script>
{% endblock %}
{% endblock %}
\ No newline at end of file
{% extends '_base_create_update.html' %}
{% load static %}
{% extends 'assets/asset_create.html' %}
{% 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">
<script src="{% static "js/plugins/inputTags.jquery.min.js" %}"></script>
{% endblock %}
{% block form %}
<form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Node' %}</h3>
{% bootstrap_field form.nodes 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 'Label' %}</label>
<div class="col-md-9">
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Label' %}" 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>
{% block extra %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Configuration' %}</h3>
{% bootstrap_field form.number layout="horizontal" %}
<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">
<button class="btn btn-white" 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>
function format(item) {
var group = item.element.parentElement.label;
return group + ':' + item.text;
}
{% block form_submit %}
<script>
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
$(".labels").select2({
allowClear: true,
templateSelection: format
});
})
</script>
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url 'api-assets:asset-detail' pk=object.id %}';
var redirect_to = '{% url "assets:asset-list" %}';
var form = $("form");
var protocols = {};
var data = form.serializeObject();
$.each(data, function (k, v) {
if (k.startsWith("form")){
delete data[k];
var _k = k.split("-");
var formName = _k.slice(0, 2).join("-");
var key = _k[_k.length-1];
if (!protocols[formName]) {
protocols[formName] = {}
}
protocols[formName][key] = v
}
});
protocols = $.map(protocols, function ( v) {
return v
});
data["protocols"] = protocols;
if (typeof data["nodes"] == "string") {
data["nodes"] = [data["nodes"]]
}
var props = {
url: the_url,
data: data,
method: "PUT",
form: form,
redirect_to: redirect_to
};
formSubmit(props);
});
</script>
{% endblock %}
\ No newline at end of file
......@@ -23,6 +23,7 @@ from django.utils import timezone
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
from django.contrib.messages.views import SuccessMessageMixin
from django.forms.formsets import formset_factory
from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none, get_logger
......@@ -30,8 +31,6 @@ from common.permissions import AdminUserRequiredMixin
from common.const import (
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
)
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from orgs.utils import current_org
from .. import forms
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
......@@ -101,10 +100,30 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
form["nodes"].initial = node
return form
def get_protocol_formset(self):
ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5)
if self.request.method == "POST":
formset = ProtocolFormset(self.request.POST)
else:
formset = ProtocolFormset()
return formset
def form_valid(self, form):
formset = self.get_protocol_formset()
valid = formset.is_valid()
if not valid:
return self.form_invalid(form)
protocols = formset.save()
instance = super().form_valid(form)
instance.protocols.set(protocols)
return instance
def get_context_data(self, **kwargs):
formset = self.get_protocol_formset()
context = {
'app': _('Assets'),
'action': _('Create asset'),
'formset': formset,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......@@ -159,10 +178,21 @@ class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
template_name = 'assets/asset_update.html'
success_url = reverse_lazy('assets:asset-list')
def get_protocol_formset(self):
ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5)
if self.request.method == "POST":
formset = ProtocolFormset(self.request.POST)
else:
initial_data = [{"name": p.name, "port": p.port} for p in self.object.protocols.all()]
formset = ProtocolFormset(initial=initial_data)
return formset
def get_context_data(self, **kwargs):
formset = self.get_protocol_formset()
context = {
'app': _('Assets'),
'action': _('Update asset'),
'formset': formset,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......
# -*- coding: utf-8 -*-
#
from django.db import transaction
def on_transaction_commit(func):
"""
如果不调用on_commit, 对象创建时添加多对多字段值失败
"""
def inner(*args, **kwargs):
transaction.on_commit(lambda: func(*args, **kwargs))
return inner
......@@ -5,6 +5,7 @@ from django.http import JsonResponse
from django.utils import timezone
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from rest_framework.utils import html
from rest_framework.settings import api_settings
from rest_framework.exceptions import ValidationError
......@@ -203,3 +204,26 @@ class DatetimeSearchMixin:
def get(self, request, *args, **kwargs):
self.get_date_range()
return super().get(request, *args, **kwargs)
class ApiMessageMixin:
success_message = _("%(name)s was %(action)s successfully")
_action_map = {"create": _("create"), "update": _("update")}
def get_success_message(self, cleaned_data):
data = {k: v for k, v in cleaned_data.items()}
action = getattr(self, "action", "create")
data["action"] = self._action_map.get(action)
message = self.success_message % data
return message
def dispatch(self, request, *args, **kwargs):
resp = super().dispatch(request, *args, **kwargs)
if request.method.lower() in ("get", "delete"):
return resp
if resp.status_code >= 400:
return resp
message = self.get_success_message(resp.data)
if message:
messages.success(request, message)
return resp
......@@ -3,5 +3,22 @@
from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _
from rest_framework.validators import (
UniqueTogetherValidator, ValidationError
)
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
\ No newline at end of file
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
class ProjectUniqueValidator(UniqueTogetherValidator):
def __call__(self, attrs):
try:
super().__call__(attrs)
except ValidationError as e:
errors = {}
for field in self.fields:
if field == "org_id":
continue
errors[field] = _('This field must be unique.')
raise ValidationError(errors)
......@@ -274,7 +274,10 @@ class Config(dict):
return v
tp = type(default_value)
try:
v = tp(v)
if tp in [list, dict]:
v = json.loads(v)
else:
v = tp(v)
except Exception:
pass
return v
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-21 21:09+0800\n"
"POT-Creation-Date: 2019-05-24 10:29+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
......@@ -76,7 +76,7 @@ msgstr "运行参数"
#: applications/templates/applications/remote_app_list.html:22
#: applications/templates/applications/user_remote_app_list.html:18
#: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:279 assets/models/authbook.py:27
#: assets/models/asset.py:296 assets/models/authbook.py:27
#: assets/serializers/admin_user.py:23 assets/serializers/system_user.py:28
#: assets/templates/assets/admin_user_list.html:49
#: assets/templates/assets/domain_detail.html:60
......@@ -133,10 +133,10 @@ msgstr "系统用户"
#: applications/templates/applications/remote_app_list.html:20
#: applications/templates/applications/user_remote_app_list.html:16
#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146
#: assets/models/base.py:26 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:20 assets/models/domain.py:20
#: assets/models/group.py:20 assets/models/label.py:18
#: assets/templates/assets/admin_user_detail.html:56
#: assets/models/asset.py:66 assets/models/base.py:26
#: assets/models/cluster.py:18 assets/models/cmd_filter.py:20
#: assets/models/domain.py:20 assets/models/group.py:20
#: assets/models/label.py:18 assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:47
#: assets/templates/assets/cmd_filter_detail.html:61
#: assets/templates/assets/cmd_filter_list.html:24
......@@ -204,7 +204,7 @@ msgstr "参数"
#: applications/models/remote_app.py:43
#: applications/templates/applications/remote_app_detail.html:77
#: assets/models/asset.py:109 assets/models/base.py:34
#: assets/models/asset.py:126 assets/models/base.py:34
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
#: assets/models/cmd_filter.py:58 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68
......@@ -226,7 +226,7 @@ msgstr "创建者"
#: applications/models/remote_app.py:46
#: applications/templates/applications/remote_app_detail.html:73
#: assets/models/asset.py:110 assets/models/cluster.py:26
#: assets/models/asset.py:127 assets/models/cluster.py:26
#: assets/models/domain.py:23 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/admin_user.py:37
#: assets/templates/assets/admin_user_detail.html:64
......@@ -252,7 +252,7 @@ msgstr "创建日期"
#: applications/templates/applications/remote_app_detail.html:81
#: applications/templates/applications/remote_app_list.html:24
#: applications/templates/applications/user_remote_app_list.html:20
#: assets/models/asset.py:111 assets/models/base.py:31
#: assets/models/asset.py:128 assets/models/base.py:31
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
#: assets/models/cmd_filter.py:55 assets/models/domain.py:21
#: assets/models/domain.py:53 assets/models/group.py:23
......@@ -306,7 +306,7 @@ msgstr "远程应用"
#: assets/templates/assets/_system_user.html:75
#: assets/templates/assets/admin_user_create_update.html:45
#: assets/templates/assets/asset_bulk_update.html:23
#: assets/templates/assets/asset_create.html:67
#: assets/templates/assets/asset_create.html:79
#: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/cmd_filter_create_update.html:15
#: assets/templates/assets/cmd_filter_rule_create_update.html:40
......@@ -342,7 +342,7 @@ msgstr "重置"
#: assets/templates/assets/_system_user.html:76
#: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:68
#: assets/templates/assets/asset_create.html:80
#: assets/templates/assets/asset_list.html:125
#: assets/templates/assets/asset_update.html:72
#: assets/templates/assets/cmd_filter_create_update.html:16
......@@ -556,9 +556,9 @@ msgstr "连接"
#: assets/templates/assets/system_user_detail.html:22
#: assets/views/admin_user.py:29 assets/views/admin_user.py:47
#: assets/views/admin_user.py:63 assets/views/admin_user.py:78
#: assets/views/admin_user.py:102 assets/views/asset.py:53
#: assets/views/asset.py:69 assets/views/asset.py:106 assets/views/asset.py:147
#: assets/views/asset.py:164 assets/views/asset.py:188
#: assets/views/admin_user.py:102 assets/views/asset.py:52
#: assets/views/asset.py:68 assets/views/asset.py:124 assets/views/asset.py:166
#: assets/views/asset.py:183 assets/views/asset.py:207
#: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46
#: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78
#: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130
......@@ -589,7 +589,12 @@ msgstr "远程应用详情"
msgid "My RemoteApp"
msgstr "我的远程应用"
#: assets/api/asset.py:126
#: assets/api/asset.py:50
#, python-format
msgid "%(hostname)s was %(action)s successfully"
msgstr "%(hostname)s %(action)s成功"
#: assets/api/asset.py:127
msgid "Please select assets that need to be updated"
msgstr "请选择需要更新的资产"
......@@ -605,7 +610,7 @@ msgstr "更新节点资产硬件信息: {}"
msgid "Test if the assets under the node are connectable: {}"
msgstr "测试节点下资产是否可连接: {}"
#: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:133
#: assets/forms/asset.py:45 assets/models/asset.py:97 assets/models/user.py:133
#: assets/templates/assets/asset_detail.html:194
#: assets/templates/assets/asset_detail.html:202
#: assets/templates/assets/system_user_asset.html:95
......@@ -614,7 +619,7 @@ msgstr "测试节点下资产是否可连接: {}"
msgid "Nodes"
msgstr "节点管理"
#: assets/forms/asset.py:30 assets/forms/asset.py:66 assets/models/asset.py:84
#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:101
#: assets/models/cluster.py:19 assets/models/user.py:91
#: assets/templates/assets/asset_detail.html:80 templates/_nav.html:24
#: xpack/plugins/cloud/models.py:124
......@@ -623,9 +628,9 @@ msgstr "节点管理"
msgid "Admin user"
msgstr "管理用户"
#: assets/forms/asset.py:33 assets/forms/asset.py:69 assets/forms/asset.py:109
#: assets/templates/assets/asset_create.html:36
#: assets/templates/assets/asset_create.html:38
#: assets/forms/asset.py:51 assets/forms/asset.py:86 assets/forms/asset.py:125
#: assets/templates/assets/asset_create.html:48
#: assets/templates/assets/asset_create.html:50
#: assets/templates/assets/asset_list.html:93
#: assets/templates/assets/asset_update.html:41
#: assets/templates/assets/asset_update.html:43
......@@ -634,7 +639,7 @@ msgstr "管理用户"
msgid "Label"
msgstr "标签"
#: assets/forms/asset.py:37 assets/forms/asset.py:73 assets/models/asset.py:79
#: assets/forms/asset.py:54 assets/forms/asset.py:89 assets/models/asset.py:96
#: assets/models/domain.py:26 assets/models/domain.py:52
#: assets/templates/assets/asset_detail.html:84
#: assets/templates/assets/user_asset_list.html:169
......@@ -642,9 +647,9 @@ msgstr "标签"
msgid "Domain"
msgstr "网域"
#: assets/forms/asset.py:41 assets/forms/asset.py:63 assets/forms/asset.py:77
#: assets/forms/asset.py:112 assets/models/node.py:31
#: assets/templates/assets/asset_create.html:30
#: assets/forms/asset.py:58 assets/forms/asset.py:80 assets/forms/asset.py:93
#: assets/forms/asset.py:128 assets/models/node.py:31
#: assets/templates/assets/asset_create.html:42
#: assets/templates/assets/asset_update.html:35
#: perms/forms/asset_permission.py:49 perms/forms/asset_permission.py:59
#: perms/models/asset_permission.py:57
......@@ -660,7 +665,7 @@ msgstr "网域"
msgid "Node"
msgstr "节点"
#: assets/forms/asset.py:45 assets/forms/asset.py:81
#: assets/forms/asset.py:62 assets/forms/asset.py:97
msgid ""
"root or other NOPASSWD sudo privilege user existed in asset,If asset is "
"windows or other set any one, more see admin user left menu"
......@@ -668,17 +673,17 @@ msgstr ""
"root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置一"
"个, 更多信息查看左侧 `管理用户` 菜单"
#: assets/forms/asset.py:48 assets/forms/asset.py:84
#: assets/forms/asset.py:65 assets/forms/asset.py:100
msgid "Windows 2016 RDP protocol is different, If is window 2016, set it"
msgstr "Windows 2016的RDP协议与之前不同,如果是请设置"
#: assets/forms/asset.py:49 assets/forms/asset.py:85
#: assets/forms/asset.py:66 assets/forms/asset.py:101
msgid ""
"If your have some network not connect with each other, you can set domain"
msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录"
#: assets/forms/asset.py:92 assets/forms/asset.py:96 assets/forms/domain.py:17
#: assets/forms/label.py:15
#: assets/forms/asset.py:108 assets/forms/asset.py:112
#: assets/forms/domain.py:17 assets/forms/label.py:15
#: perms/templates/perms/asset_permission_asset.html:88
#: xpack/plugins/change_auth_plan/forms.py:105
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:84
......@@ -794,7 +799,17 @@ msgstr "如果选择手动登录模式,用户名和密码可以不填写"
msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig"
msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
#: assets/models/asset.py:74 assets/models/domain.py:49
#: assets/models/asset.py:67 assets/models/asset.py:92
#: assets/models/domain.py:50 assets/templates/assets/admin_user_assets.html:50
#: assets/templates/assets/asset_detail.html:72
#: assets/templates/assets/domain_gateway_list.html:69
#: assets/templates/assets/system_user_asset.html:52
#: assets/templates/assets/user_asset_list.html:164
#: settings/templates/settings/replay_storage_create.html:59
msgid "Port"
msgstr "端口"
#: assets/models/asset.py:87 assets/models/domain.py:49
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:49
#: assets/templates/assets/asset_detail.html:64
......@@ -811,7 +826,7 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
msgid "IP"
msgstr "IP"
#: assets/models/asset.py:75 assets/templates/assets/_asset_list_modal.html:45
#: assets/models/asset.py:88 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/_asset_user_auth_modal.html:9
#: assets/templates/assets/_asset_user_view_auth_modal.html:25
#: assets/templates/assets/admin_user_assets.html:48
......@@ -828,8 +843,9 @@ msgstr "IP"
msgid "Hostname"
msgstr "主机名"
#: assets/models/asset.py:76 assets/models/domain.py:51
#: assets/models/user.py:136 assets/templates/assets/asset_detail.html:76
#: assets/models/asset.py:91 assets/models/asset.py:94
#: assets/models/domain.py:51 assets/models/user.py:136
#: assets/templates/assets/asset_detail.html:76
#: assets/templates/assets/domain_gateway_list.html:70
#: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:53
......@@ -838,108 +854,98 @@ msgstr "主机名"
msgid "Protocol"
msgstr "协议"
#: assets/models/asset.py:77 assets/models/domain.py:50
#: assets/templates/assets/admin_user_assets.html:50
#: assets/templates/assets/asset_detail.html:72
#: assets/templates/assets/domain_gateway_list.html:69
#: assets/templates/assets/system_user_asset.html:52
#: assets/templates/assets/user_asset_list.html:164
#: settings/templates/settings/replay_storage_create.html:59
msgid "Port"
msgstr "端口"
#: assets/models/asset.py:78 assets/templates/assets/asset_detail.html:108
#: assets/models/asset.py:95 assets/templates/assets/asset_detail.html:108
#: assets/templates/assets/user_asset_list.html:166
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:81 assets/models/cmd_filter.py:21
#: assets/models/asset.py:98 assets/models/cmd_filter.py:21
#: assets/models/domain.py:54 assets/models/label.py:22
#: assets/templates/assets/asset_detail.html:116
#: assets/templates/assets/user_asset_list.html:170
msgid "Is active"
msgstr "激活"
#: assets/models/asset.py:87 assets/templates/assets/asset_detail.html:68
#: assets/models/asset.py:104 assets/templates/assets/asset_detail.html:68
msgid "Public IP"
msgstr "公网IP"
#: assets/models/asset.py:88 assets/templates/assets/asset_detail.html:124
#: assets/models/asset.py:105 assets/templates/assets/asset_detail.html:124
msgid "Asset number"
msgstr "资产编号"
#: assets/models/asset.py:91 assets/templates/assets/asset_detail.html:88
#: assets/models/asset.py:108 assets/templates/assets/asset_detail.html:88
msgid "Vendor"
msgstr "制造商"
#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:92
#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:92
msgid "Model"
msgstr "型号"
#: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:120
#: assets/models/asset.py:110 assets/templates/assets/asset_detail.html:120
msgid "Serial number"
msgstr "序列号"
#: assets/models/asset.py:95
#: assets/models/asset.py:112
msgid "CPU model"
msgstr "CPU型号"
#: assets/models/asset.py:96
#: assets/models/asset.py:113
#: xpack/plugins/license/templates/license/license_detail.html:80
msgid "CPU count"
msgstr "CPU数量"
#: assets/models/asset.py:97
#: assets/models/asset.py:114
msgid "CPU cores"
msgstr "CPU核数"
#: assets/models/asset.py:98
#: assets/models/asset.py:115
msgid "CPU vcpus"
msgstr "CPU总数"
#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:100
#: assets/models/asset.py:116 assets/templates/assets/asset_detail.html:100
msgid "Memory"
msgstr "内存"
#: assets/models/asset.py:100
#: assets/models/asset.py:117
msgid "Disk total"
msgstr "硬盘大小"
#: assets/models/asset.py:101
#: assets/models/asset.py:118
msgid "Disk info"
msgstr "硬盘信息"
#: assets/models/asset.py:103 assets/templates/assets/asset_detail.html:112
#: assets/models/asset.py:120 assets/templates/assets/asset_detail.html:112
#: assets/templates/assets/user_asset_list.html:167
msgid "OS"
msgstr "操作系统"
#: assets/models/asset.py:104
#: assets/models/asset.py:121
msgid "OS version"
msgstr "系统版本"
#: assets/models/asset.py:105
#: assets/models/asset.py:122
msgid "OS arch"
msgstr "系统架构"
#: assets/models/asset.py:106
#: assets/models/asset.py:123
msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:108 assets/templates/assets/asset_create.html:34
#: assets/models/asset.py:125 assets/templates/assets/asset_create.html:46
#: assets/templates/assets/asset_detail.html:231
#: assets/templates/assets/asset_update.html:39 templates/_nav.html:26
msgid "Labels"
msgstr "标签管理"
#: assets/models/asset.py:117 assets/models/base.py:38
#: assets/models/asset.py:134 assets/models/base.py:38
#: assets/serializers/admin_user.py:22 assets/serializers/system_user.py:19
#: assets/templates/assets/admin_user_list.html:51
#: assets/templates/assets/system_user_list.html:57
msgid "Unreachable"
msgstr "不可达"
#: assets/models/asset.py:118 assets/models/base.py:39
#: assets/models/asset.py:135 assets/models/base.py:39
#: assets/serializers/admin_user.py:24 assets/serializers/system_user.py:27
#: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/admin_user_list.html:50
......@@ -951,7 +957,7 @@ msgstr "不可达"
msgid "Reachable"
msgstr "可连接"
#: assets/models/asset.py:119 assets/models/base.py:40
#: assets/models/asset.py:136 assets/models/base.py:40
#: authentication/utils.py:9 xpack/plugins/license/models.py:78
msgid "Unknown"
msgstr "未知"
......@@ -1198,18 +1204,22 @@ msgstr "%(value)s is not an even number"
msgid "Date updated"
msgstr "更新日期"
#: assets/serializers/asset.py:43
#: assets/serializers/asset.py:52
msgid "Hardware info"
msgstr "硬件信息"
#: assets/serializers/asset.py:44
#: assets/serializers/asset.py:53
msgid "Connectivity"
msgstr "连接"
#: assets/serializers/asset.py:45
#: assets/serializers/asset.py:54
msgid "Org name"
msgstr "组织名"
#: assets/serializers/asset.py:70
msgid "Protocol duplicate: {}"
msgstr "协议重复: {}"
#: assets/serializers/asset_user.py:23 users/forms.py:230
#: users/models/user.py:91 users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:46
......@@ -1341,7 +1351,7 @@ msgstr "启用MFA"
msgid "Import assets"
msgstr "导入资产"
#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:54
#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:53
#: templates/_nav.html:22 xpack/plugins/change_auth_plan/views.py:110
msgid "Asset list"
msgstr "资产列表"
......@@ -1437,7 +1447,7 @@ msgid "Basic"
msgstr "基本"
#: assets/templates/assets/_system_user.html:44
#: assets/templates/assets/asset_create.html:26
#: assets/templates/assets/asset_create.html:38
#: assets/templates/assets/asset_update.html:31
#: assets/templates/assets/gateway_create_update.html:45
#: users/templates/users/_user.html:21
......@@ -1449,7 +1459,7 @@ msgid "Auto generate key"
msgstr "自动生成密钥"
#: assets/templates/assets/_system_user.html:69
#: assets/templates/assets/asset_create.html:60
#: assets/templates/assets/asset_create.html:72
#: assets/templates/assets/asset_update.html:64
#: assets/templates/assets/gateway_create_update.html:53
#: perms/templates/perms/asset_permission_create_update.html:53
......@@ -1470,7 +1480,7 @@ msgstr "更新系统用户"
#: assets/templates/assets/_user_asset_detail_modal.html:11
#: assets/templates/assets/asset_asset_user_list.html:13
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:189
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:208
msgid "Asset detail"
msgstr "资产详情"
......@@ -1611,7 +1621,7 @@ msgid "Please select file"
msgstr "选择文件"
#: assets/templates/assets/asset_asset_user_list.html:16
#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:70
#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:69
msgid "Asset user list"
msgstr "资产用户列表"
......@@ -1643,6 +1653,10 @@ msgstr "选择需要修改属性"
msgid "Select all"
msgstr "全选"
#: assets/templates/assets/asset_create.html:24
msgid "Protocols"
msgstr "协议"
#: assets/templates/assets/asset_detail.html:96
msgid "CPU"
msgstr "CPU"
......@@ -1690,7 +1704,7 @@ msgstr ""
"左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,"
"右侧是属于该节点下的资产"
#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:107
#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:125
msgid "Create asset"
msgstr "创建资产"
......@@ -2033,23 +2047,23 @@ msgstr "管理用户列表"
msgid "Admin user detail"
msgstr "管理用户详情"
#: assets/views/asset.py:81 templates/_nav_user.html:4
#: assets/views/asset.py:80 templates/_nav_user.html:4
msgid "My assets"
msgstr "我的资产"
#: assets/views/asset.py:121
#: assets/views/asset.py:140
msgid "Bulk update asset success"
msgstr "批量更新资产成功"
#: assets/views/asset.py:148
#: assets/views/asset.py:167
msgid "Bulk update asset"
msgstr "批量更新资产"
#: assets/views/asset.py:165
#: assets/views/asset.py:184
msgid "Update asset"
msgstr "更新资产"
#: assets/views/asset.py:306
#: assets/views/asset.py:325
msgid "already exists"
msgstr "已经存在"
......@@ -2597,18 +2611,35 @@ msgstr ""
msgid "Encrypt field using Secret Key"
msgstr ""
#: common/mixins.py:35
#: common/mixins.py:36
msgid "is discard"
msgstr ""
#: common/mixins.py:36
#: common/mixins.py:37
msgid "discard time"
msgstr ""
#: common/validators.py:7
#: common/mixins.py:210
#, fuzzy, python-format
msgid "%(name)s was %(action)s successfully"
msgstr "%(name)s %(action)s成功"
#: common/mixins.py:211
msgid "create"
msgstr "创建"
#: common/mixins.py:211
msgid "update"
msgstr "更新"
#: common/validators.py:11
msgid "Special char not allowed"
msgstr "不能包含特殊字符"
#: common/validators.py:23
msgid "This field must be unique."
msgstr ""
#: jumpserver/views.py:185
msgid ""
"<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
......@@ -2960,7 +2991,7 @@ msgstr "命令执行列表"
msgid "Command execution"
msgstr "命令执行"
#: orgs/mixins.py:81 orgs/models.py:24
#: orgs/mixins.py:83 orgs/models.py:24
msgid "Organization"
msgstr "组织管理"
......@@ -3161,12 +3192,12 @@ msgstr "添加用户组"
#: perms/views/asset_permission.py:33 perms/views/asset_permission.py:65
#: perms/views/asset_permission.py:80 perms/views/asset_permission.py:95
#: perms/views/asset_permission.py:130 perms/views/asset_permission.py:162
#: perms/views/remote_app_permission.py:33
#: perms/views/remote_app_permission.py:48
#: perms/views/remote_app_permission.py:63
#: perms/views/remote_app_permission.py:76
#: perms/views/remote_app_permission.py:102
#: perms/views/remote_app_permission.py:138 templates/_nav.html:39
#: perms/views/remote_app_permission.py:32
#: perms/views/remote_app_permission.py:47
#: perms/views/remote_app_permission.py:62
#: perms/views/remote_app_permission.py:75
#: perms/views/remote_app_permission.py:101
#: perms/views/remote_app_permission.py:137 templates/_nav.html:39
#: xpack/plugins/orgs/templates/orgs/org_list.html:21
msgid "Perms"
msgstr "权限管理"
......@@ -3195,27 +3226,27 @@ msgstr "资产授权用户列表"
msgid "Asset permission asset list"
msgstr "资产授权资产列表"
#: perms/views/remote_app_permission.py:34
#: perms/views/remote_app_permission.py:33
msgid "RemoteApp permission list"
msgstr "远程应用授权列表"
#: perms/views/remote_app_permission.py:49
#: perms/views/remote_app_permission.py:48
msgid "Create RemoteApp permission"
msgstr "创建远程应用授权规则"
#: perms/views/remote_app_permission.py:64
#: perms/views/remote_app_permission.py:63
msgid "Update RemoteApp permission"
msgstr "更新远程应用授权规则"
#: perms/views/remote_app_permission.py:77
#: perms/views/remote_app_permission.py:76
msgid "RemoteApp permission detail"
msgstr "远程应用授权详情"
#: perms/views/remote_app_permission.py:103
#: perms/views/remote_app_permission.py:102
msgid "RemoteApp permission user list"
msgstr "远程应用授权用户列表"
#: perms/views/remote_app_permission.py:139
#: perms/views/remote_app_permission.py:138
msgid "RemoteApp permission RemoteApp list"
msgstr "远程应用授权远程应用列表"
......@@ -3861,7 +3892,7 @@ msgid "File manager"
msgstr "文件管理"
#: templates/_nav.html:68 terminal/views/command.py:50
#: terminal/views/session.py:75 terminal/views/session.py:93
#: terminal/views/session.py:74 terminal/views/session.py:92
#: terminal/views/session.py:115 terminal/views/terminal.py:31
#: terminal/views/terminal.py:46 terminal/views/terminal.py:58
msgid "Terminal"
......@@ -4241,11 +4272,11 @@ msgstr "接受终端注册"
msgid "Info"
msgstr "信息"
#: terminal/views/session.py:76
#: terminal/views/session.py:75
msgid "Session online list"
msgstr "在线会话"
#: terminal/views/session.py:94
#: terminal/views/session.py:93
msgid "Session offline list"
msgstr "离线会话"
......
......@@ -9,8 +9,10 @@ from django.forms import ModelForm
from django.http.response import HttpResponseForbidden
from django.core.exceptions import ValidationError
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
from common.utils import get_logger
from common.validators import ProjectUniqueValidator
from .utils import (
current_org, set_current_org, set_to_root_org, get_current_org_id
)
......@@ -214,3 +216,14 @@ class OrgResourceSerializerMixin(serializers.Serializer):
(同时为serializer.is_valid()对Model的unique_together校验做准备)
"""
org_id = serializers.HiddenField(default=get_current_org_id)
def get_validators(self):
_validators = super().get_validators()
validators = []
for v in _validators:
if isinstance(v, UniqueTogetherValidator) \
and "org_id" in v.fields:
v = ProjectUniqueValidator(v.queryset, v.fields)
validators.append(v)
return validators
......@@ -3,11 +3,12 @@
from common.permissions import AdminUserRequiredMixin
from users.models import User, UserGroup
from assets.models import Asset, SystemUser, Node, RemoteApp
from assets.models import Asset, SystemUser, Node
from assets.serializers import (
AssetGrantedSerializer, NodeSerializer
)
from applications.serializers import RemoteAppSerializer
from applications.models import RemoteApp
......@@ -11,9 +11,8 @@ from django.conf import settings
from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from users.models import UserGroup
from assets.models import RemoteApp
from ..hands import RemoteApp, UserGroup
from ..models import RemoteAppPermission
from ..forms import RemoteAppPermissionCreateUpdateForm
......
......@@ -453,4 +453,16 @@ div.dataTables_wrapper div.dataTables_filter {
#tree-refresh .fa-refresh {
font: normal normal normal 14px/1 FontAwesome !important;
}
\ No newline at end of file
}
.select2-selection__rendered span.select2-selection, .select2-container .select2-selection--single, .select2-selection__arrow {
height: 34px !important;
}
.select2-selection {
border-radius: 0 !important;
}
span.select2-selection__placeholder {
line-height: 34px !important;
}
......@@ -165,11 +165,13 @@ function formSubmit(props) {
/*
{
"form": $("form"),
"data": {},
"url": "",
"method": "POST",
"redirect_to": "",
"success": function(data, textStatue, jqXHR){},
"error": function(jqXHR, textStatus, errorThrown) {}
"error": function(jqXHR, textStatus, errorThrown) {},
"message": "",
}
*/
props = props || {};
......@@ -183,6 +185,10 @@ function formSubmit(props) {
dataType: props.data_type || "json"
}).done(function (data, textState, jqXHR) {
if (redirect_to) {
if (props.message) {
var messages="ed65330a45559c87345a0eb6ac7812d18d0d8976$[[\"__json_message\"\0540\05425\054\"asdfasdf \\u521b\\u5efa\\u6210\\u529f\"]]"
setCookie("messages", messages)
}
location.href = redirect_to;
} else if (typeof props.success === 'function') {
return props.success(data, textState, jqXHR);
......@@ -230,7 +236,15 @@ function formSubmit(props) {
var help_msg = v.join("<br/>") ;
helpBlockRef.html(help_msg);
} else {
noneFieldErrorMsg += v + '<br/>';
$.each(v, function (kk, vv) {
if (typeof errors === "object") {
$.each(vv, function (kkk, vvv) {
noneFieldErrorMsg += " " + vvv + '<br/>';
})
} else{
noneFieldErrorMsg += vv + '<br/>';
}
})
}
});
if (noneFieldErrorRef.length === 1 && noneFieldErrorMsg !== '') {
......
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