主机管理+堡垒机系统开发:前端批量命令结果(十二)

一、实际生产的架构图

1、生产环境为什么要这样干

  1. 解耦
  2. 异步

 2、常用的queue软件

  1. redis, rabbitmq
  2. github ,
  3. restful API
  4. 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.	%s[%s]" %(index,host_group.name, host_group.bind_hosts.count()))
                print("z.	未分组主机[%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.	%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'  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)
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、执行成功状态变化

原文地址:https://www.cnblogs.com/luoahong/p/9501791.html