diff --git a/.gitignore b/.gitignore index c6a0a14a9..4eaa616d2 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ nosetests.xml .mr.developer.cfg .project .pydevproject +*.xlsx node_modules logs keys diff --git a/jasset/ansible_api.py b/jasset/ansible_api.py new file mode 100644 index 000000000..7041c7bf1 --- /dev/null +++ b/jasset/ansible_api.py @@ -0,0 +1,459 @@ +# -*- coding: utf-8 -*- + +from ansible.inventory.group import Group +from ansible.inventory.host import Host +from ansible.inventory import Inventory +from ansible.runner import Runner +from ansible.playbook import PlayBook + +from ansible import callbacks +from ansible import utils +from passlib.hash import sha512_crypt + +# from utils import get_rand_pass + +import random +import os.path +API_DIR = os.path.dirname(os.path.abspath(__file__)) +ANSIBLE_DIR = os.path.join(API_DIR, 'playbooks') + +def get_rand_pass(): + """ + get a reandom password. + """ + lower = [chr(i) for i in range(97,123)] + upper = [chr(i).upper() for i in range(97,123)] + digit = [str(i) for i in range(10)] + password_pool = [] + password_pool.extend(lower) + password_pool.extend(upper) + password_pool.extend(digit) + pass_list = [random.choice(password_pool) for i in range(1,14)] + pass_list.insert(random.choice(range(1,14)), '@') + pass_list.insert(random.choice(range(1,14)), random.choice(digit)) + password = ''.join(pass_list) + return password + +class AnsibleError(StandardError): + """ + the base AnsibleError which contains error(required), + data(optional) and message(optional). + 存储所有Ansible 异常对象 + """ + def __init__(self, error, data='', message=''): + super(AnsibleError, self).__init__(message) + self.error = error + self.data = data + self.message = message + + +class CommandValueError(AnsibleError): + """ + indicate the input value has error or invalid. + the data specifies the error field of input form. + 输入不合法 异常对象 + """ + def __init__(self, field, message=''): + super(CommandValueError, self).__init__('value:invalid', field, message) + + +class MyInventory(object): + """ + this is my ansible inventory object. + """ + def __init__(self, resource): + """ + resource的数据格式是一个列表字典,比如 + { + "group1": { + "hosts": [{"hostname": "10.10.10.10", "port": "22", "username": "test", "password": "mypass"}, ...], + "vars": {"var1": value1, "var2": value2, ...} + } + } + + 如果你只传入1个列表,这默认该列表内的所有主机属于my_group组,比如 + [{"hostname": "10.10.10.10", "port": "22", "username": "test", "password": "mypass"}, ...] + """ + self.resource = resource + self.inventory = Inventory() + self.gen_inventory() + + def add_group(self, hosts, groupname, groupvars=None): + """ + add hosts to a group + """ + my_group = Group(name=groupname) + + # if group variables exists, add them to group + if groupvars: + for key, value in groupvars.iteritems(): + my_group.set_variable(key, value) + + # add hosts to group + for host in hosts: + # set connection variables + hostname = host.get("hostname") + hostport = host.get("port") + username = host.get("username") + password = host.get("password") + my_host = Host(name=hostname, port=hostport) + my_host.set_variable('ansible_ssh_host', hostname) + my_host.set_variable('ansible_ssh_port', hostport) + my_host.set_variable('ansible_ssh_user', username) + my_host.set_variable('ansible_ssh_pass', password) + # set other variables + for key, value in host.iteritems(): + if key not in ["hostname", "port", "username", "password"]: + my_host.set_variable(key, value) + # add to group + my_group.add_host(my_host) + + self.inventory.add_group(my_group) + + def gen_inventory(self): + """ + add hosts to inventory. + """ + if isinstance(self.resource, list): + self.add_group(self.resource, 'my_group') + elif isinstance(self.resource, dict): + for groupname, hosts_and_vars in self.resource.iteritems(): + self.add_group(hosts_and_vars.get("hosts"), groupname, hosts_and_vars.get("vars")) + + +class Command(MyInventory): + """ + this is a command object for parallel execute command. + """ + def __init__(self, *args, **kwargs): + super(Command, self).__init__(*args, **kwargs) + self.results = '' + + def run(self, command, module_name="command", timeout=5, forks=10, group='my_group'): + """ + run command from andible ad-hoc. + command : 必须是一个需要执行的命令字符串, 比如 + 'uname -a' + """ + if module_name not in ["raw", "command", "shell"]: + raise CommandValueError("module_name", + "module_name must be of the 'raw, command, shell'") + hoc = Runner(module_name=module_name, + module_args=command, + timeout=timeout, + inventory=self.inventory, + subset=group, + forks=forks + ) + self.results = hoc.run() + + if self.stdout: + return {"ok": self.stdout} + else: + msg = [] + if self.stderr: + msg.append(self.stderr) + if self.dark: + msg.append(self.dark) + return {"failed": msg} + + @property + def raw_results(self): + """ + get the ansible raw results. + """ + return self.results + + @property + def exec_time(self): + """ + get the command execute time. + """ + result = {} + all = self.results.get("contacted") + for key, value in all.iteritems(): + result[key] = { + "start": value.get("start"), + "end" : value.get("end"), + "delta": value.get("delta"),} + return result + + @property + def stdout(self): + """ + get the comamnd standard output. + """ + result = {} + all = self.results.get("contacted") + for key, value in all.iteritems(): + result[key] = value.get("stdout") + return result + + @property + def stderr(self): + """ + get the command standard error. + """ + result = {} + all = self.results.get("contacted") + for key, value in all.iteritems(): + result[key] = { + "stderr": value.get("stderr"), + "warnings": value.get("warnings"),} + return result + + @property + def dark(self): + """ + get the dark results. + """ + return self.results.get("dark") + + +class Tasks(Command): + """ + this is a tasks object for include the common command. + """ + def __init__(self, *args, **kwargs): + super(Tasks, self).__init__(*args, **kwargs) + + def __run(self, module_args, module_name="command", timeout=5, forks=10, group='my_group'): + """ + run command from andible ad-hoc. + command : 必须是一个需要执行的命令字符串, 比如 + 'uname -a' + """ + hoc = Runner(module_name=module_name, + module_args=module_args, + timeout=timeout, + inventory=self.inventory, + subset=group, + forks=forks + ) + + self.results = hoc.run() + + @property + def msg(self): + """ + get the contacted and dark msg + """ + msg = {} + for result in ["contacted", "dark"]: + all = self.results.get(result) + for key, value in all.iteritems(): + if value.get("msg"): + msg[key] = value.get("msg") + return msg + + def push_key(self, user, key_path): + """ + push the ssh authorized key to target. + """ + module_args = 'user="%s" key="{{ lookup("file", "%s") }}"' % (user, key_path) + self.__run(module_args, "authorized_key") + + return {"status": "failed","msg": self.msg} if self.msg else {"status": "ok"} + + def del_key(self, user, key_path): + """ + push the ssh authorized key to target. + """ + module_args = 'user="%s" key="{{ lookup("file", "%s") }}" state="absent"' % (user, key_path) + self.__run(module_args, "authorized_key") + + return {"status": "failed","msg": self.msg} if self.msg else {"status": "ok"} + + def add_user(self, username, password): + """ + add a host user. + """ + encrypt_pass = sha512_crypt.encrypt(password) + module_args = 'name=%s shell=/bin/bash password=%s' % (username, encrypt_pass) + self.__run(module_args, "user") + + return {"status": "failed","msg": self.msg} if self.msg else {"status": "ok"} + + def add_multi_user(self, *args): + """ + add multi user + :param args: + user + :return: + """ + results = {} + users = {} + action = results["action_info"] = {} + for user in args: + users[user] = get_rand_pass() + for user, password in users.iteritems(): + ret = self.add_user(user, password) + action[user] = ret + results["user_info"] = users + + return results + + def del_user(self, username): + """ + delete a host user. + """ + module_args = 'name=%s state=absent remove=yes move_home=yes force=yes' % (username) + self.__run(module_args, "user") + + return {"status": "failed","msg": self.msg} if self.msg else {"status": "ok"} + + def add_init_users(self): + """ + add initail users: SA, DBA, DEV + """ + results = {} + action = results["action_info"] = {} + users = {"SA": get_rand_pass(), "DBA": get_rand_pass(), "DEV": get_rand_pass()} + for user, password in users.iteritems(): + ret = self.add_user(user, password) + action[user] = ret + results["user_info"] = users + + return results + + def del_init_users(self): + """ + delete initail users: SA, DBA, DEV + """ + results = {} + action = results["action_info"] = {} + for user in ["SA", "DBA", "DEV"]: + ret = self.del_user(user) + action[user] = ret + return results + + def get_host_info(self): + """ + use the setup module get host informations + :return: + all_ip is list + processor_count is int + system_dist_version is string + system_type is string + disk is dict (device_name: device_size} + system_dist is string + processor_type is string + default_ip is string + hostname is string + product_sn is string + memory_total is int (MB) + default_mac is string + product_name is string + """ + self.__run('', 'setup') + + result = {} + all = self.results.get("contacted") + for key, value in all.iteritems(): + setup =value.get("ansible_facts") + # get disk informations + disk_all = setup.get("ansible_devices") + disk_need = {} + for disk_name, disk_info in disk_all.iteritems(): + if disk_name.startswith('sd') or disk_name.startswith('hd') or disk_name.startswith('vd'): + disk_need[disk_name] = disk_info.get("size") + + result[key] = { + "other_ip": setup.get("ansible_all_ipv4_addresses"), + "hostname": setup.get("ansible_hostname" ), + "ip": setup.get("ansible_default_ipv4").get("address"), + "mac": setup.get("ansible_default_ipv4").get("macaddress"), + "brand": setup.get("ansible_product_name"), + "cpu_type": setup.get("ansible_processor"), + "cpu_cores": setup.get("ansible_processor_count"), + "memory": setup.get("ansible_memtotal_mb"), + "disk": disk_need, + "system_type": setup.get("ansible_distribution"), + "system_version": setup.get("ansible_distribution_version"), + "asset_type": setup.get("ansible_system"), + "sn": setup.get("ansible_product_serial") + } + + return {"status": "failed", "msg": self.msg} if self.msg else {"status": "ok", "result": result} + + +class CustomAggregateStats(callbacks.AggregateStats): + """ + Holds stats about per-host activity during playbook runs. + """ + def __init__(self): + super(CustomAggregateStats, self).__init__() + self.results = [] + + def compute(self, runner_results, setup=False, poll=False, + ignore_errors=False): + """ + Walk through all results and increment stats. + """ + super(CustomAggregateStats, self).compute(runner_results, setup, poll, + ignore_errors) + + self.results.append(runner_results) + + + def summarize(self, host): + """ + Return information about a particular host + """ + summarized_info = super(CustomAggregateStats, self).summarize(host) + + # Adding the info I need + summarized_info['result'] = self.results + + return summarized_info + + +class MyPlaybook(MyInventory): + """ + this is my playbook object for execute playbook. + """ + def __init__(self, *args, **kwargs): + super(MyPlaybook, self).__init__(*args, **kwargs) + + + def run(self, playbook_relational_path, extra_vars=None): + """ + run ansible playbook, + only surport relational path. + """ + stats = callbacks.AggregateStats() + playbook_cb = callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY) + runner_cb = callbacks.PlaybookRunnerCallbacks(stats, verbose=utils.VERBOSITY) + playbook_path = os.path.join(ANSIBLE_DIR, playbook_relational_path) + + pb = PlayBook( + playbook = playbook_path, + stats = stats, + callbacks = playbook_cb, + runner_callbacks = runner_cb, + inventory = self.inventory, + extra_vars = extra_vars, + check=False) + + self.results = pb.run() + + @property + def raw_results(self): + """ + get the raw results after playbook run. + """ + return self.results + + +class App(MyPlaybook): + """ + this is a app object for inclue the common playbook. + """ + def __init__(self, *args, **kwargs): + super(App, self).__init__(*args, **kwargs) + + +if __name__ == "__main__": + pass + + diff --git a/jasset/asset_api.py b/jasset/asset_api.py index 6968efa7b..998d669c5 100644 --- a/jasset/asset_api.py +++ b/jasset/asset_api.py @@ -1,5 +1,9 @@ # coding: utf-8 +import xlrd +import xlsxwriter +from django.db.models import AutoField from jumpserver.api import * +from jasset.models import ASSET_STATUS, ASSET_TYPE, ASSET_ENV, IDC, AssetRecord def group_add_asset(group, asset_id=None, asset_ip=None): @@ -32,6 +36,21 @@ def db_add_group(**kwargs): group_add_asset(group, asset_id) +def db_update_group(**kwargs): + """ + add a asset group in database + 数据库中更新资产 + """ + group_id = kwargs.pop('id') + asset_id_list = kwargs.pop('asset_select') + group = get_object(AssetGroup, id=group_id) + + for asset_id in asset_id_list: + group_add_asset(group, asset_id) + + AssetGroup.objects.filter(id=group_id).update(**kwargs) + + def db_asset_add(**kwargs): """ add asset to db @@ -80,11 +99,12 @@ def db_asset_update(**kwargs): asset_id = kwargs.pop('id') Asset.objects.filter(id=asset_id).update(**kwargs) + # # -# def batch_host_edit(host_info, j_user='', j_password=''): +# def batch_host_edit(host_alter_dic, j_user='', j_password=''): # """ 批量修改主机函数 """ -# j_id, j_ip, j_idc, j_port, j_type, j_group, j_dept, j_active, j_comment = host_info +# j_id, j_ip, j_idc, j_port, j_type, j_group, j_dept, j_active, j_comment = host_alter_dic # groups, depts = [], [] # is_active = {u'是': '1', u'否': '2'} # login_types = {'LDAP': 'L', 'MAP': 'M'} @@ -156,3 +176,230 @@ def db_asset_update(**kwargs): # else: # return httperror(request, '删除失败, 没有这个IDC!') + +def sort_ip_list(ip_list): + """ ip地址排序 """ + ip_list.sort(key=lambda s: map(int, s.split('.'))) + return ip_list + + +def get_tuple_name(asset_tuple, value): + """""" + for t in asset_tuple: + if t[0] == value: + return t[1] + + return '' + + +def get_tuple_diff(asset_tuple, field_name, value): + """""" + old_name = get_tuple_name(asset_tuple, int(value[0])) if value[0] else u'' + new_name = get_tuple_name(asset_tuple, int(value[1])) if value[1] else u'' + alert_info = [field_name, old_name, new_name] + return alert_info + + +def asset_diff(before, after): + """ + asset change before and after + """ + alter_dic = {} + before_dic, after_dic = before, dict(after.iterlists()) + for k, v in before_dic.items(): + after_dic_values = after_dic.get(k, []) + if k == 'group': + after_dic_value = after_dic_values if len(after_dic_values) > 0 else u'' + uv = v if v is not None else u'' + else: + after_dic_value = after_dic_values[0] if len(after_dic_values) > 0 else u'' + uv = unicode(v) if v is not None else u'' + if uv != after_dic_value: + alter_dic.update({k: [uv, after_dic_value]}) + + for k, v in alter_dic.items(): + if v == [None, u'']: + alter_dic.pop(k) + + return alter_dic + + +def asset_diff_one(before, after): + print before.__dict__, after.__dict__ + fields = Asset._meta.get_all_field_names() + for field in fields: + print before.field, after.field + + +def db_asset_alert(asset, username, alert_dic): + """ + asset alert info to db + """ + alert_list = [] + asset_tuple_dic = {'status': ASSET_STATUS, 'env': ASSET_ENV, 'asset_type': ASSET_TYPE} + for field, value in alert_dic.iteritems(): + print field + field_name = Asset._meta.get_field_by_name(field)[0].verbose_name + if field == 'idc': + old = IDC.objects.filter(id=value[0]) if value[0] else u'' + new = IDC.objects.filter(id=value[1]) if value[1] else u'' + old_name = old[0].name if old else u'' + new_name = new[0].name if new else u'' + alert_info = [field_name, old_name, new_name] + + elif field in ['status', 'env', 'asset_type']: + alert_info = get_tuple_diff(asset_tuple_dic.get(field), field_name, value) + + elif field == 'group': + old, new = [], [] + for group_id in value[0]: + group_name = AssetGroup.objects.get(id=int(group_id)).name + old.append(group_name) + for group_id in value[1]: + group_name = AssetGroup.objects.get(id=int(group_id)).name + new.append(group_name) + if old == new: + continue + else: + alert_info = [field_name, ','.join(old), ','.join(new)] + + elif field == 'use_default_auth': + if unicode(value[0]) == 'True' and unicode(value[1]) == 'on' or \ + unicode(value[0]) == 'False' and unicode(value[1]) == '': + continue + else: + name = asset.username + alert_info = [field_name, u'默认', name] if unicode(value[0]) == 'True' else \ + [field_name, name, u'默认'] + + elif field in ['username', 'password']: + continue + + elif field == 'is_active': + if unicode(value[0]) == 'True' and unicode(value[1]) == '1' or \ + unicode(value[0]) == 'False' and unicode(value[1]) == '0': + continue + else: + alert_info = [u'是否激活', u'激活', u'禁用'] if unicode(value[0]) == 'True' else \ + [u'是否激活', u'禁用', u'激活'] + + else: + alert_info = [field_name, unicode(value[0]), unicode(value[1])] + + if 'alert_info' in dir(): + alert_list.append(alert_info) + + if alert_list: + AssetRecord.objects.create(asset=asset, username=username, content=alert_list) + + +def write_excel(asset_all): + data = [] + now = datetime.datetime.now().strftime('%Y_%m_%d_%H_%M') + file_name = 'cmdb_excel_' + now + '.xlsx' + workbook = xlsxwriter.Workbook('static/files/excels/%s' % file_name) + worksheet = workbook.add_worksheet(u'CMDB数据') + worksheet.set_first_sheet() + worksheet.set_column('A:Z', 14) + title = [u'主机名', u'IP', u'IDC', u'MAC', u'远控IP', u'CPU', u'内存', u'硬盘', u'操作系统', u'机柜位置', + u'所属主机组', u'机器状态', u'备注'] + for asset in asset_all: + group_list = [] + for p in asset.group.all(): + group_list.append(p.name) + + group_all = '/'.join(group_list) + status = asset.get_status_display() + idc_name = asset.idc.name if asset.idc else u'' + alter_dic = [asset.hostname, asset.ip, idc_name, asset.mac, asset.remote_ip, asset.cpu, asset.memory, + asset.disk, (asset.system_type + asset.system_version), asset.cabinet, group_all, status, + asset.comment] + data.append(alter_dic) + format = workbook.add_format() + format.set_border(1) + format.set_align('center') + + format_title = workbook.add_format() + format_title.set_border(1) + format_title.set_bg_color('#cccccc') + format_title.set_align('center') + format_title.set_bold() + + format_ave = workbook.add_format() + format_ave.set_border(1) + format_ave.set_num_format('0.00') + + worksheet.write_row('A1', title, format_title) + i = 2 + for alter_dic in data: + location = 'A' + str(i) + worksheet.write_row(location, alter_dic, format) + i += 1 + + workbook.close() + ret = (True, file_name) + return ret + + +def copy_model_instance(obj): + initial = dict([(f.name, getattr(obj, f.name)) + for f in obj._meta.fields + if not isinstance(f, AutoField) and \ + not f in obj._meta.parents.values()]) + return obj.__class__(**initial) + + +def ansible_record(asset, ansible_dic, username): + alert_dic = {} + asset_dic = asset.__dict__ + for field, value in ansible_dic.items(): + old = asset_dic.get(field) + new = ansible_dic.get(field) + if unicode(old) != unicode(new): + print old, new, type(old), type(new) + setattr(asset, field, value) + asset.save() + alert_dic[field] = [old, new] + + db_asset_alert(asset, username, alert_dic) + + +def excel_to_db(excel_file): + """ + Asset add batch function + """ + try: + data = xlrd.open_workbook(filename=None, file_contents=excel_file.read()) + except Exception, e: + return False + + else: + table = data.sheets()[0] + rows = table.nrows + group_instance = [] + for row_num in range(1, rows): + row = table.row_values(row_num) + if row: + ip, port, hostname, use_default_auth, username, password, group = row + print ip + use_default_auth = 1 if use_default_auth == u'默认' else 0 + if get_object(Asset, ip=ip): + continue + if ip and port: + asset = Asset(ip=ip, + port=port, + use_default_auth=use_default_auth, + username=username, + password=password + ) + asset.save() + group_list = group.split('/') + for group_name in group_list: + group = get_object(AssetGroup, name=group_name) + if group: + group_instance.append(group) + if group_instance: + print group_instance + asset.group = group_instance + asset.save() + return True diff --git a/jasset/forms.py b/jasset/forms.py new file mode 100644 index 000000000..f9af4d499 --- /dev/null +++ b/jasset/forms.py @@ -0,0 +1,32 @@ +# coding:utf-8 +from django import forms + +from jasset.models import IDC, Asset, AssetGroup + + +class AssetForm(forms.ModelForm): + + class Meta: + model = Asset + + fields = [ + "ip", "other_ip", "hostname", "port", "group", "username", "password", "use_default_auth", + "idc", "mac", "remote_ip", "brand", "cpu", "memory", "disk", "system_type", "system_version", + "cabinet", "position", "number", "status", "asset_type", "env", "sn", "is_active", "comment" + ] + + +class AssetGroupForm(forms.ModelForm): + class Meta: + model = AssetGroup + fields = [ + "name", "comment" + ] + + +class IdcForm(forms.ModelForm): + class Meta: + model = IDC + fields = ['name', "bandwidth", "operator", 'linkman', 'phone', 'address', 'network', 'comment'] + + diff --git a/jasset/models.py b/jasset/models.py index 413387281..ede5a6436 100644 --- a/jasset/models.py +++ b/jasset/models.py @@ -1,6 +1,25 @@ +# coding: utf-8 + import datetime from django.db import models -# from juser.models import User, UserGroup +from juser.models import User, UserGroup + +ASSET_ENV = ( + (1, U'生产环境'), + (2, U'测试环境') + ) + +ASSET_STATUS = ( + (1, u"已使用"), + (2, u"未使用"), + (3, u"报废") + ) + +ASSET_TYPE = ( + (1, u"服务器"), + (2, u"网络设备"), + (3, u"其他") + ) class AssetGroup(models.Model): @@ -14,86 +33,74 @@ class AssetGroup(models.Model): def __unicode__(self): return self.name - # def get_asset(self): - # return self.asset_set.all() - # - # def get_asset_info(self, printable=False): - # assets = self.get_asset() - # ip_comment = {} - # for asset in assets: - # ip_comment[asset.ip] = asset.comment - # - # for ip in sorted(ip_comment): - # if ip_comment[ip]: - # print '%-15s -- %s' % (ip, ip_comment[ip]) - # else: - # print '%-15s' % ip - # print '' - # - # def get_asset_num(self): - # return len(self.get_asset()) - # - # def get_user_group(self): - # perm_list = self.perm_set.all() - # user_group_list = [] - # for perm in perm_list: - # user_group_list.append(perm.user_group) - # return user_group_list - # - # def get_user(self): - # user_list = [] - # user_group_list = self.get_user_group() - # for user_group in user_group_list: - # user_list.extend(user_group.user_set.all()) - # return user_list - # - # def is_permed(self, user=None, user_group=None): - # if user: - # if user in self.get_user(): - # return True - # - # if user_group: - # if user_group in self.get_user_group(): - # return True - # return False + +class IDC(models.Model): + name = models.CharField(max_length=32, verbose_name=u'机房名称') + bandwidth = models.CharField(max_length=32, blank=True, null=True, verbose_name=u'机房带宽') + linkman = models.CharField(max_length=16, null=True, verbose_name=u'联系人') + phone = models.CharField(max_length=32, verbose_name=u'联系电话') + address = models.CharField(max_length=128, blank=True, null=True, verbose_name=u"机房地址") + network = models.TextField(blank=True, null=True, verbose_name=u"IP地址段") + date_added = models.DateField(auto_now=True, default=datetime.datetime.now(), null=True) + operator = models.IntegerField(max_length=32, blank=True, null=True, verbose_name=u"运营商") + comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=u"备注") + + def __unicode__(self): + return self.name + + class Meta: + verbose_name = u"IDC机房" + verbose_name_plural = verbose_name class Asset(models.Model): - ip = models.GenericIPAddressField(unique=True) - port = models.IntegerField() - group = models.ManyToManyField(AssetGroup) - username = models.CharField(max_length=20, blank=True, null=True) - password = models.CharField(max_length=80, blank=True, null=True) - use_default_auth = models.BooleanField(default=True) - date_added = models.DateTimeField(auto_now_add=True) - is_active = models.BooleanField(default=True) - comment = models.CharField(max_length=100, blank=True, null=True) + """ + asset modle + """ + ip = models.IPAddressField(unique=True, verbose_name=u"主机IP") + other_ip = models.CharField(max_length=255, blank=True, null=True, verbose_name=u"其他IP") + hostname = models.CharField(max_length=64, blank=True, null=True, verbose_name=u"主机名") + port = models.IntegerField(max_length=6, verbose_name=u"端口号") + group = models.ManyToManyField(AssetGroup, blank=True, null=True, verbose_name=u"所属主机组") + username = models.CharField(max_length=16, blank=True, null=True, verbose_name=u"管理用户名") + password = models.CharField(max_length=64, blank=True, null=True, verbose_name=u"密码") + use_default_auth = models.BooleanField(default=True, verbose_name=u"使用默认管理账号") + idc = models.ForeignKey(IDC, blank=True, null=True, on_delete=models.SET_NULL, verbose_name=u'机房') + mac = models.CharField(max_length=20, blank=True, null=True, verbose_name=u"MAC地址") + remote_ip = models.CharField(max_length=16, blank=True, null=True, verbose_name=u'远控卡') + brand = models.CharField(max_length=64, blank=True, null=True, verbose_name=u'硬件厂商型号') + cpu = models.CharField(max_length=64, blank=True, null=True, verbose_name=u'CPU') + memory = models.CharField(max_length=128, blank=True, null=True, verbose_name=u'内存') + disk = models.CharField(max_length=128, blank=True, null=True, verbose_name=u'硬盘') + system_type = models.CharField(max_length=32, blank=True, null=True, verbose_name=u"系统类型") + system_version = models.CharField(max_length=8, blank=True, null=True, verbose_name=u"版本号") + cabinet = models.CharField(max_length=32, blank=True, null=True, verbose_name=u'机柜号') + position = models.IntegerField(max_length=2, blank=True, null=True, verbose_name=u'机器位置') + number = models.CharField(max_length=32, blank=True, null=True, verbose_name=u'资产编号') + status = models.IntegerField(max_length=2, choices=ASSET_STATUS, blank=True, null=True, default=1, verbose_name=u"机器状态") + asset_type = models.IntegerField(max_length=2, choices=ASSET_TYPE, blank=True, null=True, verbose_name=u"主机类型") + env = models.IntegerField(max_length=2, choices=ASSET_ENV, blank=True, null=True, verbose_name=u"运行环境") + sn = models.CharField(max_length=128, blank=True, null=True, verbose_name=u"SN编号") + date_added = models.DateTimeField(auto_now=True, default=datetime.datetime.now(), null=True) + is_active = models.BooleanField(default=True, verbose_name=u"是否激活") + comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=u"备注") def __unicode__(self): return self.ip - # def get_user(self): - # perm_list = [] - # asset_group_all = self.bis_group.all() - # for asset_group in asset_group_all: - # perm_list.extend(asset_group.perm_set.all()) - # - # user_group_list = [] - # for perm in perm_list: - # user_group_list.append(perm.user_group) - # - # user_permed_list = [] - # for user_group in user_group_list: - # user_permed_list.extend(user_group.user_set.all()) - # user_permed_list = list(set(user_permed_list)) - # return user_permed_list + +class AssetRecord(models.Model): + asset = models.ForeignKey(Asset) + username = models.CharField(max_length=30, null=True) + alert_time = models.DateTimeField(auto_now_add=True) + content = models.TextField(null=True, blank=True) + comment = models.TextField(null=True, blank=True) class AssetAlias(models.Model): - pass -# user = models.ForeignKey(User) -# asset = models.ForeignKey(Asset) -# alias = models.CharField(max_length=100, blank=True, null=True) -# -# def __unicode__(self): -# return self.alias + user = models.ForeignKey(User) + asset = models.ForeignKey(Asset) + alias = models.CharField(max_length=100, blank=True, null=True) + + def __unicode__(self): + return self.alias diff --git a/jasset/urls.py b/jasset/urls.py index a3df6dbec..c9ec8c462 100644 --- a/jasset/urls.py +++ b/jasset/urls.py @@ -4,23 +4,27 @@ from jasset.views import * urlpatterns = patterns('', url(r'^asset_add/$', asset_add), - # url(r"^host_add_multi/$", host_add_batch), - url(r'^group_add/$', group_add), - url(r'^group_list/$', group_list), + url(r"^asset_add_batch/$", asset_add_batch), url(r'^group_del/$', group_del), url(r'^asset_list/$', asset_list), url(r'^asset_del/$', asset_del), url(r"^asset_detail/$", asset_detail), url(r'^asset_edit/$', asset_edit), + url(r'^asset_update/$', asset_update), # url(r'^search/$', host_search), - # url(r"^host_detail/$", host_detail), - # url(r"^dept_host_ajax/$", dept_host_ajax), # url(r"^show_all_ajax/$", show_all_ajax), - # url(r'^group_edit/$', group_edit), - # url(r'^group_list/$', group_list), - # url(r'^group_detail/$', group_detail), + url(r'^group_add/$', group_add), + url(r'^group_list/$', group_list), + url(r'^group_edit/$', group_edit), + url(r'^group_list/$', group_list), + url(r'^group_detail/$', group_detail), # url(r'^group_del_host/$', group_del_host), - - # url(r'^host_edit/batch/$', host_edit_batch), + url(r'^asset_edit_batch/$', asset_edit_batch), # url(r'^host_edit_common/batch/$', host_edit_common_batch), + url(r'^idc_add/$', idc_add), + url(r'^idc_list/$', idc_list), + url(r'^idc_detail/$', idc_detail), + url(r'^idc_edit/$', idc_edit), + url(r'^idc_del/$', idc_del), + url(r'^upload/$', asset_upload), ) \ No newline at end of file diff --git a/jasset/views.py b/jasset/views.py index 57f88fdbe..6432ca5f5 100644 --- a/jasset/views.py +++ b/jasset/views.py @@ -1,13 +1,13 @@ # coding:utf-8 import ast - from django.db.models import Q -from django.template import RequestContext from django.shortcuts import get_object_or_404 - from jasset.asset_api import * from jumpserver.api import * +from jasset.forms import AssetForm, IdcForm +from jasset.models import Asset, IDC, AssetGroup, ASSET_TYPE, ASSET_STATUS +from ansible_api import Tasks @require_role('admin') @@ -36,13 +36,69 @@ def group_add(request): except ServerError: pass + else: db_add_group(name=name, comment=comment, asset_select=asset_select) - msg = u"主机组 %s 添加成功" % name + smg = u"主机组 %s 添加成功" % name return my_render('jasset/group_add.html', locals(), request) +@require_role('admin') +def group_edit(request): + """ + Edit asset group + 编辑资产组 + """ + header_title, path1, path2 = u'编辑主机组', u'资产管理', u'编辑主机组' + group_id = request.GET.get('id', '') + group = get_object(AssetGroup, id=group_id) + + asset_all = Asset.objects.all() + asset_select = Asset.objects.filter(group=group) + asset_no_select = [a for a in asset_all if a not in asset_select] + + if request.method == 'POST': + name = request.POST.get('name', '') + asset_select = request.POST.getlist('asset_select', []) + comment = request.POST.get('comment', '') + + try: + if not name: + emg = u'组名不能为空' + raise ServerError(emg) + + if group.name != name: + asset_group_test = get_object(AssetGroup, name=name) + if asset_group_test: + emg = u"该组名 %s 已存在" % name + raise ServerError(emg) + + except ServerError: + pass + + else: + group.asset_set.clear() + db_update_group(id=group_id, name=name, comment=comment, asset_select=asset_select) + smg = u"主机组 %s 添加成功" % name + + return HttpResponseRedirect('/jasset/group_list') + + return my_render('jasset/group_edit.html', locals(), request) + + +@require_role('admin') +def group_detail(request): + """ 主机组详情 """ + header_title, path1, path2 = u'主机组详情', u'资产管理', u'主机组详情' + group_id = request.GET.get('id', '') + group = get_object(AssetGroup, id=group_id) + asset_all = Asset.objects.filter(group=group).order_by('ip') + + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(asset_all, request) + return my_render('jasset/group_detail.html', locals(), request) + + @require_role('admin') def group_list(request): """ @@ -85,23 +141,13 @@ def asset_add(request): """ header_title, path1, path2 = u'添加资产', u'资产管理', u'添加资产' asset_group_all = AssetGroup.objects.all() + af = AssetForm() if request.method == 'POST': - ip = request.POST.get('ip') - groups = request.POST.getlist('groups') - use_default = True if request.POST.getlist('use_default', []) else False - is_active = True if request.POST.get('is_active') else False - comment = request.POST.get('comment') - - if not use_default: - username = request.POST.get('username') - password = request.POST.get('password') - port = request.POST.get('port') - password_encode = password - else: - username = None - port = None - password_encode = None - + af_post = AssetForm(request.POST) + print af_post + ip = request.POST.get('ip', '') + is_active = True if request.POST.get('is_active') == '1' else False + use_default_auth = request.POST.get('use_default_auth', '') try: if Asset.objects.filter(ip=str(ip)): error = u'该IP %s 已存在!' % ip @@ -110,34 +156,27 @@ def asset_add(request): except ServerError: pass else: - db_asset_add( - ip=ip, port=port, use_default=use_default, is_active=is_active, comment=comment, - groups=groups, username=username, password=password_encode - ) + if af_post.is_valid(): + asset_save = af_post.save(commit=False) + if not use_default_auth: + password = request.POST.get('password', '') + password_encode = CRYPTOR.encrypt(password) + asset_save.password = password_encode + asset_save.is_active = True if is_active else False + asset_save.save() + af_post.save_m2m() - msg = u'主机 %s 添加成功' % ip + msg = u'主机 %s 添加成功' % ip + else: + esg = u'主机 %s 添加失败' % ip return my_render('jasset/asset_add.html', locals(), request) -@require_role(role='user') -def asset_list(request): - """ - list assets - 列出资产表 - """ - header_title, path1, path2 = u'查看主机', u'资产管理', u'查看主机' - keyword = request.GET.get('keyword', '') - gid = request.GET.get('gid', '') # asset group id - sid = request.GET.get('sid', '') - assets_list = Asset.objects.all().order_by('ip') - - if keyword: - assets_list = assets_list.filter(Q(ip__contains=keyword) | - Q(comment__contains=keyword)).distinct().order_by('ip') - - assets_list, p, assets, page_range, current_page, show_first, show_end = pages(assets_list, request) - return my_render('jasset/asset_list.html', locals(), request) +@require_role('admin') +def asset_add_batch(request): + header_title, path1, path2 = u'添加资产', u'资产管理', u'批量添加' + return my_render('jasset/asset_add_batch.html', locals(), request) @require_role('admin') @@ -149,64 +188,279 @@ def asset_del(request): asset_id = request.GET.get('id', '') if asset_id: Asset.objects.filter(id=asset_id).delete() - return HttpResponse(u'删除成功') - return Http404 + + if request.method == 'POST': + asset_batch = request.GET.get('arg', '') + asset_id_all = str(request.POST.get('asset_id_all', '')) + + if asset_batch: + for asset_id in asset_id_all.split(','): + asset = get_object(Asset, id=asset_id) + asset.delete() + + return HttpResponse(u'删除成功') @require_role(role='super') def asset_edit(request): - """ 修改主机 """ + """ + edit a asset + 修改主机 + """ header_title, path1, path2 = u'修改资产', u'资产管理', u'修改资产' asset_id = request.GET.get('id', '') - if not asset_id: - return HttpResponse('没有该主机') + username = request.session.get('username', 'admin') asset = get_object(Asset, id=asset_id) - + asset_old = copy_model_instance(asset) + af = AssetForm(instance=asset) if request.method == 'POST': - ip = request.POST.get('ip') - groups = request.POST.getlist('groups') - use_default = True if request.POST.getlist('use_default', []) else False - is_active = True if request.POST.get('is_active') else False - comment = request.POST.get('comment') - - if not use_default: - username = request.POST.get('username') - password = request.POST.get('password') - port = request.POST.get('port') - if password == asset.password: - password_encode = password - else: - password_encode = CRYPTOR.encrypt(password) - else: - username = None - password_encode = None - port = 22 - + af_post = AssetForm(request.POST, instance=asset) + ip = request.POST.get('ip', '') + use_default_auth = request.POST.get('use_default_auth') try: asset_test = get_object(Asset, ip=ip) - if asset_test and asset_id != str(asset_test.id): + if asset_test and asset_id != unicode(asset_test.id): error = u'该IP %s 已存在!' % ip raise ServerError(error) except ServerError: pass else: - db_asset_update(id=asset_id, ip=ip, port=port, use_default=use_default, - username=username, password=password_encode, - is_active=is_active, comment=comment) - msg = u'主机 %s 修改成功' % ip + if af_post.is_valid(): + af_save = af_post.save(commit=False) + if use_default_auth: + af_save.username = '' + af_save.password = '' + af_save.save() + af_post.save_m2m() + # asset_new = get_object(Asset, id=asset_id) + # asset_diff_one(asset_old, asset_new) + info = asset_diff(af_post.__dict__.get('initial'), request.POST) + db_asset_alert(asset, username, info) + + msg = u'主机 %s 修改成功' % ip + else: + emg = u'主机 %s 修改失败' % ip return HttpResponseRedirect('/jasset/asset_detail/?id=%s' % asset_id) return my_render('jasset/asset_edit.html', locals(), request) +@require_role('user') +def asset_list(request): + """ + asset list view + """ + idc_all = IDC.objects.filter() + asset_group_all = AssetGroup.objects.all() + asset_types = ASSET_TYPE + asset_status = ASSET_STATUS + + idc_name = request.GET.get('idc', '') + group_name = request.GET.get('group', '') + asset_type = request.GET.get('asset_type', '') + status = request.GET.get('status', '') + keyword = request.GET.get('keyword', '') + export = request.GET.get("export", False) + + asset_find = Asset.objects.all() + if idc_name: + asset_find = asset_find.filter(idc__name__contains=idc_name) + + if group_name: + asset_find = asset_find.filter(group__name__contains=group_name) + + if asset_type: + asset_find = asset_find.filter(asset_type__contains=asset_type) + + if status: + asset_find = asset_find.filter(status__contains=status) + + if keyword: + asset_find = asset_find.filter( + Q(hostname__contains=keyword) | + Q(other_ip__contains=keyword) | + Q(ip__contains=keyword) | + Q(remote_ip__contains=keyword) | + Q(comment__contains=keyword) | + Q(group__name__contains=keyword) | + Q(cpu__contains=keyword) | + Q(memory__contains=keyword) | + Q(disk__contains=keyword)) + + if export: + s = write_excel(asset_find) + if s[0]: + file_name = s[1] + smg = 'excel文件已生成,请点击下载!' + return my_render('jasset/asset_excel_download.html', locals(), request) + assets_list, p, assets, page_range, current_page, show_first, show_end = pages(asset_find, request) + return my_render('jasset/asset_list.html', locals(), request) + + +@require_role('admin') +def asset_edit_batch(request): + af = AssetForm() + asset_group_all = AssetGroup.objects.all() + return my_render('jasset/asset_edit_batch.html', locals(), request) + + @require_role('admin') def asset_detail(request): - """ 主机详情 """ + """ + Asset detail view + """ header_title, path1, path2 = u'主机详细信息', u'资产管理', u'主机详情' asset_id = request.GET.get('id', '') asset = get_object(Asset, id=asset_id) + asset_record = AssetRecord.objects.filter(asset=asset).order_by('-alert_time') return my_render('jasset/asset_detail.html', locals(), request) +@require_role('admin') +def asset_update(request): + """ + Asset update host info via ansible view + """ + asset_id = request.GET.get('id', '') + asset = get_object(Asset, id=asset_id) + if not asset: + return HttpResponseRedirect('/jasset/asset_detail/?id=%s' % asset_id) + name = request.session.get('username', 'admin') + if asset.use_default_auth: + username = 'root' + password = '123456' + else: + username = asset.username + password = asset.password + + resource = [{"hostname": asset.ip, "port": asset.port, + "username": username, "password": password}] + + ansible_instance = Tasks(resource) + ansible_asset_info = ansible_instance.get_host_info() + if ansible_asset_info['status'] == 'ok': + asset_info = ansible_asset_info['result'][asset.ip] + if asset_info: + hostname = asset_info.get('hostname') + other_ip = ','.join(asset_info.get('other_ip')) + cpu_type = asset_info.get('cpu_type')[1] + cpu_cores = asset_info.get('cpu_cores') + cpu = cpu_type + ' * ' + unicode(cpu_cores) + memory = asset_info.get('memory') + disk = asset_info.get('disk') + sn = asset_info.get('sn') + brand = asset_info.get('brand') + system_type = asset_info.get('system_type') + system_version = asset_info.get('system_version') + + asset_dic = {"hostname": hostname, "other_ip": other_ip, "cpu": cpu, + "memory": memory, "disk": disk, "system_type": system_type, + "system_version": system_version, "brand": brand, "sn": sn + } + + ansible_record(asset, asset_dic, name) + + return HttpResponseRedirect('/jasset/asset_detail/?id=%s' % asset_id) + + +@require_role('admin') +def idc_add(request): + """ + IDC add view + """ + header_title, path1, path2 = u'添加IDC', u'资产管理', u'添加IDC' + if request.method == 'POST': + idc_form = IdcForm(request.POST) + if idc_form.is_valid(): + idc_name = idc_form.cleaned_data['name'] + + if IDC.objects.filter(name=idc_name): + emg = u'添加失败, 此IDC %s 已存在!' % idc_name + return my_render('jasset/idc_add.html', locals(), request) + else: + idc_form.save() + smg = u'IDC: %s添加成功' % idc_name + return HttpResponseRedirect("/jasset/idc_list/") + else: + idc_form = IdcForm() + return render_to_response('jasset/idc_add.html', + locals(), + context_instance=RequestContext(request)) + + +@require_role('admin') +def idc_list(request): + """ + IDC list view + """ + header_title, path1, path2 = u'查看IDC', u'资产管理', u'查看IDC' + posts = IDC.objects.all() + keyword = request.GET.get('keyword', '') + if keyword: + posts = IDC.objects.filter(Q(name__contains=keyword) | Q(comment__contains=keyword)) + else: + posts = IDC.objects.exclude(name='ALL').order_by('id') + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) + return render_to_response('jasset/idc_list.html', + locals(), + context_instance=RequestContext(request)) + + +@require_role('admin') +def idc_edit(request): + """ + IDC edit view + """ + header_title, path1, path2 = u'编辑IDC', u'资产管理', u'编辑IDC' + idc_id = request.GET.get('id', '') + idc = get_object(IDC, id=idc_id) + if request.method == 'POST': + idc_form = IdcForm(request.POST, instance=idc) + if idc_form.is_valid(): + idc_form.save() + return HttpResponseRedirect("/jasset/idc_list/") + else: + idc_form = IdcForm(instance=idc) + return my_render('jasset/idc_edit.html', locals(), request) + + +@require_role('admin') +def idc_detail(request): + """ + IDC detail view + """ + header_title, path1, path2 = u'IDC详情', u'资产管理', u'IDC详情' + idc_id = request.GET.get('id', '') + idc = get_object(IDC, id=idc_id) + posts = Asset.objects.filter(idc=idc).order_by('ip') + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) + + return my_render('jasset/idc_detail.html', locals(), request) + + +@require_role('admin') +def idc_del(request): + """ + IDC delete view + """ + uuid = request.GET.get('uuid', '') + idc = get_object_or_404(IDC, uuid=uuid) + idc.delete() + return HttpResponseRedirect('/jasset/idc_list/') + + +@require_role('admin') +def asset_upload(request): + """ + Upload file view + """ + if request.method == 'POST': + excel_file = request.FILES.get('file_name', '') + ret = excel_to_db(excel_file) + if ret: + smg = u'批量添加成功' + else: + emg = u'批量添加失败,请检查格式.' + return my_render('jasset/asset_add_batch.html', locals(), request) diff --git a/jumpserver.conf b/jumpserver.conf index 1a6a23fce..7261fcf38 100644 --- a/jumpserver.conf +++ b/jumpserver.conf @@ -15,8 +15,6 @@ database = jumpserver [websocket] web_socket_host = 127.0.0.1:3000 - - [mail] mail_enable = 1 email_host = smtp.exmail.qq.com diff --git a/jumpserver/settings.py b/jumpserver/settings.py index 942d6808f..fa752d4a9 100644 --- a/jumpserver/settings.py +++ b/jumpserver/settings.py @@ -65,12 +65,12 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.humanize', + 'bootstrapform', 'jumpserver', 'juser', 'jasset', 'jperm', 'jlog', - ) MIDDLEWARE_CLASSES = ( @@ -146,3 +146,5 @@ USE_TZ = False # https://docs.djangoproject.com/en/1.7/howto/static-files/ STATIC_URL = '/static/' + +BOOTSTRAP_COLUMN_COUNT = 10 diff --git a/jumpserver/templatetags/mytags.py b/jumpserver/templatetags/mytags.py index 52da56a05..47ab3a93a 100644 --- a/jumpserver/templatetags/mytags.py +++ b/jumpserver/templatetags/mytags.py @@ -182,3 +182,132 @@ def asset_which_group(asset, member): names = [members.name for members in member.all()] return ','.join(names) +# +# +# @register.filter(name='get_user_asset_group') +# def get_user_asset_group(user): +# return user.get_asset_group() +# +# +# @register.filter(name='group_asset_list') +# def group_asset_list(group): +# return group.asset_set.all() +# +# +# @register.filter(name='group_asset_list_count') +# def group_asset_list_count(group): +# return group.asset_set.all().count() +# +# +# @register.filter(name='time_delta') +# def time_delta(time_before): +# delta = datetime.datetime.now() - time_before +# days = delta.days +# if days: +# return "%s 天前" % days +# else: +# hours = delta.seconds/3600 +# if hours: +# return "%s 小时前" % hours +# else: +# mins = delta.seconds/60 +# if mins: +# return '%s 分钟前' % mins +# else: +# return '%s 秒前' % delta.seconds +# +# +# @register.filter(name='sudo_cmd_list') +# def sudo_cmd_list(cmd_group_id): +# cmd_group = CmdGroup.objects.filter(id=cmd_group_id) +# if cmd_group: +# cmd_group = cmd_group[0] +# return cmd_group.cmd.split(',') +# +# +# @register.filter(name='sudo_cmd_count') +# def sudo_cmd_count(user_group_id): +# user_group = UserGroup.objects.filter(id=user_group_id) +# cmds = [] +# if user_group: +# user_group = user_group[0] +# cmd_groups = [] +# +# for perm in user_group.sudoperm_set.all(): +# cmd_groups.extend(perm.cmd_group.all()) +# +# for cmd_group in cmd_groups: +# cmds.extend(cmd_group.cmd.split(',')) +# return len(set(cmds)) +# +# else: +# return 0 +# +# +# @register.filter(name='sudo_cmd_count') +# def sudo_cmd_count(user_group_id): +# user_group = UserGroup.objects.filter(id=user_group_id) +# cmds = [] +# if user_group: +# user_group = user_group[0] +# cmd_groups = [] +# for perm in user_group.sudoperm_set.all(): +# cmd_groups.extend(perm.cmd_group.all()) +# +# for cmd_group in cmd_groups: +# cmds.extend(cmd_group.cmd.split(',')) +# return len(set(cmds)) +# else: +# return 0 +# +# +# @register.filter(name='sudo_cmd_ids') +# def sudo_cmd_ids(user_group_id): +# user_group = UserGroup.objects.filter(id=user_group_id) +# if user_group: +# user_group = user_group[0] +# cmd_groups = [] +# for perm in user_group.sudoperm_set.all(): +# cmd_groups.extend(perm.cmd_group.all()) +# cmd_ids = [str(cmd_group.id) for cmd_group in cmd_groups] +# return ','.join(cmd_ids) +# else: +# return '0' +# +# +# @register.filter(name='cmd_group_split') +# def cmd_group_split(cmd_group): +# return cmd_group.cmd.split(',') + + +@register.filter(name='str_to_list') +def str_to_list(info): + """ + str to list + """ + print ast.literal_eval(info), type(ast.literal_eval(info)) + return ast.literal_eval(info) + + +@register.filter(name='str_to_dic') +def str_to_dic(info): + """ + str to list + """ + return ast.literal_eval(info).iteritems() + + +@register.filter(name='str_to_code') +def str_to_code(char_str): + if char_str: + return char_str + else: + return u'空' + + +@register.filter(name='ip_str_to_list') +def ip_str_to_list(ip_str): + """ + ip str to list + """ + return ip_str.split(',') diff --git a/static/css/style.css b/static/css/style.css index ee9d0978a..1257e3571 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -4562,3 +4562,8 @@ body.skin-3 { .red-fonts { color: #ed5565; } + +.form-group.required .control-label:after { + content: " *"; + color: red; +} \ No newline at end of file diff --git a/static/js/base.js b/static/js/base.js index be5b3cade..b6ac16196 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -135,3 +135,11 @@ function selectAll(){ // }) //} +function getIDall() { + var check_array = []; + $(".gradeX input:checked").each(function () { + var id = $(this).attr("value"); + check_array.push(id); + }); + return check_array.join(","); +} \ No newline at end of file diff --git a/templates/jasset/asset_add.html b/templates/jasset/asset_add.html index 13125ca0d..46ea10bfe 100644 --- a/templates/jasset/asset_add.html +++ b/templates/jasset/asset_add.html @@ -1,5 +1,6 @@ {% extends 'base.html' %} {% load mytags %} +{% load bootstrap %} {% block content %} {% include 'nav_cat_bar.html' %}