主机管理+堡垒机系统开发:前端批量命令结果(十二)
一、实际生产的架构图
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/"
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' onclick="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 onclick="HostListDisplayToggle(this)" class="list-group-item " href="#"> <input type="checkbox" onclick="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 onclick="HostListDisplayToggle(this)" class="list-group-item " href="#"> <input type="checkbox" onclick="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)
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)
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、执行成功状态变化
作者:罗阿红
出处:http://www.cnblogs.com/luoahong/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。