[Change] 修改一些view

pull/417/head
ibuler 2017-03-24 00:27:33 +08:00
parent c940a4c0fb
commit b60e5a7ee3
15 changed files with 300 additions and 25 deletions

View File

@ -112,7 +112,12 @@ class Asset(models.Model):
'groups': [group.name for group in self.groups.all()], 'groups': [group.name for group in self.groups.all()],
'username': self.admin_user.username if self.admin_user else '', 'username': self.admin_user.username if self.admin_user else '',
'password': self.admin_user.password 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: class Meta:

View File

@ -3,12 +3,14 @@
# #
from __future__ import unicode_literals from __future__ import unicode_literals
import os
import logging import logging
from hashlib import md5
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ 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 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 = models.BooleanField(default=True)
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4) become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64) 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( _public_key = models.CharField(
max_length=4096, blank=True, verbose_name=_('SSH public key')) max_length=4096, blank=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
@ -74,6 +76,18 @@ class AdminUser(models.Model):
def private_key(self, private_key_raw): def private_key(self, private_key_raw):
self._private_key = signer.sign(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 @property
def public_key(self): def public_key(self):
return signer.unsign(self._public_key) return signer.unsign(self._public_key)

View File

@ -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 %}

View File

@ -4,14 +4,16 @@ from .. import views
app_name = 'audits' app_name = 'audits'
urlpatterns = [ urlpatterns = [
url(r'^proxy-log$', views.ProxyLogListView.as_view(), url(r'^proxy-log-offline/$', views.ProxyLogOfflineListView.as_view(),
name='proxy-log-list'), name='proxy-log-offline-list'),
url(r'^proxy-log/(?P<pk>\d+)$', views.ProxyLogDetailView.as_view(), 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'), name='proxy-log-detail'),
# url(r'^proxy-log/(?P<pk>\d+)/commands$', views.ProxyLogCommandsListView.as_view(), name='proxy-log-commands-list'), # 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'), name='command-log-list'),
url(r'^login-log$', views.LoginLogListView.as_view(), url(r'^login-log/$', views.LoginLogListView.as_view(),
name='login-log-list'), name='login-log-list'),
] ]

View File

@ -22,7 +22,7 @@ from audits.backends import CommandLogSerializer
class ProxyLogListView(AdminUserRequiredMixin, ListView): class ProxyLogListView(AdminUserRequiredMixin, ListView):
model = ProxyLog model = ProxyLog
template_name = 'audits/proxy_log_list.html' template_name = 'audits/proxy_log_online_list.html'
context_object_name = 'proxy_log_list' context_object_name = 'proxy_log_list'
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
keyword = user = asset = system_user = date_from_s = date_to_s = '' 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) 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, class ProxyLogDetailView(AdminUserRequiredMixin,
SingleObjectMixin, SingleObjectMixin,
ListView): ListView):

View File

