mirror of https://github.com/jumpserver/jumpserver
[Change] 修改一些view
parent
c940a4c0fb
commit
b60e5a7ee3
|
@ -112,7 +112,12 @@ class Asset(models.Model):
|
|||
'groups': [group.name for group in self.groups.all()],
|
||||
'username': self.admin_user.username if self.admin_user else '',
|
||||
'password': self.admin_user.password if self.admin_user else '',
|
||||
'private_key': self.admin_user.private_key if self.admin_user else None,
|
||||
'private_key': self.admin_user.private_key_file if self.admin_user else None,
|
||||
'become': {
|
||||
'method': self.admin_user.become_method,
|
||||
'user': self.admin_user.become_user,
|
||||
'pass': self.admin_user.become_pass,
|
||||
} if self.admin_user.become else {},
|
||||
}
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import logging
|
||||
from hashlib import md5
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import signer, validate_ssh_private_key, ssh_key_string_to_obj
|
||||
|
||||
|
@ -38,7 +40,7 @@ class AdminUser(models.Model):
|
|||
become = models.BooleanField(default=True)
|
||||
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
|
||||
become_user = models.CharField(default='root', max_length=64)
|
||||
become_password = models.CharField(default='', max_length=128)
|
||||
become_pass = models.CharField(default='', max_length=128)
|
||||
_public_key = models.CharField(
|
||||
max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
@ -74,6 +76,18 @@ class AdminUser(models.Model):
|
|||
def private_key(self, private_key_raw):
|
||||
self._private_key = signer.sign(private_key_raw)
|
||||
|
||||
@property
|
||||
def private_key_file(self):
|
||||
if not self.private_key:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
key_name = md5(self._private_key).hexdigest()
|
||||
key_path = os.path.join(tmp_dir, key_name)
|
||||
if not os.path.exists(key_path):
|
||||
self.private_key.write_private_key_file(key_path)
|
||||
return key_path
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
return signer.unsign(self._public_key)
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% block content_left_head %}
|
||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
<div class="form-group" id="date">
|
||||
<div class="input-daterange input-group" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from }}">
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="username">
|
||||
<option value="">{% trans 'User' %}</option>
|
||||
{% for user in user_list %}
|
||||
<option value="{{ user }}" {% if user == username %} selected {% endif %}>{{ user }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="ip">
|
||||
<option value="">{% trans 'Asset' %}</option>
|
||||
{% for asset in asset_list %}
|
||||
<option value="{{ asset }}" {% if asset == ip %} selected {% endif %}>{{ asset }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="system_user">
|
||||
<option value="">{% trans 'System user' %}</option>
|
||||
{% for su in system_user_list %}
|
||||
<option value="{{ su }}" {% if su == system_user %} selected {% endif %}>{{ su }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="keyword" placeholder="Keyword" value="{{ keyword }}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% trans 'ID' %}</th>
|
||||
<th class="text-center">{% trans 'User' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'System user' %}</th>
|
||||
<th class="text-center">{% trans 'Terminal' %}</th>
|
||||
<th class="text-center">{% trans 'Command' %}</th>
|
||||
<th class="text-center">{% trans 'Success' %}</th>
|
||||
<th class="text-center">{% trans 'Finished' %}</th>
|
||||
<th class="text-center">{% trans 'R/M' %}</th>
|
||||
<th class="text-center">{% trans 'Date start' %}</th>
|
||||
<th class="text-center">{% trans 'Time' %}</th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_body %}
|
||||
{% for proxy_log in proxy_log_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center"><input type="checkbox" class="cbx-term" value="{{ proxy_log.id }}"></td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'audits:proxy-log-detail' pk=proxy_log.id %}">{{ proxy_log.id }}</a>
|
||||
</td>
|
||||
<td class="text-center">{{ proxy_log.user }}</td>
|
||||
<td class="text-center">{{ proxy_log.asset }}</td>
|
||||
<td class="text-center">{{ proxy_log.system_user }}</td>
|
||||
<td class="text-center">{{ proxy_log.terminal }}</td>
|
||||
<td class="text-center">{{ proxy_log.commands.all|length}}</td>
|
||||
<td class="text-center">
|
||||
{% if proxy_log.is_failed %}
|
||||
<i class="fa fa-times text-danger"></i>
|
||||
{% else %}
|
||||
<i class="fa fa-check text-navy"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if proxy_log.is_finished %}
|
||||
<td class="text-center">
|
||||
<i class="fa fa-check text-navy"></i>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a><span class="text-navy"><i class="fa fa-play-circle"></i></span></a>
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="text-center">
|
||||
<a class="btn-term" value="{{ proxy_log.id }}"><i class="fa fa-times text-danger"></i></a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a><span class="text-danger"><i class="fa fa-eye"></i></span></a>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td class="text-center">{{ proxy_log.date_start }}</td>
|
||||
<td class="text-center">{{ proxy_log.date_finished|timeuntil:proxy_log.date_start }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content_bottom_left %}
|
||||
<div id="actions">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="terminate">{% trans 'Terminate selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
function terminateConnection(data) {
|
||||
function success() {
|
||||
window.setTimeout(function () {
|
||||
window.location.reload()
|
||||
}, 300)
|
||||
}
|
||||
var the_url = "{% url 'api-applications:terminate-connection' %}";
|
||||
APIUpdateAttr({url: the_url, method: 'POST', body: JSON.stringify(data), success: success, success_message: 'Terminate success'});
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$('table').DataTable({
|
||||
"searching": false,
|
||||
"paging": false,
|
||||
"bInfo" : false,
|
||||
"order": []
|
||||
});
|
||||
$('.select2').select2();
|
||||
$('#date .input-daterange').datepicker({
|
||||
dateFormat: 'mm/dd/yy',
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
autoclose: true
|
||||
});
|
||||
}).on('click', '.btn-term', function () {
|
||||
var $this = $(this);
|
||||
var proxy_log_id = $this.attr('value');
|
||||
var data = {
|
||||
proxy_log_id: proxy_log_id
|
||||
};
|
||||
terminateConnection(data)
|
||||
}).on('click', '#btn_bulk_update', function () {
|
||||
var data = [];
|
||||
$('.cbx-term:checked').each(function () {
|
||||
data.push({proxy_log_id: $(this).attr('value')})
|
||||
});
|
||||
terminateConnection(data)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -4,14 +4,16 @@ from .. import views
|
|||
app_name = 'audits'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^proxy-log$', views.ProxyLogListView.as_view(),
|
||||
name='proxy-log-list'),
|
||||
url(r'^proxy-log/(?P<pk>\d+)$', views.ProxyLogDetailView.as_view(),
|
||||
url(r'^proxy-log-offline/$', views.ProxyLogOfflineListView.as_view(),
|
||||
name='proxy-log-offline-list'),
|
||||
url(r'^proxy-log-online/$', views.ProxyLogOnlineListView.as_view(),
|
||||
name='proxy-log-online-list'),
|
||||
url(r'^proxy-log/(?P<pk>\d+)/$', views.ProxyLogDetailView.as_view(),
|
||||
name='proxy-log-detail'),
|
||||
# url(r'^proxy-log/(?P<pk>\d+)/commands$', views.ProxyLogCommandsListView.as_view(), name='proxy-log-commands-list'),
|
||||
url(r'^command-log$', views.CommandLogListView.as_view(),
|
||||
url(r'^command-log/$', views.CommandLogListView.as_view(),
|
||||
name='command-log-list'),
|
||||
url(r'^login-log$', views.LoginLogListView.as_view(),
|
||||
url(r'^login-log/$', views.LoginLogListView.as_view(),
|
||||
name='login-log-list'),
|
||||
]
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ from audits.backends import CommandLogSerializer
|
|||
|
||||
class ProxyLogListView(AdminUserRequiredMixin, ListView):
|
||||
model = ProxyLog
|
||||
template_name = 'audits/proxy_log_list.html'
|
||||
template_name = 'audits/proxy_log_online_list.html'
|
||||
context_object_name = 'proxy_log_list'
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
keyword = user = asset = system_user = date_from_s = date_to_s = ''
|
||||
|
@ -89,6 +89,38 @@ class ProxyLogListView(AdminUserRequiredMixin, ListView):
|
|||
return super(ProxyLogListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ProxyLogOfflineListView(ProxyLogListView):
|
||||
template_name = 'audits/proxy_log_online_list.html'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(ProxyLogOfflineListView, self).get_queryset()
|
||||
queryset = queryset.filter(is_finished=True)
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'action': _('Proxy log offline list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(ProxyLogOfflineListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ProxyLogOnlineListView(ProxyLogListView):
|
||||
template_name = 'audits/proxy_log_online_list.html'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(ProxyLogOnlineListView, self).get_queryset()
|
||||
queryset = queryset.filter(is_finished=False)
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'action': _('Proxy log online list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(ProxyLogOnlineListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ProxyLogDetailView(AdminUserRequiredMixin,
|
||||
SingleObjectMixin,
|
||||
ListView):
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from collections import OrderedDict
|
||||
from six import string_types
|
||||
import base64
|
||||
import os
|
||||
|
@ -53,6 +54,7 @@ def get_object_or_none(model, **kwargs):
|
|||
|
||||
|
||||
class Signer(object):
|
||||
"""用来加密,解密,和基于时间戳的方式验证token"""
|
||||
def __init__(self, secret_key=SECRET_KEY):
|
||||
self.secret_key = secret_key
|
||||
|
||||
|
@ -330,13 +332,13 @@ def encrypt_password(password):
|
|||
return None
|
||||
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
def capacity_convert(size, expect='auto', rate=1000):
|
||||
"""
|
||||
:param cap: '100MB', '1G'
|
||||
:param size: '100MB', '1G'
|
||||
:param expect: 'K, M, G, T
|
||||
:param rate: Default 1000, may be 1024
|
||||
:return:
|
||||
"""
|
||||
rate_mapping = (
|
||||
|
|
|
@ -21,16 +21,16 @@ class JMSHost(Host):
|
|||
# 添加密码和秘钥
|
||||
if asset.get('password'):
|
||||
self.set_variable('ansible_ssh_pass', asset['password'])
|
||||
if asset.get('key'):
|
||||
if asset.get('private_key'):
|
||||
self.set_variable('ansible_ssh_private_key_file', asset['private_key'])
|
||||
|
||||
# 添加become支持
|
||||
become = asset.get("become", None)
|
||||
if become is not None:
|
||||
become = asset.get("become", False)
|
||||
if become:
|
||||
self.set_variable("ansible_become", True)
|
||||
self.set_variable("ansible_become_method", become.get('method'))
|
||||
self.set_variable("ansible_become_user", become.get('user'))
|
||||
self.set_variable("ansible_become_pass", become.get('pass'))
|
||||
self.set_variable("ansible_become_method", become.get('method', 'sudo'))
|
||||
self.set_variable("ansible_become_user", become.get('user', 'root'))
|
||||
self.set_variable("ansible_become_pass", become.get('pass', ''))
|
||||
else:
|
||||
self.set_variable("ansible_become", False)
|
||||
|
||||
|
|
|
@ -265,8 +265,10 @@ class AdHocRunner(object):
|
|||
result['success'].append(host)
|
||||
|
||||
for host, msgs in self.results_callback.result_q['dark'].items():
|
||||
msg = '\n'.join(['{}: {}'.format(msg.get('invocation', {}).get('module_name'),
|
||||
msg.get('msg', '')) for msg in msgs])
|
||||
msg = '\n'.join(['{} {}: {}'.format(
|
||||
msg.get('module_stdout', ''),
|
||||
msg.get('invocation', {}).get('module_name'),
|
||||
msg.get('msg', '')) for msg in msgs])
|
||||
result['failed'].append((host, msg))
|
||||
return result
|
||||
|
||||
|
|
|
@ -68,7 +68,17 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Is success ' %}:</td>
|
||||
{% if object.is_finished %}
|
||||
<td><b>{{ object.is_success|yesno:"Yes,No,Unkown" }}</b></td>
|
||||
{% else %}
|
||||
<td>
|
||||
<div class="progress progress-striped active">
|
||||
<div style="width: 50%" aria-valuemax="100" aria-valuemin="0" aria-valuenow="75" role="progressbar" class="progress-bar progress-bar-danger">
|
||||
<span class="sr-only">40% Complete (success)</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Assets ' %}:</td>
|
||||
|
@ -84,6 +94,31 @@
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span><b>Result</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<pre>
|
||||
{{ object.result }}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-danger">
|
||||
|
|
|
@ -18,7 +18,7 @@ logger = get_logger(__file__)
|
|||
def run_AdHoc(task_tuple, assets,
|
||||
task_name='Ansible AdHoc runner',
|
||||
task_id=None, pattern='all',
|
||||
record=True, verbose=False):
|
||||
record=True, verbose=True):
|
||||
"""
|
||||
:param task_tuple: (('module_name', 'module_args'), ('module_name', 'module_args'))
|
||||
:param assets: [asset1, asset2]
|
||||
|
@ -51,6 +51,11 @@ def run_AdHoc(task_tuple, assets,
|
|||
else:
|
||||
record = Task.objects.get(uuid=task_id)
|
||||
record.date_start = timezone.now()
|
||||
record.date_finished = None
|
||||
record.timedelta = None
|
||||
record.is_finished = False
|
||||
record.is_success = False
|
||||
record.save()
|
||||
ts_start = time.time()
|
||||
if verbose:
|
||||
logger.debug('Start runner {}'.format(task_name))
|
||||
|
@ -61,7 +66,7 @@ def run_AdHoc(task_tuple, assets,
|
|||
record.date_finished = timezone.now()
|
||||
record.is_finished = True
|
||||
if verbose:
|
||||
record.result = json.dumps(result)
|
||||
record.result = json.dumps(result, indent=4, sort_keys=True)
|
||||
record.summary = json.dumps(summary)
|
||||
record.timedelta = timedelta
|
||||
if len(summary['failed']) == 0:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
from __future__ import unicode_literals
|
||||
import time
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
|
@ -81,4 +82,5 @@ class TaskRunView(View):
|
|||
def get(self, request, *args, **kwargs):
|
||||
pk = kwargs.get(self.pk_url_kwarg)
|
||||
rerun_task.delay(pk)
|
||||
time.sleep(0.5)
|
||||
return redirect(reverse('ops:task-detail', kwargs={'pk': pk}))
|
||||
|
|
|
@ -35,10 +35,10 @@ def push_users(self, assets, users):
|
|||
('authorized_key', "user={} state=present key='{}'".format(
|
||||
user['username'], user['public_key'])),
|
||||
('lineinfile',
|
||||
"name=/etc/sudoers state=present regexp='^{0} ALL=(ALL)' "
|
||||
"dest=/etc/sudoers state=present regexp='^{0} ALL=' "
|
||||
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
|
||||
"validate='visudo -cf %s'".format(
|
||||
user['username'], user.get('sudo', '/bin/whoami')
|
||||
user['username'], user.get('sudo', '/sbin/ifconfig')
|
||||
))
|
||||
])
|
||||
task_name = 'Push user {}'.format(','.join([user['name'] for user in users]))
|
||||
|
|
|
@ -56,8 +56,11 @@
|
|||
<li id="audits">
|
||||
<a href="#"><i class="fa fa-files-o"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span></a>
|
||||
<ul class="nav nav-second-level">
|
||||
<li id="proxy-log">
|
||||
<a href="{% url 'audits:proxy-log-list' %}">{% trans 'Proxy log' %}</a>
|
||||
<li id="proxy-log-offline">
|
||||
<a href="{% url 'audits:proxy-log-offline-list' %}">{% trans 'Session online' %}</a>
|
||||
</li>
|
||||
<li id="proxy-log-online">
|
||||
<a href="{% url 'audits:proxy-log-online-list' %}">{% trans 'Session history' %}</a>
|
||||
</li>
|
||||
<li id="command-log">
|
||||
<a href="{% url 'audits:command-log-list' %}">{% trans 'Command log' %}</a>
|
||||
|
|
Loading…
Reference in New Issue