diff --git a/apps/assets/api.py b/apps/assets/api.py index 860f7111c..1fe371dcd 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -44,9 +44,11 @@ class AssetViewSet(IDInFilterMixin, BulkModelViewSet): else: assets_granted = get_user_granted_assets(self.request.user) queryset = self.queryset.filter(id__in=[asset.id for asset in assets_granted]) + cluster_id = self.request.query_params.get('cluster_id') asset_group_id = self.request.query_params.get('asset_group_id') admin_user_id = self.request.query_params.get('admin_user_id') + if cluster_id: queryset = queryset.filter(cluster__id=cluster_id) if asset_group_id: diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 2bf6df507..e41044f16 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -1,5 +1,4 @@ # coding:utf-8 -import uuid from django import forms from django.utils.translation import gettext_lazy as _ @@ -9,7 +8,6 @@ from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, logger = get_logger(__file__) -from rest_framework import serializers class AssetCreateForm(forms.ModelForm): @@ -57,11 +55,11 @@ class AssetUpdateForm(forms.ModelForm): class AssetBulkUpdateForm(forms.ModelForm): - assets = forms.MultipleChoiceField( + assets = forms.ModelMultipleChoiceField( required=True, help_text='* required', label=_('Select assets'), - choices=[(asset.id, asset.hostname) for asset in Asset.objects.all()], + queryset=Asset.objects.all(), widget=forms.SelectMultiple( attrs={ 'class': 'select2', @@ -94,10 +92,9 @@ class AssetBulkUpdateForm(forms.ModelForm): cleaned_data = {k: v for k, v in self.cleaned_data.items() if k in changed_fields} - print(cleaned_data) - assets_id = cleaned_data.pop('assets') + assets = cleaned_data.pop('assets') groups = cleaned_data.pop('groups', []) - assets = Asset.objects.filter(id__in=assets_id) + assets = Asset.objects.filter(id__in=[asset.id for asset in assets]) assets.update(**cleaned_data) if groups: for asset in assets: @@ -175,16 +172,18 @@ class AdminUserForm(forms.ModelForm): password = None if private_key: - public_key = ssh_pubkey_gen(private_key) + public_key = ssh_pubkey_gen(private_key, password=password) admin_user.set_auth(password=password, public_key=public_key, private_key=private_key) return admin_user def clean_private_key_file(self): private_key_file = self.cleaned_data['private_key_file'] + password = self.cleaned_data['password'] + if private_key_file: private_key = private_key_file.read() - if not validate_ssh_private_key(private_key): + if not validate_ssh_private_key(private_key, password): raise forms.ValidationError(_('Invalid private key')) return private_key return private_key_file diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 1bbc2ddd7..02d2724d6 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -18,10 +18,6 @@ __all__ = ['Asset'] logger = logging.getLogger(__name__) -def get_default_cluster(): - return Cluster.initial() - - class Asset(models.Model): # Todo: Move them to settings STATUS_CHOICES = ( @@ -48,7 +44,7 @@ class Asset(models.Model): hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname')) port = models.IntegerField(default=22, verbose_name=_('Port')) groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups')) - cluster = models.ForeignKey(Cluster, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('Cluster'),) + cluster = models.ForeignKey(Cluster, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('Cluster')) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),) env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),) diff --git a/apps/assets/models/group.py b/apps/assets/models/group.py index 965ceb1f3..18b0c5289 100644 --- a/apps/assets/models/group.py +++ b/apps/assets/models/group.py @@ -10,7 +10,6 @@ from django.db import models import logging from django.utils.translation import ugettext_lazy as _ -from .user import SystemUser __all__ = ['AssetGroup'] logger = logging.getLogger(__name__) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 56651b4eb..674ffa008 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -66,16 +66,15 @@ class AssetUser(models.Model): @property def private_key_file(self): - if not self.private_key: + if not self.private_key_obj: return None project_dir = settings.PROJECT_DIR tmp_dir = os.path.join(project_dir, 'tmp') key_str = signer.unsign(self._private_key) - key_name = md5(key_str.encode('utf-8')).hexdigest() + key_name = '.' + md5(key_str.encode('utf-8')).hexdigest() key_path = os.path.join(tmp_dir, key_name) if not os.path.exists(key_path): - with open(key_path, 'w') as f: - f.write(key_str) + self.private_key_obj.write_private_key_file(key_path) os.chmod(key_path, 0o400) return key_path @@ -105,7 +104,6 @@ class AssetUser(models.Model): update_fields.append('_public_key') if update_fields: - print(update_fields) self.save(update_fields=update_fields) def auto_gen_auth(self): @@ -149,7 +147,11 @@ class AdminUser(AssetUser): @property def become_pass(self): - return signer.unsign(self._become_pass) + password = signer.unsign(self._become_pass) + if password: + return password + else: + return "" @become_pass.setter def become_pass(self, password): @@ -199,7 +201,7 @@ class SystemUser(AssetUser): ('K', 'Public key'), ) cluster = models.ManyToManyField('assets.Cluster', blank=True, verbose_name=_("Cluster")) - priority = models.IntegerField(default=10, verbose_name=_("Priority")) # Todo: If user granted more priority user, default will be login as the hign + priority = models.IntegerField(default=10, verbose_name=_("Priority")) protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo')) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 09430da96..a46e697fc 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -402,12 +402,6 @@ def push_system_user_on_auth_change(sender, instance=None, update_fields=None, * push_system_user_to_cluster_assets.delay(instance, task_name) -@receiver(on_app_ready, dispatch_uid="my_unique_identifier") -def test_admin_user_on_app_ready(sender, **kwargs): - logger.debug("Receive app ready signal, test admin connectability") - test_admin_user_connectability_period.delay() - - celery_app.conf['CELERYBEAT_SCHEDULE'].update( { 'update_assets_hardware_period': { diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html index dce243c0e..cb5b082cc 100644 --- a/apps/assets/templates/assets/asset_detail.html +++ b/apps/assets/templates/assets/asset_detail.html @@ -63,7 +63,7 @@ {% trans 'Public IP' %}: - {{ asset.public_ip }} + {{ asset.public_ip|default:"" }} {% trans 'Port' %}: @@ -74,12 +74,12 @@ {% if asset.admin_user_avail %} {{ asset.admin_user_avail.name }} {% else %} - None + {% endif %} {% trans 'Remote card IP' %}: - {{ asset.remote_card_ip }} + {{ asset.remote_card_ip|default:"" }} {% trans 'Cluster' %}: @@ -87,39 +87,39 @@ {% trans 'Cabinet number' %}: - {{ asset.cabinet_no }} + {{ asset.cabinet_no|default:"" }} {% trans 'Cabinet position' %}: - {{ asset.cabinet_pos }} + {{ asset.cabinet_pos|default:"" }} {% trans 'Vendor' %}: - {{ asset.vendor }} + {{ asset.vendor|default:"" }} {% trans 'Model' %}: - {{ asset.model }} + {{ asset.model|default:"" }} {% trans 'CPU' %}: - {{ asset.cpu_model }} {{ asset.cpu_count }}*{{ asset.cpu_cores }} + {{ asset.cpu_model|default:"" }} {{ asset.cpu_count|default:"" }}*{{ asset.cpu_cores|default:"" }} {% trans 'Memory' %}: - {{ asset.memory }} + {{ asset.memory|default:"" }} {% trans 'Disk' %}: - {{ asset.disk_total }} + {{ asset.disk_total|default:"" }} {% trans 'Platform' %}: - {{ asset.platform }} + {{ asset.platform|default:"" }} {% trans 'OS' %}: - {{ asset.os }} {{ asset.os_version }} {{ asset.os_arch }} + {{ asset.os|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:"" }} {% trans 'Asset status' %}: @@ -127,7 +127,7 @@ {% trans 'Is active' %}: - {{ asset.is_active }} + {{ asset.is_active|yesno:"Yes,No" }} {% trans 'Asset type' %}: @@ -139,11 +139,11 @@ {% trans 'Serial number' %}: - {{ asset.sn }} + {{ asset.sn|default:"" }} {% trans 'Asset number' %}: - {{ asset.number }} + {{ asset.number|default:"" }} {% trans 'Created by' %}: diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index d1475906d..fa7638879 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -64,8 +64,7 @@ {% endblock %} + {% block table_search %}{% endblock %} {% block table_container %}
diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html index 2580b3b7d..5ad20a57f 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -1,6 +1,13 @@ {% extends '_base_list.html' %} {% load i18n %} +{% block help_message %} +
+ 系统用户是 用户登录资产(服务器)时使用的用户,如 web, sa, dba等具有特殊功能的用户。系统用户创建时,如果选择了自动推送 + Jumpserver会使用ansible自动推送到系统用户所在集群的资产中,如果资产(交换机)不支持ansible, 请手动填写账号密码。 +
+{% endblock %} + {% block table_search %} {% endblock %} diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html index 0b137a961..4278ffa4e 100644 --- a/apps/assets/templates/assets/user_asset_list.html +++ b/apps/assets/templates/assets/user_asset_list.html @@ -5,26 +5,22 @@ - {% endblock %} {% block content_left_head %}{% endblock %} {% block table_search %} -{#
#} -{# #} -{#
#} +
+ +
{% endblock %} - {% block table_container %} @@ -36,8 +32,8 @@ - - + + @@ -48,216 +44,45 @@ {% block custom_foot_js %} diff --git a/apps/assets/utils.py b/apps/assets/utils.py index e21a2120e..ab63bbafc 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -1,5 +1,6 @@ # ~*~ coding: utf-8 ~*~ # +from collections import defaultdict from common.utils import get_object_or_none from .models import Asset, SystemUser @@ -17,3 +18,12 @@ def get_system_user_by_name(name): return system_user +def check_assets_have_system_user(assets, system_users): + errors = defaultdict(list) + + for system_user in system_users: + clusters = system_user.cluster.all() + for asset in assets: + if asset.cluster not in clusters: + errors[asset].append(system_user) + return errors diff --git a/apps/common/models.py b/apps/common/models.py index bd4b2abe9..beeb30826 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -1,5 +1,2 @@ -from __future__ import unicode_literals - from django.db import models -# Create your models here. diff --git a/apps/common/utils.py b/apps/common/utils.py index 236ae2e02..e7b07860b 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -195,11 +195,11 @@ def ssh_key_string_to_obj(text, password=None): return key -def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost'): +def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None): if isinstance(private_key, bytes): private_key = private_key.decode("utf-8") if isinstance(private_key, string_types): - private_key = ssh_key_string_to_obj(private_key) + private_key = ssh_key_string_to_obj(private_key, password=password) if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)): raise IOError('Invalid private key') @@ -238,14 +238,14 @@ def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', h raise IOError('These is error when generate ssh key.') -def validate_ssh_private_key(text): +def validate_ssh_private_key(text, password=None): if isinstance(text, bytes): try: text = text.decode("utf-8") except UnicodeDecodeError: return False - key = ssh_key_string_to_obj(text) + key = ssh_key_string_to_obj(text, password=password) if key is None: return False else: diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 783c29710..e8b2d19df 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -59,7 +59,6 @@ INSTALLED_APPS = [ 'assets.apps.AssetsConfig', 'perms.apps.PermsConfig', 'ops.apps.OpsConfig', - # 'audits.apps.AuditsConfig', 'common.apps.CommonConfig', 'terminal.apps.TerminalConfig', 'rest_framework', diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 37df92beb..810b14c51 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -1,7 +1,5 @@ # ~*~ coding: utf-8 ~*~ -from collections import defaultdict - from ansible.plugins.callback import CallbackBase from ansible.plugins.callback.default import CallbackModule @@ -21,9 +19,8 @@ class AdHocResultCallback(CallbackModule): # "contacted": {"hostname",...}, # "dark": {"hostname": {"task_name": {}, "task_name": {}},...,}, # } - self.results_raw = dict(ok=defaultdict(dict), failed=defaultdict(dict), - unreachable=defaultdict(dict), skipped=defaultdict(dict)) - self.results_summary = dict(contacted=[], dark=defaultdict(dict)) + self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={}) + self.results_summary = dict(contacted=[], dark={}) super().__init__() def gather_result(self, t, res): @@ -34,8 +31,8 @@ class AdHocResultCallback(CallbackModule): if self.results_raw[t].get(host): self.results_raw[t][host][task_name] = task_result - # else: - # self.results_raw[t][host] = {task_name: task_result} + else: + self.results_raw[t][host] = {task_name: task_result} self.clean_result(t, host, task_name, task_result) def clean_result(self, t, host, task_name, task_result): @@ -45,10 +42,10 @@ class AdHocResultCallback(CallbackModule): if host not in contacted: contacted.append(host) else: - # if dark.get(host): - dark[host][task_name] = task_result.values - # else: - # dark[host] = {task_name: task_result} + if dark.get(host): + dark[host][task_name] = task_result.values + else: + dark[host] = {task_name: task_result} if host in contacted: contacted.remove(host) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index d3d342368..94dfaa984 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -32,6 +32,7 @@ class BaseHost(Host): } """ self.host_data = host_data + print(host_data) hostname = host_data.get('hostname') or host_data.get('ip') port = host_data.get('port') or 22 super().__init__(hostname, port) diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html index c08fad156..503f1046a 100644 --- a/apps/ops/templates/ops/task_list.html +++ b/apps/ops/templates/ops/task_list.html @@ -27,7 +27,7 @@
@@ -37,7 +37,7 @@ {% block table_head %} - + diff --git a/apps/ops/utils.py b/apps/ops/utils.py index a79e10aa7..994e81db0 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -1,8 +1,8 @@ # ~*~ coding: utf-8 ~*~ -import re import time from django.utils import timezone +from django.db import transaction from common.utils import get_logger, get_object_or_none, get_short_uuid_str from .ansible import AdHocRunner, CommandResultCallback @@ -58,6 +58,7 @@ def get_inventory(hostname_list, run_as_admin=False, run_as=None, become_info=No ) +@record_adhoc def get_adhoc_runner(hostname_list, run_as_admin=False, run_as=None, become_info=None): inventory = get_inventory( hostname_list, run_as_admin=run_as_admin, @@ -67,7 +68,6 @@ def get_adhoc_runner(hostname_list, run_as_admin=False, run_as=None, become_info return runner -@record_adhoc def run_adhoc_object(adhoc, **options): """ :param adhoc: Instance of AdHoc @@ -109,6 +109,8 @@ def create_or_update_task( run_as_admin=False, run_as="", become_info=None, created_by=None ): + print(options) + print(task_name) task = get_object_or_none(Task, name=task_name) if task is None: task = Task(name=task_name, created_by=created_by) @@ -125,6 +127,7 @@ def create_or_update_task( if not adhoc or adhoc != new_adhoc: new_adhoc.save() task.latest_adhoc = new_adhoc + print("Return task") return task diff --git a/apps/perms/forms.py b/apps/perms/forms.py index f6f41f624..c4c4ff0c9 100644 --- a/apps/perms/forms.py +++ b/apps/perms/forms.py @@ -38,3 +38,34 @@ class AssetPermissionForm(forms.ModelForm): 'user_groups': _('User or user group at least one required'), 'asset_groups': _('Asset or Asset group at least one required'), } + + def clean_system_users(self): + from assets.utils import check_assets_have_system_user + + errors = [] + assets = self.cleaned_data['assets'] + asset_groups = self.cleaned_data['asset_groups'] + system_users = self.cleaned_data['system_users'] + + error_data = check_assets_have_system_user(assets, system_users) + if error_data: + for asset, system_users in error_data.items(): + msg = _("Asset {} not have [{}] system users, please check \n") + error = forms.ValidationError(msg.format( + asset.hostname, + ", ".join(system_user.name for system_user in system_users) + )) + errors.append(error) + + for group in asset_groups: + msg = _("Asset {}: {} not have [{}] system users, please check") + assets = group.assets.all() + error_data = check_assets_have_system_user(assets, system_users) + for asset, system_users in error_data.items(): + errors.append(msg.format( + group.name, asset.hostname, + ", ".join(system_user.name for system_user in system_users) + )) + if errors: + raise forms.ValidationError(errors) + return self.cleaned_data['system_users'] diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html index b8c2a60f0..86a11ba62 100644 --- a/apps/perms/templates/perms/asset_permission_create_update.html +++ b/apps/perms/templates/perms/asset_permission_create_update.html @@ -55,7 +55,7 @@
- +
{{ form.date_expired.errors }}
diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html index 71861702b..da4c88f80 100644 --- a/apps/perms/templates/perms/asset_permission_list.html +++ b/apps/perms/templates/perms/asset_permission_list.html @@ -4,6 +4,12 @@ {% block table_search %} {% endblock %} +{% block help_message %} +
+ 提前规划好集群中的系统用户,授权时选择的资产(组内资产)必须存在该系统用户,否则可能无法成功登录 +
+{% endblock %} + {% block table_container %}
diff --git a/apps/perms/views.py b/apps/perms/views.py index 2ad929c4b..d1c44a46a 100644 --- a/apps/perms/views.py +++ b/apps/perms/views.py @@ -66,9 +66,7 @@ class MessageMixin: return success_message -class AssetPermissionCreateView(AdminUserRequiredMixin, - MessageMixin, - CreateView): +class AssetPermissionCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): model = AssetPermission form_class = AssetPermissionForm template_name = 'perms/asset_permission_create_update.html' @@ -83,8 +81,19 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, kwargs.update(context) return super().get_context_data(**kwargs) + def get_success_message(self, cleaned_data): + url = reverse_lazy( + 'perms:asset-permission-detail', + kwargs={'pk': self.object.pk} + ) + success_message = _( + 'Create asset permission {name} ' + 'success.'.format(url=url, name=self.object.name) + ) + return success_message -class AssetPermissionUpdateView(AdminUserRequiredMixin, MessageMixin, UpdateView): + +class AssetPermissionUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = AssetPermission form_class = AssetPermissionForm template_name = 'perms/asset_permission_create_update.html' @@ -98,6 +107,17 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, MessageMixin, UpdateView kwargs.update(context) return super().get_context_data(**kwargs) + def get_success_message(self, cleaned_data): + url = reverse_lazy( + 'perms:asset-permission-detail', + kwargs={'pk': self.object.pk} + ) + success_message = _( + 'Update asset permission {name} ' + 'success.'.format(url=url, name=self.object.name) + ) + return success_message + class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView): template_name = 'perms/asset_permission_detail.html' diff --git a/apps/media/avatar/default.png b/apps/static/img/avatar/admin.png similarity index 100% rename from apps/media/avatar/default.png rename to apps/static/img/avatar/admin.png diff --git a/apps/static/img/avatar/user.png b/apps/static/img/avatar/user.png new file mode 100644 index 000000000..0c7d8fb74 Binary files /dev/null and b/apps/static/img/avatar/user.png differ diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index cb7f1b098..04b081dcb 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -156,8 +156,8 @@ function activeNav() { function APIUpdateAttr(props) { // props = {url: .., body: , success: , error: , method: ,} props = props || {}; - var success_message = props.success_message || 'Update successfully!'; - var fail_message = props.fail_message || 'Error occurred while updating.'; + var success_message = props.success_message || '更新成功!'; + var fail_message = props.fail_message || '更新时发生未知错误.'; $.ajax({ url: props.url, type: props.method || "PATCH", @@ -183,7 +183,7 @@ function objectDelete(obj, name, url, redirectTo) { function doDelete() { var body = {}; var success = function() { - swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success"); + // swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success"); if (!redirectTo) { $(obj).parent().parent().remove(); } else { @@ -191,7 +191,7 @@ function objectDelete(obj, name, url, redirectTo) { } }; var fail = function() { - swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error"); + swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error"); }; APIUpdateAttr({ url: url, @@ -202,14 +202,14 @@ function objectDelete(obj, name, url, redirectTo) { }); } swal({ - title: 'Are you sure delete ?', + title: '你确定删除吗 ?', text: " [" + name + "] ", type: "warning", showCancelButton: true, - cancelButtonText: 'Cancel', - confirmButtonColor: "#DD6B55", - confirmButtonText: 'Confirm', - closeOnConfirm: false + cancelButtonText: '取消', + confirmButtonColor: "#ed5565", + confirmButtonText: '确认', + closeOnConfirm: true, }, function () { doDelete() }); @@ -334,3 +334,9 @@ String.prototype.format = function(args) { } return result; }; + +function setCookie(key, value) { + var expires = new Date(); + expires.setTime(expires.getTime() + (24 * 60 * 60 * 1000)); + document.cookie = key + '=' + value + ';expires=' + expires.toUTCString(); +} diff --git a/apps/templates/_left_side_bar.html b/apps/templates/_left_side_bar.html index 0d58e79a2..04aa89c3c 100644 --- a/apps/templates/_left_side_bar.html +++ b/apps/templates/_left_side_bar.html @@ -2,7 +2,7 @@ @@ -26,3 +33,15 @@ JMS
+ diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py index 87a90bfcb..ed404a54b 100644 --- a/apps/terminal/tasks.py +++ b/apps/terminal/tasks.py @@ -1,13 +1,7 @@ # -*- coding: utf-8 -*- # -import time from celery import shared_task -from django.core.cache import cache -from django.db.utils import ProgrammingError, OperationalError - -from .models import Session - CACHE_REFRESH_INTERVAL = 10 diff --git a/apps/terminal/templates/terminal/command_list.html b/apps/terminal/templates/terminal/command_list.html index 3291b53ea..20e7b1bcf 100644 --- a/apps/terminal/templates/terminal/command_list.html +++ b/apps/terminal/templates/terminal/command_list.html @@ -13,7 +13,6 @@ {% endblock %} {% block content_left_head %} - 123 {% endblock %} {% block table_search %} @@ -51,12 +50,12 @@
- +
diff --git a/apps/terminal/templates/terminal/terminal_list.html b/apps/terminal/templates/terminal/terminal_list.html index 966edae22..3784806ce 100644 --- a/apps/terminal/templates/terminal/terminal_list.html +++ b/apps/terminal/templates/terminal/terminal_list.html @@ -78,14 +78,8 @@ function initTable() { var reject_btn = '{% trans "Reject" %}' .replace('{{ DEFAULT_PK }}', cellData) .replace('99991938', rowData.name); - var connect_btn = '{% trans "Connect" %} ' - .replace('{{ DEFAULT_PK }}', cellData); if (rowData.is_accepted) { - {% if user.is_superuser %} - $(td).html(connect_btn + update_btn + delete_btn); - {% else %} - $(td).html(connect_btn); - {% endif %} + $(td).html(update_btn + delete_btn); } else { {% if user.is_superuser %} $(td).html(accept_btn + reject_btn); diff --git a/apps/users/forms.py b/apps/users/forms.py index c2cc8c8df..48021ec74 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -67,15 +67,23 @@ class UserProfileForm(forms.ModelForm): class UserPasswordForm(forms.Form): old_password = forms.CharField( - max_length=128, widget=forms.PasswordInput) + max_length=128, widget=forms.PasswordInput, + label=_("Old password") + ) new_password = forms.CharField( - min_length=5, max_length=128, widget=forms.PasswordInput) + min_length=5, max_length=128, + widget=forms.PasswordInput, + label=_("New password") + ) confirm_password = forms.CharField( - min_length=5, max_length=128, widget=forms.PasswordInput) + min_length=5, max_length=128, + widget=forms.PasswordInput, + label=_("Confirm password") + ) def __init__(self, *args, **kwargs): self.instance = kwargs.pop('instance') - super(UserPasswordForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_old_password(self): old_password = self.cleaned_data['old_password'] @@ -102,20 +110,21 @@ class UserPublicKeyForm(forms.Form): public_key = forms.CharField( label=_('ssh public key'), max_length=5000, widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}), - help_text=_('Paste your id_rsa.pub here.')) + help_text=_('Paste your id_rsa.pub here.') + ) def __init__(self, *args, **kwargs): if 'instance' in kwargs: self.instance = kwargs.pop('instance') else: self.instance = None - super(UserPublicKeyForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_public_key(self): public_key = self.cleaned_data['public_key'] if self.instance.public_key and public_key == self.instance.public_key: - raise forms.ValidationError(_('Public key should not be the ' - 'same as your old one.')) + msg = _('Public key should not be the same as your old one.') + raise forms.ValidationError(msg) if not validate_ssh_public_key(public_key): raise forms.ValidationError(_('Not a valid ssh public key')) @@ -129,11 +138,11 @@ class UserPublicKeyForm(forms.Form): class UserBulkUpdateForm(forms.ModelForm): - users = forms.MultipleChoiceField( + users = forms.ModelMultipleChoiceField( required=True, help_text='* required', label=_('Select users'), - choices=[(user.id, user.name) for user in User.objects.all()], + queryset=User.objects.all(), widget=forms.SelectMultiple( attrs={ 'class': 'select2', @@ -162,9 +171,9 @@ class UserBulkUpdateForm(forms.ModelForm): cleaned_data = {k: v for k, v in self.cleaned_data.items() if k in changed_fields} - users_id = cleaned_data.pop('users', '') + users = cleaned_data.pop('users', '') groups = cleaned_data.pop('groups', []) - users = User.objects.filter(id__in=users_id) + users = User.objects.filter(id__in=[user.id for user in users]) users.update(**cleaned_data) if groups: for user in users: diff --git a/apps/users/models/user.py b/apps/users/models/user.py index dda2be521..81faccf6c 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -146,7 +146,7 @@ class User(AbstractUser): if not self.name: self.name = self.username - super(User, self).save(*args, **kwargs) + super().save(*args, **kwargs) # Add the current user to the default group. if not self.groups.count(): group = UserGroup.initial() @@ -180,13 +180,14 @@ class User(AbstractUser): return False def avatar_url(self): + admin_default = settings.STATIC_URL + "img/avatar/admin.png" + user_default = settings.STATIC_URL + "img/avatar/user.png" if self.avatar: return self.avatar.url + if self.is_superuser: + return admin_default else: - avatar_dir = os.path.join(settings.MEDIA_ROOT, 'avatar') - if os.path.isdir(avatar_dir): - return os.path.join(settings.MEDIA_URL, 'avatar', 'default.png') - return 'https://www.gravatar.com/avatar/c6812ab450230979465d7bf288eadce2a?s=120&d=identicon' + return user_default def generate_reset_token(self): return signer.sign_t({'reset': str(self.id), 'email': self.email}, expires_in=3600) diff --git a/apps/users/templates/users/login_log_list.html b/apps/users/templates/users/login_log_list.html index 8d2859b45..7ac9fe4fc 100644 --- a/apps/users/templates/users/login_log_list.html +++ b/apps/users/templates/users/login_log_list.html @@ -31,12 +31,12 @@
- +
@@ -85,7 +85,9 @@ forceParse: false, autoclose: true }); - $('.select2').select2(); + $('.select2').select2({ + dropdownAutoWidth: true + }); }) {% endblock %} diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index 177819e67..7612b1dfd 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -73,7 +73,7 @@ function initTable() { } }}, {targets: 6, createdCell: function (td, cellData, rowData) { - var update_btn = '{% trans "Update" %}'.replace('00000000-0000-0000-0000-000000000000', cellData); + var update_btn = '{% trans "Update" %}'.replace('00000000-0000-0000-0000-000000000000', cellData); var del_btn = ""; if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ user.username }}") {
{% trans 'Type' %} {% trans 'Env' %} {% trans 'Hardware' %}{% trans 'Valid' %}{% trans 'Alive' %}{% trans 'Active' %}{% trans 'Connective' %}
{% trans 'Name' %}{% trans 'F/S/T' %}{% trans 'Run times' %} {% trans 'Versions' %} {% trans 'Hosts' %} {% trans 'Success' %}