一、实际生产的架构图
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' ο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)
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、执行成功状态变化