@ -2,6 +2,7 @@
# #
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import OrderedDict
from six import string_types from six import string_types
import base64 import base64
import os import os
@ -53,6 +54,7 @@ def get_object_or_none(model, **kwargs):
class Signer(object): class Signer(object):
"""用来加密,解密,和基于时间戳的方式验证token"""
def __init__(self, secret_key=SECRET_KEY): def __init__(self, secret_key=SECRET_KEY):
self.secret_key = secret_key self.secret_key = secret_key
@ -330,13 +332,13 @@ def encrypt_password(password):
return None return None
from collections import OrderedDict
def capacity_convert(size, expect='auto', rate=1000): def capacity_convert(size, expect='auto', rate=1000):
""" """
:param cap: '100MB', '1G' :param size: '100MB', '1G'
:param expect: 'K, M, G, T :param expect: 'K, M, G, T
:param rate: Default 1000, may be 1024
:return: :return:
""" """
rate_mapping = ( rate_mapping = (

View File

@ -21,16 +21,16 @@ class JMSHost(Host):
# 添加密码和秘钥 # 添加密码和秘钥
if asset.get('password'): if asset.get('password'):
self.set_variable('ansible_ssh_pass', asset['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']) self.set_variable('ansible_ssh_private_key_file', asset['private_key'])
# 添加become支持 # 添加become支持
become = asset.get("become", None) become = asset.get("become", False)
if become is not None: if become:
self.set_variable("ansible_become", True) self.set_variable("ansible_become", True)
self.set_variable("ansible_become_method", become.get('method')) self.set_variable("ansible_become_method", become.get('method', 'sudo'))
self.set_variable("ansible_become_user", become.get('user')) self.set_variable("ansible_become_user", become.get('user', 'root'))
self.set_variable("ansible_become_pass", become.get('pass')) self.set_variable("ansible_become_pass", become.get('pass', ''))
else: else:
self.set_variable("ansible_become", False) self.set_variable("ansible_become", False)

View File

@ -265,8 +265,10 @@ class AdHocRunner(object):
result['success'].append(host) result['success'].append(host)
for host, msgs in self.results_callback.result_q['dark'].items(): for host, msgs in self.results_callback.result_q['dark'].items():
msg = '\n'.join(['{}: {}'.format(msg.get('invocation', {}).get('module_name'), msg = '\n'.join(['{} {}: {}'.format(
msg.get('msg', '')) for msg in msgs]) msg.get('module_stdout', ''),
msg.get('invocation', {}).get('module_name'),
msg.get('msg', '')) for msg in msgs])
result['failed'].append((host, msg)) result['failed'].append((host, msg))
return result return result

View File

@ -68,7 +68,17 @@
</tr> </tr>
<tr> <tr>
<td>{% trans 'Is success ' %}:</td> <td>{% trans 'Is success ' %}:</td>
{% if object.is_finished %}
<td><b>{{ object.is_success|yesno:"Yes,No,Unkown" }}</b></td> <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>
<tr> <tr>
<td>{% trans 'Assets ' %}:</td> <td>{% trans 'Assets ' %}:</td>
@ -84,6 +94,31 @@
</table> </table>
</div> </div>
</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>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0"> <div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-danger"> <div class="panel panel-danger">

View File

@ -18,7 +18,7 @@ logger = get_logger(__file__)
def run_AdHoc(task_tuple, assets, def run_AdHoc(task_tuple, assets,
task_name='Ansible AdHoc runner', task_name='Ansible AdHoc runner',
task_id=None, pattern='all', 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 task_tuple: (('module_name', 'module_args'), ('module_name', 'module_args'))
:param assets: [asset1, asset2] :param assets: [asset1, asset2]
@ -51,6 +51,11 @@ def run_AdHoc(task_tuple, assets,
else: else:
record = Task.objects.get(uuid=task_id) record = Task.objects.get(uuid=task_id)
record.date_start = timezone.now() 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() ts_start = time.time()
if verbose: if verbose:
logger.debug('Start runner {}'.format(task_name)) logger.debug('Start runner {}'.format(task_name))
@ -61,7 +66,7 @@ def run_AdHoc(task_tuple, assets,
record.date_finished = timezone.now() record.date_finished = timezone.now()
record.is_finished = True record.is_finished = True
if verbose: if verbose:
record.result = json.dumps(result) record.result = json.dumps(result, indent=4, sort_keys=True)
record.summary = json.dumps(summary) record.summary = json.dumps(summary)
record.timedelta = timedelta record.timedelta = timedelta
if len(summary['failed']) == 0: if len(summary['failed']) == 0:

View File

@ -1,5 +1,6 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
import time
import json import json
from datetime import datetime from datetime import datetime
@ -81,4 +82,5 @@ class TaskRunView(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
pk = kwargs.get(self.pk_url_kwarg) pk = kwargs.get(self.pk_url_kwarg)
rerun_task.delay(pk) rerun_task.delay(pk)
time.sleep(0.5)
return redirect(reverse('ops:task-detail', kwargs={'pk': pk})) return redirect(reverse('ops:task-detail', kwargs={'pk': pk}))

View File

@ -35,10 +35,10 @@ def push_users(self, assets, users):
('authorized_key', "user={} state=present key='{}'".format( ('authorized_key', "user={} state=present key='{}'".format(
user['username'], user['public_key'])), user['username'], user['public_key'])),
('lineinfile', ('lineinfile',
"name=/etc/sudoers state=present regexp='^{0} ALL=(ALL)' " "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' " "line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf %s'".format( "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])) task_name = 'Push user {}'.format(','.join([user['name'] for user in users]))

View File

@ -56,8 +56,11 @@
<li id="audits"> <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> <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"> <ul class="nav nav-second-level">
<li id="proxy-log"> <li id="proxy-log-offline">
<a href="{% url 'audits:proxy-log-list' %}">{% trans 'Proxy log' %}</a> <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>
<li id="command-log"> <li id="command-log">
<a href="{% url 'audits:command-log-list' %}">{% trans 'Command log' %}</a> <a href="{% url 'audits:command-log-list' %}">{% trans 'Command log' %}</a>

0
tmp/.gitkeep Normal file
View File