一、实际生产的架构图
1、生产环境为什么要这样干
- 解耦
- 异步
2、常用的queue软件
- redis, rabbitmq
- github ,
- restful API
- celery
二、我们今天如何实现?
1、实现思路
问题:views和web之间已返回,线程这里就断开了,那是因为你用django又启了一个线程
怎样才启动一个独立的进程,和django是没有关系,只不过是用django启动的,由操作系统来管理
三、目录结构
四、代码实现
1、backend
1、main
import subprocess
from web import models
from django.contrib.auth import authenticate
import random,string,uuid
class HostManager(object):
"""用户登陆堡垒机后的交互程序"""
def __init__(self):
self.user = None
def get_session_id(self,bind_host_obj,tag):
'''apply session id'''
session_obj = models.Session(user_id = self.user.id,bind_host=bind_host_obj,tag=tag)
session_obj.save()
return session_obj
def interactive(self):
"""交互脚本"""
print("----run---------")
count = 0
while count <3:
username = input("Username:").strip()
password = input("Password:").strip()
user = authenticate(username=username,password=password)
if user:
print("Welcome %s".center(50,'-') % user.name )
self.user = user
break
else:
print("Wrong username or password!")
count += 1
else:
exit("Too many attempts, bye.")
if self.user: #验证成功
while True:
for index,host_group in enumerate(self.user.host_groups.all()): #select_related()
print("%s.\t%s[%s]" %(index,host_group.name, host_group.bind_hosts.count()))
print("z.\t未分组主机[%s]" %(self.user.bind_hosts.count()))
choice = input("%s>>:"% self.user.name).strip()
if len(choice) == 0:continue
selected_host_group = None
if choice.isdigit():
choice = int(choice)
if choice >=0 and choice <= index: #合法选项
selected_host_group = self.user.host_groups.all()[choice]
elif choice == 'z':
selected_host_group = self.user
if selected_host_group:
print("selected host group", selected_host_group)
while True:
for index, bind_host in enumerate(selected_host_group.bind_hosts.all()):
print("%s.\t%s" % (index, bind_host))
choice = choice = input("%s>>>:" % self.user.name).strip()
if choice.isdigit():
choice = int(choice)
if choice >= 0 and choice <= index: # 合法选项
print("going to logon ....", selected_host_group.bind_hosts.all()[choice])
bind_host = selected_host_group.bind_hosts.all()[choice]
ssh_tag = uuid.uuid4()
session_obj = self.get_session_id(bind_host,ssh_tag)
monitor_script = subprocess.Popen("sh /opt/CrazyEye/backend/session_tracker.sh %s %s" % (ssh_tag,session_obj.id),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
#print(monitor_script.stderr.read())
subprocess.run('sshpass -p %s ssh %s@%s -E %s -o StrictHostKeyChecking=no' %(bind_host.remote_user.password,
bind_host.remote_user.username,
bind_host.host.ip_addr ,ssh_tag ), shell=True)
elif choice == 'b':
break
2、task_manager
import json,os ,subprocess
from django import conf
from web import models
class MultiTaskManger(object):
"""负责解析并触发批量任务"""
def __init__(self,request):
self.request = request
self.call_task()
def task_parser(self):
"""解析任务"""
self.task_data = json.loads(self.request.POST.get("task_data"))
def call_task(self):
self.task_parser()
if self.task_data['task_type'] == 0:#cmd
self.cmd_task()
elif self.task_data['task_type'] == 1:#file transfer
self.file_transfer_task()
def cmd_task(self):
"""
1.生产任务id
2.触发任务
3.返回任务id
:return:
"""
task_obj = models.Task.objects.create(user=self.request.user,
task_type=self.task_data['task_type'],
content = self.task_data["cmd"])
sub_task_objs = []
for host_id in self.task_data['selected_host_ids'] :
sub_task_objs.append(models.TaskLogDetail(task=task_obj,bind_host_id=host_id,result='init...',status=2))
models.TaskLogDetail.objects.bulk_create(sub_task_objs)
task_script_obj = subprocess.Popen("python %s %s" %(conf.settings.MULTITASK_SCRIPT,task_obj.id),
shell=True,stdout=subprocess.PIPE)
self.task = task_obj
def file_transfer_task(self):
"""
1.生产任务记录
2.触发任务
3. 返回任务id
:return:
"""
task_obj = models.Task.objects.create(user=self.request.user,
task_type=self.task_data['task_type'],
content=json.dumps(self.task_data))
sub_task_objs = []
for host_id in self.task_data['selected_host_ids']:
sub_task_objs.append(models.TaskLogDetail(task=task_obj, bind_host_id=host_id, result='init...', status=2))
models.TaskLogDetail.objects.bulk_create(sub_task_objs)
task_script_obj = subprocess.Popen("python %s %s" % (conf.settings.MULTITASK_SCRIPT, task_obj.id),
shell=True, stdout=subprocess.PIPE)
self.task = task_obj
3、task_runner
import sys ,os
import time,json
from concurrent.futures import ThreadPoolExecutor
import paramiko
def ssh_cmd(task_log_obj):
host = task_log_obj.bind_host.host
user_obj = task_log_obj.bind_host.remote_user
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(host.ip_addr, host.port, user_obj.username, user_obj.password,timeout=10)
stdin, stdout, stderr = ssh.exec_command(task_log_obj.task.content)
stdout_res = stdout.read()
stderr_res = stderr.read()
result = stdout_res + stderr_res
print(result)
task_log_obj.result = result
task_log_obj.status = 0
ssh.close()
except Exception as e :
task_log_obj.result = e
task_log_obj.status = 1
task_log_obj.save()
def file_transfer(task_log_obj):
host = task_log_obj.bind_host.host
user_obj = task_log_obj.bind_host.remote_user
try:
t = paramiko.Transport((host.ip_addr, host.port))
t.connect(username=user_obj.username, password=user_obj.password)
sftp = paramiko.SFTPClient.from_transport(t)
task_data = json.loads(task_log_obj.task.content)
if task_data['file_transfer_type'] == 'send':
sftp.put(task_data['local_file_path'],task_data['remote_file_path'])
task_log_obj.result = "send local file [%s] to remote [%s] succeeded!" %(task_data['local_file_path'],
task_data['remote_file_path'])
else: #get
local_file_path = "%s/%s" %(django.conf.settings.DOWNLOAD_DIR,task_log_obj.task.id)
if not os.path.isdir(local_file_path):
os.mkdir(local_file_path)
file_name = task_data['remote_file_path'].split('/')[-1]
sftp.get(task_data['remote_file_path'], "%s/%s.%s" %(local_file_path,host.ip_addr,file_name))
task_log_obj.result = "get remote file [%s] succeeded" %(task_data['remote_file_path'])
t.close()
task_log_obj.status = 0
except Exception as e:
task_log_obj.result = e
task_log_obj.status = 1
task_log_obj.save()
if __name__ == '__main__':
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CrazyEye.settings")
import django
django.setup()
from django import conf
from web import models
if len(sys.argv) == 1:
exit("error:must provide task_id!")
task_id = sys.argv[1]
task_obj = models.Task.objects.get(id=task_id)
#1. 生产多线程
pool = ThreadPoolExecutor(10)
if task_obj.task_type == 0:#cmd
thread_func = ssh_cmd
else: #file_transfer
thread_func = file_transfer
for task_log_detail_obj in task_obj.tasklogdetail_set.all():
pool.submit(thread_func,task_log_detail_obj)
#ssh_cmd(task_log_detail_obj)
pool.shutdown(wait=True)
2、CrazyEye
1、settings
1 import os
2
3 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
4 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
5
6
7 # Quick-start development settings - unsuitable for production
8 # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
9
10 # SECURITY WARNING: keep the secret key used in production secret!
11 SECRET_KEY = 'c+lq-s!5j1($4zj+_3icw1xwr)yt#%x%&um#!e!b*-*5x(0&3a'
12
13 # SECURITY WARNING: don't run with debug turned on in production!
14 DEBUG = True
15
16 ALLOWED_HOSTS = ["*"]
17
18
19 # Application definition
20
21 INSTALLED_APPS = [
22 'django.contrib.admin',
23 'django.contrib.auth',
24 'django.contrib.contenttypes',
25 'django.contrib.sessions',
26 'django.contrib.messages',
27 'django.contrib.staticfiles',
28 'web',
29 ]
30
31 MIDDLEWARE = [
32 'django.middleware.security.SecurityMiddleware',
33 'django.contrib.sessions.middleware.SessionMiddleware',
34 'django.middleware.common.CommonMiddleware',
35 'django.middleware.csrf.CsrfViewMiddleware',
36 'django.contrib.auth.middleware.AuthenticationMiddleware',
37 'django.contrib.messages.middleware.MessageMiddleware',
38 'django.middleware.clickjacking.XFrameOptionsMiddleware',
39 ]
40
41 ROOT_URLCONF = 'CrazyEye.urls'
42
43 TEMPLATES = [
44 {
45 'BACKEND': 'django.template.backends.django.DjangoTemplates',
46 'DIRS': [os.path.join(BASE_DIR, 'templates')]
47 ,
48 'APP_DIRS': True,
49 'OPTIONS': {
50 'context_processors': [
51 'django.template.context_processors.debug',
52 'django.template.context_processors.request',
53 'django.contrib.auth.context_processors.auth',
54 'django.contrib.messages.context_processors.messages',
55 ],
56 },
57 },
58 ]
59
60 WSGI_APPLICATION = 'CrazyEye.wsgi.application'
61
62
63 # Database
64 # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
65
66 DATABASES = {
67 'default': {
68 'ENGINE': 'django.db.backends.sqlite3',
69 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
70 }
71 }
72
73
74 # Password validation
75 # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
76
77 AUTH_PASSWORD_VALIDATORS = [
78 {
79 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
80 },
81 {
82 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
83 },
84 {
85 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
86 },
87 {
88 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
89 },
90 ]
91
92
93 # Internationalization
94 # https://docs.djangoproject.com/en/1.10/topics/i18n/
95
96 LANGUAGE_CODE = 'en-us'
97
98 TIME_ZONE = 'UTC'
99
100 USE_I18N = True
101
102 USE_L10N = True
103
104 USE_TZ = True
105
106
107 # Static files (CSS, JavaScript, Images)
108 # https://docs.djangoproject.com/en/1.10/howto/static-files/
109
110 STATIC_URL = '/static/'
111
112 STATICFILES_DIRS = (
113 os.path.join(BASE_DIR,'statics'),
114 )
115
116
117 AUTH_USER_MODEL = 'web.UserProfile'
118
119 AUDIT_LOG_DIR = os.path.join(BASE_DIR,'log')
120 MULTITASK_SCRIPT= os.path.join(BASE_DIR,'backend/task_runner.py')
121
122 DOWNLOAD_DIR = os.path.join(BASE_DIR,'downloads')
123
124
125 LOGIN_URL = "/login/"
settings
2、urls
from django.conf.urls import url
from django.contrib import admin
from web import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', views.dashboard),
url(r'^user_audit/$', views.user_audit,name="user_audit"),
url(r'^audit_log/(\w+-\w+-\w+)/$', views.audit_log_date,name="audit_log_date"),
url(r'^audit_log/(\w+-\w+-\w+)/(\d+)/$', views.audit_log_detail,name="audit_log_detail"),
url(r'^webssh/$', views.webssh,name="webssh"),
url(r'^multitask/cmd/$', views.multitask_cmd,name="multitask_cmd"),
url(r'^multitask/file_transfer/$', views.multitask_file_transfer,name="multitask_file_transfer"),
url(r'^multitask/$', views.multitask,name="multitask"),
url(r'^multitask/result/$', views.multitask_result,name="task_result"),
url(r'^login/$', views.acc_login),
url(r'^logout/$', views.acc_logout,name="logout"),
]
3、templates
1、multitack_cmd.html
{% extends 'index.html' %}
{% block page-title %}主机管理|批量命令{% endblock %}
{% block page-content %}
{% csrf_token %}
<div class="row">
{% include 'multitask_host_list_component.html' %}
<div class="col-lg-8">
<div class="panel">
<div class="panel-heading">
<h3 class="panel-title">命令操作</h3>
</div>
<div class="panel-body">
<textarea id="cmd_text" class="form-control"></textarea>
<input type="button" id='post_task_btn' οnclick="PostTask(this,'cmd')" class="btn btn-success pull-right" value="执行命令">
</div>
</div>
<div class="panel">
<div class="panel-heading">
<h3 class="panel-title">任务结果</h3>
</div>
<div class="panel-body">
<ul id="task_result_container"></ul>
</div>
</div>
</div>
</div>
{% include 'multitask_js_component.html' %}
{% endblock %}
2、multitask_host_list_component
<div class="col-lg-4">
<div class="panel">
<div class="panel-heading">
<h3 class="panel-title">主机列表</h3>
</div>
<div class="panel-body">
<div class="list-group bord-no">
<a οnclick="HostListDisplayToggle(this)" class="list-group-item " href="#">
<input type="checkbox" οnclick="SelectGroup(this)">
未分组主机
<span class="badge badge-primary">{{ request.user.bind_hosts.count }}</span>
</a>
<ol class="hide">
{% for bind_host in request.user.bind_hosts.all %}
<li><input type="checkbox" select_host="true" value="{{ bind_host.id }}">{{ bind_host.host.hostname }}({{ bind_host.host.ip_addr }})@{{ bind_host.remote_user.username}}</li>
{% endfor %}
</ol>
{% for host_group in request.user.host_groups.select_related %}
<a οnclick="HostListDisplayToggle(this)" class="list-group-item " href="#">
<input type="checkbox" οnclick="SelectGroup(this)">
{{ host_group.name }}
<span class="badge badge-primary">{{ host_group.bind_hosts.count }}</span>
</a>
<ol class="hide">
{% for bind_host in host_group.bind_hosts.all %}
<li><input type="checkbox" select_host="true" value="{{ bind_host.id }}">{{ bind_host.host.hostname }}({{ bind_host.host.ip_addr }})@{{ bind_host.remote_user.username}}</li>
{% endfor %}
</ol>
{% endfor %}
</div>
</div>
</div>
</div>
3、multitask_js_component
<script>
function SelectFileTransferType(ele) {
if ($(ele).val() == 'get'){
$("#local_file_path").addClass("hide");
}else {
$("#local_file_path").removeClass("hide");
}
}
function HostListDisplayToggle(ele) {
$(ele).next().toggleClass("hide");
}
function SelectGroup(ele) {
$(ele).parent().next().find("input").prop("checked",$(ele).prop("checked"))
}
function GetTaskResult(task_id) {
$.getJSON( "{% url 'task_result' %}" ,{'task_id':task_id},function(callback){
console.log( callback);
var all_task_done = true;
$.each(callback,function (index,ele) {
var li_ele = $("li[bind_host_id='"+ ele['id'] +"']");
li_ele.next().text(ele['result']);
$(li_ele.children()[0]).text(ele.status);
if ( ele.status == 2 ){
all_task_done = false; //有任务未完成
}
})
if (all_task_done){
clearInterval(ResultRefreshObj);
$("#post_task_btn").removeClass("disabled");
}
});//end getJSON
}
function PostTask(ele,task_type) {
var selected_host_ids = [];
$("input[select_host]:checked").each(function () {
selected_host_ids.push( $(this).val() );
})
console.log(selected_host_ids)
if (selected_host_ids.length == 0){
alert("必须选择主机!")
return false
}
if (task_type == "cmd"){
var cmd_text = $("#cmd_text").val().trim();
if (cmd_text.length == 0){
alert("必须输入要执行的命令!")
return false
}
var task_arguments = {
'selected_host_ids' : selected_host_ids,
'task_type':0 ,//cmd
'cmd': cmd_text,
}
}else {
var file_transfer_type = $("select[name='file_transfer_type']").val()
var local_file_path = $("#local_file_path").val().trim()
var remote_file_path = $("#remote_file_path").val().trim()
if (file_transfer_type == "send"){
if (local_file_path.length == 0){
alert("必须输入本地文件路径!")
return false
}
}
if (remote_file_path.length == 0){
alert("必须输入远程文件路径!")
return false
}
var task_arguments = {
'selected_host_ids' : selected_host_ids,
'task_type':1 ,//file_transfer
'file_transfer_type': file_transfer_type,
'local_file_path':local_file_path,
'remote_file_path':remote_file_path
}
}
//再此任务执行完成前,不允许再提交新任务
$(ele).addClass("disabled")
//提交新任务之前情况任务结果面版
$("#task_result_container").empty();
$.post("{% url 'multitask' %}" , {'task_data':JSON.stringify(task_arguments),'csrfmiddlewaretoken':$("input[name='csrfmiddlewaretoken']").val() },function(callback){
console.log(callback);
var callback = JSON.parse(callback);
$.each(callback.selected_hosts,function (index,ele) {
var li_ele = "<li bind_host_id='"+ ele['id'] +"'>Host:" + ele.bind_host__host__hostname + "(" +ele.bind_host__host__ip_addr +")----------------<span></span></li><pre>sdff</pre>" ;
$("#task_result_container").append(li_ele);
})
//去后端定时拿结果
ResultRefreshObj = setInterval(function () {
GetTaskResult(callback.task_id);
},2000);
});//end post
}
</script>
4、web
1、admin
1 from django.contrib import admin
2
3 from web import models
4 # Register your models here.
5
6
7 from django import forms
8 from django.contrib import admin
9 from django.contrib.auth.models import Group
10 from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField
12
13 from web.models import UserProfile
14
15
16 class UserCreationForm(forms.ModelForm):
17 """A form for creating new users. Includes all the required
18 fields, plus a repeated password."""
19 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
20 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
21
22 class Meta:
23 model = UserProfile
24 fields = ('email', 'name')
25
26 def clean_password2(self):
27 # Check that the two password entries match
28 password1 = self.cleaned_data.get("password1")
29 password2 = self.cleaned_data.get("password2")
30 if password1 and password2 and password1 != password2:
31 raise forms.ValidationError("Passwords don't match")
32 return password2
33
34 def save(self, commit=True):
35 # Save the provided password in hashed format
36 user = super(UserCreationForm, self).save(commit=False)
37 user.set_password(self.cleaned_data["password1"])
38 if commit:
39 user.save()
40 return user
41
42
43 class UserChangeForm(forms.ModelForm):
44 """A form for updating users. Includes all the fields on
45 the user, but replaces the password field with admin's
46 password hash display field.
47 """
48 password = ReadOnlyPasswordHashField()
49
50 class Meta:
51 model = UserProfile
52 fields = ('email', 'password', 'name', 'is_active', 'is_admin')
53
54 def clean_password(self):
55 # Regardless of what the user provides, return the initial value.
56 # This is done here, rather than on the field, because the
57 # field does not have access to the initial value
58 return self.initial["password"]
59
60
61 class UserProfileAdmin(BaseUserAdmin):
62 # The forms to add and change user instances
63 form = UserChangeForm
64 add_form = UserCreationForm
65
66 # The fields to be used in displaying the User model.
67 # These override the definitions on the base UserAdmin
68 # that reference specific fields on auth.User.
69 list_display = ('email', 'name','is_staff', 'is_admin')
70 list_filter = ('is_admin','is_staff')
71 fieldsets = (
72 (None, {'fields': ('email', 'password')}),
73 ('Personal info', {'fields': ('name',)}),
74 ('堡垒机主机授权', {'fields': ('bind_hosts','host_groups')}),
75 ('Permissions', {'fields': ('is_admin','is_staff','user_permissions','groups')}),
76 )
77 # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
78 # overrides get_fieldsets to use this attribute when creating a user.
79 add_fieldsets = (
80 (None, {
81 'classes': ('wide',),
82 'fields': ('email', 'name', 'password1', 'password2')}
83 ),
84 )
85 search_fields = ('email',)
86 ordering = ('email',)
87 filter_horizontal = ('user_permissions','groups','bind_hosts','host_groups')
88
89 # Now register the new UserAdmin...
90 admin.site.register(models.UserProfile, UserProfileAdmin)
91 # ... and, since we're not using Django's built-in permissions,
92 # unregister the Group model from admin.
93 admin.site.unregister(Group)
94
95
96
97 class RemoteUserAdmin(admin.ModelAdmin):
98 list_display = ('username','auth_type','password')
99
100
101 class TaskAdmin(admin.ModelAdmin):
102 list_display = ['id','user','task_type','content','date']
103
104
105 class TaskLogDetailAdmin(admin.ModelAdmin):
106 list_display = ['id','task','bind_host','result','status','start_date','end_date']
107
108
109 admin.site.register(models.Host)
110 admin.site.register(models.HostGroup)
111 admin.site.register(models.BindHost)
112 admin.site.register(models.RemoteUser,RemoteUserAdmin)
113 admin.site.register(models.IDC)
114 admin.site.register(models.Session)
115 admin.site.register(models.Task,TaskAdmin)
116 admin.site.register(models.TaskLogDetail,TaskLogDetailAdmin)
View Code
2、models
1 from django.db import models
2 from django.contrib.auth.models import User
3
4 from django.contrib.auth.models import (
5 BaseUserManager, AbstractBaseUser,PermissionsMixin
6 )
7
8
9
10 # Create your models here.
11
12 class IDC(models.Model):
13 name = models.CharField(max_length=64,unique=True)
14
15 def __str__(self):
16 return self.name
17
18 class Host(models.Model):
19 """存储所有主机"""
20 hostname = models.CharField(max_length=64)
21 ip_addr = models.GenericIPAddressField(unique=True)
22 port = models.PositiveSmallIntegerField(default=22)
23 idc = models.ForeignKey("IDC")
24
25 enabled = models.BooleanField(default=True)
26
27 def __str__(self):
28 return self.ip_addr
29
30
31
32 class HostGroup(models.Model):
33 """主机组"""
34 name = models.CharField(max_length=64, unique=True)
35 bind_hosts = models.ManyToManyField("BindHost")
36 def __str__(self):
37 return self.name
38
39
40
41 class RemoteUser(models.Model):
42 """存储远程用户名密码"""
43 username = models.CharField(max_length=64)
44 auth_type_choices = ((0,'ssh/password'),(1,'ssh/key'))
45 auth_type = models.SmallIntegerField(choices=auth_type_choices,default=0)
46 password = models.CharField(max_length=128,blank=True,null=True)
47
48 #hosts = models.ManyToManyField("Host")
49
50 def __str__(self):
51 return "%s(%s)%s" %( self.username,self.get_auth_type_display(),self.password)
52
53 class Meta:
54 unique_together = ('username','auth_type','password')
55
56 class BindHost(models.Model):
57 """绑定远程主机和远程用户的对应关系"""
58 host = models.ForeignKey("Host")
59 remote_user = models.ForeignKey("RemoteUser")
60
61 def __str__(self):
62 return "%s -> %s" %(self.host,self.remote_user)
63 class Meta:
64 unique_together = ("host","remote_user")
65
66
67
68 class UserProfileManager(BaseUserManager):
69 def create_user(self, email, name, password=None):
70 """
71 Creates and saves a User with the given email, date of
72 birth and password.
73 """
74 if not email:
75 raise ValueError('Users must have an email address')
76
77 user = self.model(
78 email=self.normalize_email(email),
79 name=name,
80 )
81
82 user.set_password(password)
83 user.save(using=self._db)
84 return user
85
86 def create_superuser(self, email, name, password):
87 """
88 Creates and saves a superuser with the given email, date of
89 birth and password.
90 """
91 user = self.create_user(
92 email,
93 password=password,
94 name=name,
95 )
96 user.is_admin = True
97 user.is_staff = True
98 user.save(using=self._db)
99 return user
100
101
102 class UserProfile(AbstractBaseUser,PermissionsMixin):
103 email = models.EmailField(
104 verbose_name='email address',
105 max_length=255,
106 unique=True,
107 )
108 name = models.CharField(max_length=64)
109 is_active = models.BooleanField(default=True)
110 is_admin = models.BooleanField(default=False)
111 is_staff = models.BooleanField(
112 ('staff status'),
113 default=False,
114 help_text=('Designates whether the user can log into this admin site.'),
115 )
116
117 bind_hosts = models.ManyToManyField("BindHost",blank=True)
118 host_groups = models.ManyToManyField("HostGroup",blank=True)
119
120 objects = UserProfileManager()
121
122 USERNAME_FIELD = 'email'
123 REQUIRED_FIELDS = ['name',]
124
125 def get_full_name(self):
126 # The user is identified by their email address
127 return self.email
128
129 def get_short_name(self):
130 # The user is identified by their email address
131 return self.email
132
133 def __str__(self): # __unicode__ on Python 2
134 return self.email
135
136 def has_perm(self, perm, obj=None):
137 "Does the user have a specific permission?"
138 # Simplest possible answer: Yes, always
139 return True
140
141 def has_module_perms(self, app_label):
142 "Does the user have permissions to view the app `app_label`?"
143 # Simplest possible answer: Yes, always
144 return True
145
146
147
148
149 class Session(models.Model):
150 '''生成用户操作session id '''
151 user = models.ForeignKey('UserProfile')
152 bind_host = models.ForeignKey('BindHost')
153 tag = models.CharField(max_length=128,default='n/a')
154 closed = models.BooleanField(default=False)
155 cmd_count = models.IntegerField(default=0) #命令执行数量
156 stay_time = models.IntegerField(default=0, help_text="每次刷新自动计算停留时间",verbose_name="停留时长(seconds)")
157 date = models.DateTimeField(auto_now_add=True)
158
159 def __str__(self):
160 return '<id:%s user:%s bind_host:%s>' % (self.id,self.user.email,self.bind_host.host)
161 class Meta:
162 verbose_name = '审计日志'
163 verbose_name_plural = '审计日志'
164
165
166
167 class Task(models.Model):
168 """批量任务记录表"""
169 user = models.ForeignKey("UserProfile")
170 task_type_choices = ((0,'cmd'),(1,'file_transfer'))
171 task_type = models.SmallIntegerField(choices=task_type_choices)
172 content = models.TextField(verbose_name="任务内容")
173 #hosts = models.ManyToManyField("BindHost")
174 date = models.DateTimeField(auto_now_add=True)
175
176 def __str__(self):
177 return "%s %s" %(self.task_type,self.content)
178
179
180 class TaskLogDetail(models.Model):
181 task = models.ForeignKey("Task")
182 bind_host = models.ForeignKey("BindHost")
183 result = models.TextField()
184
185 status_choices = ((0,'success'),(1,'failed'),(2,'init'))
186 status = models.SmallIntegerField(choices=status_choices)
187
188 start_date = models.DateTimeField(auto_now_add=True)
189 end_date = models.DateTimeField(blank=True,null=True)
190
191
192 def __str__(self):
193 return "%s %s" %(self.bind_host,self.status)
models
3、views
from django.shortcuts import render,redirect,HttpResponse
from django.contrib.auth.decorators import login_required
from django.contrib.auth import authenticate,logout,login
from django.conf import settings
import os,re,json
from web import models
from backend.task_manager import MultiTaskManger
from backend import audit
# Create your views here.
def json_date_handler(obj):
if hasattr(obj, 'isoformat'):
return obj.strftime("%Y-%m-%d %T")
@login_required
def dashboard(request):
return render(request,'index.html')
def acc_login(request):
error_msg = ''
if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username,password=password)
if user:
login(request,user)
return redirect("/")
else:
error_msg = "Wrong username or password!"
return render(request,"login.html",{'error_msg':error_msg})
def acc_logout(request):
logout(request)
return redirect("/login/")
@login_required
def webssh(request):
return render(request,'web_ssh.html')
@login_required
def user_audit(request):
log_dirs = os.listdir(settings.AUDIT_LOG_DIR)
return render(request,'user_audit.html',locals())
@login_required
def audit_log_date(request,log_date):
log_date_path = "%s/%s" %(settings.AUDIT_LOG_DIR,log_date)
log_file_dirs = os.listdir(log_date_path)
session_ids = [re.search("\d+",i).group() for i in log_file_dirs ]
session_objs = models.Session.objects.filter(id__in=session_ids)
return render(request, 'user_audit_file_list.html', locals())
@login_required
def multitask_cmd(request):
return render(request,"multitask_cmd.html")
@login_required
def multitask_file_transfer(request):
return render(request,'multitask_file_transfer.html')
@login_required
def multitask_result(request):
task_id = request.GET.get('task_id')
task_obj = models.Task.objects.get(id=task_id)
task_log_results = list(task_obj.tasklogdetail_set.values('id', 'result','status','start_date','end_date'))
return HttpResponse(json.dumps(task_log_results,default=json_date_handler))
@login_required
def multitask(request):
print("--->",request.POST)
task_data = json.loads(request.POST.get('task_data'))
print("--->selcted hosts",task_data)
task_obj= MultiTaskManger(request)
selected_hosts = list(task_obj.task.tasklogdetail_set.all().values('id', 'bind_host__host__ip_addr',
'bind_host__host__hostname', 'bind_host__remote_user__username'))
return HttpResponse(
json.dumps({'task_id':task_obj.task.id,'selected_hosts':selected_hosts})
)
@login_required
def audit_log_detail(request,log_date,session_id):
log_date_path = "%s/%s" % (settings.AUDIT_LOG_DIR, log_date)
log_file_path = "%s/session_%s.log" %(log_date_path,session_id)
log_parser = audit.AuditLogHandler(log_file_path)
cmd_list = log_parser.parse()
return render(request,"user_audit_detail.html",locals())
五、测试截图
1、执行一条命令
1、web端
2、admin后台
2、执行多条命令
1、web前端
2、admin后台
4、连接不上的命令截图
连接不上直接排除timeout
3、后台触发一条命令
1、后台触发创建
2、控制台截图
3、admin后台截图
4、执行结果前端web显示
1、任务结果前端显示hostid
2、任务结果限制执行结果
3、超时处理
4、机器连接不通
5、超时状态数字变化
6、执行成功状态变化