├── README.md ├── run.py ├── xk_application ├── __init__.py ├── xk_app.py └── xk_main.py ├── xk_config ├── __init__.py ├── xk_setting.sample.py └── xk_url.py ├── xk_db_sql └── xk_dnsmasq.sql ├── xk_handler ├── __init__.py ├── xk_dhcp.py ├── xk_dns.py ├── xk_index.py ├── xk_login.py ├── xk_public.py ├── xk_test.py └── xk_users.py ├── xk_html ├── xk_dhcp_host.html ├── xk_dhcp_pool.html ├── xk_domain.html ├── xk_footer.html ├── xk_index.html ├── xk_login.html ├── xk_nav.html ├── xk_record.html ├── xk_top_nav.html ├── xk_users.html └── xk_users_logs.html ├── xk_screenshot ├── xk_dashboard.png ├── xk_dhcp.png ├── xk_domain.png ├── xk_login.png └── xk_record.png └── xk_static ├── bootstrap ├── css │ ├── bootstrap-responsive.css │ ├── bootstrap-responsive.min.css │ ├── bootstrap.css │ └── bootstrap.min.css ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png └── js │ ├── bootstrap.js │ └── bootstrap.min.js ├── css ├── bootstrap-switch.css ├── xk_dnsmasqweb.css └── xk_login.css ├── images ├── 160x120.gif ├── 260x180.gif ├── 360x268.gif ├── 670x240.gif └── login_bg.png └── js ├── bootstrap-switch.js ├── html5shiv.min.js ├── jquery-1.8.3.min.js ├── xk_dhcp.js ├── xk_dnsmasqweb.js └── xk_users.js /README.md: -------------------------------------------------------------------------------- 1 | DNSmasqWeb 2 | ========== 3 | 4 | 基于DNSmasq的开源轻量级DNS解析、DHCP地址分配的开源系统 5 | 6 | Desgin By [Xiaok](http://github.luxiaok.com) 7 | 8 | 9 | ## 【Python运维圈】微信公众号 ## 10 | 11 | ![Python运维圈](https://raw.githubusercontent.com/luxiaok/SaltAdmin/master/static/images/ops_circle_qrcode.jpg) 12 | 13 | >也可以微信搜索 **Python运维圈** 14 | 15 | 16 | ## 技术交流QQ群 ## 17 | 18 | **459457262** 19 | 20 | >加群时请注明来自 **Github** 21 | 22 | 23 | ## 一、应用说明 ## 24 | * 本系统可同时提供DNS解析功能和DHCP地址分配功能 25 | * 本系统可以用于机房内网、公司内网、家庭内网等类似内部网络环境 26 | * 系统基于DNSmasq,Web端基于Python语言和Tornado框架 27 | 28 | ## 二、运行环境 ## 29 | * OS:RHEL 6.5 x64 30 | * Python:2.7.8 31 | * DnsMasq:2.72 32 | * Tornado:4.0.2 33 | * Jinja2:2.7.3 34 | 35 | 注意:以上是测试运行正常的环境,其他环境请自行测试 36 | 37 | ## 三、DNSmasq配置说明 ## 38 | * 常规安装(版本:2.48) 39 | 40 | `yum -y install dnsmasq` 41 | 42 | `chkconfig dnsmasq on` 43 | 44 | * 编辑安装(版本:2.72) 45 | 46 | `wget http://www.thekelleys.org.uk/dnsmasq/dnsmasq-2.72.tar.gz` 47 | 48 | `tar zxf dnsmasq-2.72.tar.gz` 49 | 50 | `cd dnsmasq-2.72` 51 | 52 | `vim Makefile` 53 | 54 | `PREFIX = /usr/local/dnsmasq` 55 | 56 | `make && make install` 57 | 58 | `cp dnsmasq.conf.example /etc/dnsmasq.conf` 59 | 60 | `ln -s /usr/local/dnsmasq/sbin/dnsmasq /usr/sbin/` 61 | 62 | `dnsmasq --version` 63 | 64 | * 主配文件:/etc/dnsmasq.conf 65 | 66 | `resolv-file=/etc/dnsmasq.resolv.conf` 67 | 68 | `addn-hosts=/etc/dnsmasq.hosts` 69 | 70 | `conf-dir=/etc/dnsmasq.d` 71 | 72 | ## 四、Web配置 ## 73 | * 安装Tornado 74 | 75 | `easy_install tornado` 76 | 77 | * 安装jinja2 78 | 79 | `easy_install tornado` 80 | 81 | * 安装数据库驱动 82 | 83 | `yum -y install MySQL-python` 84 | 85 | `easy_install torndb` 86 | 87 | * 导入数据库文件 88 | 89 | `mysql> create database xk_dnsmasq;` 90 | 91 | `mysql> use xk_dnsmasq;` 92 | 93 | `mysql> source xk_db_sql/xk_dnsmasq.sql;` 94 | 95 | * 配置Web 96 | 97 | `cp xk_config/xk_setting.sample.py xk_config/xk_setting.py` 98 | 99 | 在文件xk_config/xk_setting.py设置MySQL的主机、端口、用户名、密码 100 | 101 | * 启动Web端 102 | 103 | `python run.py` 104 | 105 | 默认用户名/密码:admin/admin 106 | 107 | 默认端口:9886 108 | 109 | ## 五、截图 ## 110 | 111 | * 登录页面 112 | 113 | ![DnsMasqWeb Login](https://github.com/luxiaok/DNSmasqWeb/raw/master/xk_screenshot/xk_login.png) 114 | 115 | * 控制中心 116 | 117 | ![DnsMasqWeb Dashboard](https://github.com/luxiaok/DNSmasqWeb/raw/master/xk_screenshot/xk_dashboard.png) 118 | 119 | * 域名管理 120 | 121 | ![DnsMasqWeb Domain](https://github.com/luxiaok/DNSmasqWeb/raw/master/xk_screenshot/xk_domain.png) 122 | 123 | * DNS记录管理 124 | 125 | ![DnsMasqWeb Record](https://github.com/luxiaok/DNSmasqWeb/raw/master/xk_screenshot/xk_record.png) 126 | 127 | * DHCP管理 128 | 129 | ![DnsMasqWeb DHCP](https://github.com/luxiaok/DNSmasqWeb/raw/master/xk_screenshot/xk_dhcp.png) -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # DNSmasq Web Admin 4 | # Desgin By Xiaok 5 | # 2014-11-23 02:18:50 6 | 7 | from xk_application.xk_app import * 8 | 9 | if __name__ == "__main__": 10 | main() 11 | -------------------------------------------------------------------------------- /xk_application/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiaok/DNSmasqWeb/26989d1fd2583bdc74047a9119379aaf20c9b0f5/xk_application/__init__.py -------------------------------------------------------------------------------- /xk_application/xk_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # Desgin By Xiaok 4 | import torndb 5 | import tornado.httpserver 6 | import tornado.ioloop 7 | import tornado.web 8 | import tornado.netutil 9 | import tornado.process 10 | import time 11 | from xk_config.xk_setting import * 12 | from xk_config.xk_url import * 13 | 14 | MainSetting = dict( 15 | template_path = 'xk_html', 16 | static_path = 'xk_static', 17 | static_url_prefix = '/xk_static/', 18 | xsrf_cookies = False, 19 | cookie_secret = "db884468559f4c432bf1c1775f3dc9da", 20 | login_url = "/login", 21 | debug = options.debug, 22 | autoreload = options.debug, 23 | ) 24 | 25 | class HttpApplication(tornado.web.Application): 26 | def __init__(self): 27 | handlers = HandlersURL 28 | settings = MainSetting 29 | tornado.web.Application.__init__(self, handlers, **settings) 30 | 31 | # Have one global connection to DB across all handlers 32 | self.db = torndb.Connection( 33 | host=options.mysql_host, database=options.mysql_database, 34 | user=options.mysql_user, password=options.mysql_password, 35 | time_zone='+8:00',charset='utf8') 36 | 37 | ping_db = lambda: self.db.query("select now()") 38 | #def print_test(): 39 | # print "Hello Test" 40 | # 每3分钟执行一次数据库查询,防止mysql gone away,时间间隔要小于msyql的wait_timeout时长 41 | tornado.ioloop.PeriodicCallback(ping_db,3 * 60 * 1000).start() 42 | #tornado.ioloop.PeriodicCallback(print_test,1 * 30 * 1000).start() 43 | 44 | def main(): 45 | if options.ipv6: 46 | host = None 47 | else: 48 | host = "0.0.0.0" 49 | tornado.options.parse_command_line() 50 | 51 | if options.debug: 52 | http_server = tornado.httpserver.HTTPServer(request_callback=HttpApplication(),xheaders=True) 53 | http_server.listen(options.port,host) 54 | now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) 55 | print '[%s] Listen On Port %s' % ( now, options.port ) 56 | else: 57 | http_sockets = tornado.netutil.bind_sockets(options.port,host) 58 | tornado.process.fork_processes(num_processes=options.processes) 59 | http_server = tornado.httpserver.HTTPServer(request_callback=HttpApplication(),xheaders=True) 60 | http_server.add_sockets(http_sockets) 61 | 62 | tornado.ioloop.IOLoop.instance().start() 63 | -------------------------------------------------------------------------------- /xk_application/xk_main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # Jinja2 For Tornado 4 | # Design By Xiaok 5 | # 2014-11-15 22:01:17 6 | import sys 7 | reload(sys) 8 | sys.setdefaultencoding('utf8') 9 | import os 10 | from tornado.web import RequestHandler 11 | from tornado.web import authenticated as Auth 12 | from jinja2 import Environment, FileSystemLoader, TemplateNotFound 13 | import datetime 14 | import time 15 | import functools 16 | from hashlib import md5 17 | 18 | class TemplateRendering: 19 | """ 20 | A simple class to hold methods for rendering templates. 21 | """ 22 | def render_template(self, template_name, **kwargs): 23 | template_dirs = [] 24 | if self.settings.get('template_path', ''): 25 | template_dirs.append( 26 | self.settings["template_path"] 27 | ) 28 | 29 | env = Environment(loader=FileSystemLoader(template_dirs),trim_blocks=True) # trim_blocks去除生成html产生的大量空行 30 | 31 | try: 32 | template = env.get_template(template_name) 33 | except TemplateNotFound: 34 | raise TemplateNotFound(template_name) 35 | content = template.render(kwargs) 36 | return content 37 | 38 | class BaseHandler(RequestHandler, TemplateRendering): 39 | # 自定义Header信息 40 | def set_default_headers(self): 41 | self.set_header("Server","XK-WebServer/2014") 42 | self.set_header("X-Powered-By","LuXiaok") 43 | self.set_header("Date",self.get_time()) 44 | 45 | @property 46 | def db(self): 47 | return self.application.db 48 | 49 | def get_current_user(self): 50 | username = self.get_secure_cookie("xk_auth_token") 51 | if not username: return None 52 | #return self.db.get("SELECT * FROM xk_users WHERE id = %s", int(user_id)) 53 | return username 54 | 55 | #@property 56 | def user_info(self): 57 | if self.current_user: 58 | user = self.db.get("SELECT id,username,name FROM xk_users WHERE username = %s", self.current_user) 59 | return user 60 | else: 61 | return None 62 | 63 | # 或者文件的MD5值 64 | def get_md5(self,file): 65 | m = md5() 66 | # 需要使用二进制格式读取文件内容 67 | f = open(file, 'rb') 68 | m.update(f.read()) 69 | f.close() 70 | return m.hexdigest() 71 | 72 | # 格式化时间 73 | def get_time(self,s=None): 74 | return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(s)) 75 | 76 | # 格式化文件大小 77 | def format_size(self,i): 78 | i = int(i) 79 | unit = 'Bytes' 80 | if i >= 1024: 81 | i = i / 1024.0 82 | unit = 'KB' 83 | if i >= 1024: 84 | i = i / 1024 85 | unit = 'MB' 86 | if i >= 1024: 87 | i = i / 1024 88 | unit = 'GB' 89 | else: 90 | return '%d %s' % (i,unit) 91 | return '%.2f %s' % (i,unit) 92 | 93 | # 格式化秒 94 | def format_seconds(self,s): 95 | s = int(s) 96 | D = 0 97 | H = 0 98 | M = 0 99 | S = s 100 | if S > 59: 101 | M = S / 60 102 | S = S % 60 103 | if M > 59: 104 | H = M / 60 105 | M = M % 60 106 | if H > 23: 107 | D = H / 24 108 | H = H % 24 109 | return { 'days':D, 'hours':H, 'minutes':M, 'seconds':S } 110 | 111 | """ 112 | RequestHandler already has a `render()` method. I'm writing another 113 | method `render2()` and keeping the API almost same. 114 | """ 115 | def render2(self, template_name, **kwargs): 116 | """ 117 | This is for making some extra context variables available to 118 | the template 119 | """ 120 | kwargs.update({ 121 | 'settings': self.settings, 122 | 'STATIC_URL': self.settings.get('static_url_prefix', '/xk_static/'), 123 | 'static_url': self.static_url, 124 | 'get_time': self.get_time, 125 | 'user_info': self.user_info, 126 | 'format_size': self.format_size, 127 | 'format_seconds': self.format_seconds, 128 | 'request': self.request, 129 | 'xsrf_token': self.xsrf_token, 130 | 'xsrf_form_html': self.xsrf_form_html, 131 | }) 132 | content = self.render_template(template_name, **kwargs) 133 | self.write(content) 134 | 135 | # 模块权限管理装饰器 136 | def Perm(method): 137 | @functools.wraps(method) 138 | def wrapper(self, *args, **kwargs): 139 | #### 权限控制 #### 140 | class_name = self.__class__.__name__ #获取类名,其实获取到了待用该方法的类名,方便更加精确的权限判断 141 | request_method = self.request.method #获取请求方法名:GET|POST 142 | #print "Class Name: %s" % class_name 143 | #print "Method: %s" % request_method 144 | permission = self.db.get("select is_admin from login_users where username = %s and status = 'yes'",self.current_user) 145 | if permission: 146 | is_admin = permission['is_admin'] 147 | if is_admin == "no": 148 | self.write(''' Sorry,您没有权限操作!''') 149 | return 150 | ########### 151 | return method(self, *args, **kwargs) 152 | return wrapper 153 | 154 | -------------------------------------------------------------------------------- /xk_config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiaok/DNSmasqWeb/26989d1fd2583bdc74047a9119379aaf20c9b0f5/xk_config/__init__.py -------------------------------------------------------------------------------- /xk_config/xk_setting.sample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # Desgin By Xiaok 4 | from tornado.options import define, options 5 | 6 | # HTTP Port Setting 7 | define("port", default=9886, help="Run on the given port", type=int) 8 | 9 | # Debug Setting 10 | define("debug", default=True, help="Debug Setting",type=bool) 11 | 12 | # IPv6 Setting 13 | define("ipv6", default=False, help="IPv6 Setting",type=bool) 14 | 15 | # Worker Processes Setting 16 | define("processes", default=4, help="Worker Processes Setting", type=int) 17 | 18 | # MySQL Config Options 19 | define("mysql_host", default="127.0.0.1:3306", help="Database Host and Port") 20 | define("mysql_database", default="xk_dnsmasq", help="Database Name") 21 | define("mysql_user", default="test", help="Database User") 22 | define("mysql_password", default="test", help="Database Password") 23 | -------------------------------------------------------------------------------- /xk_config/xk_url.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | #Design By Xiaok 4 | 5 | from xk_handler import * 6 | 7 | HandlersURL = [ 8 | (r"/(|login)/?", xk_login.LoginHandler), 9 | (r"/logout", xk_login.LogoutHandler), 10 | (r"/dashboard/?", xk_index.IndexHandler), 11 | (r"/dns/domain", xk_dns.DnsDomainHandler), 12 | (r"/dns/record", xk_dns.DnsRecordHandler), 13 | (r"/dhcp/pool", xk_dhcp.DhcpPoolHandler), 14 | (r"/dhcp/host", xk_dhcp.DhcpHostHandler), 15 | (r"/public/api", xk_public.PublicAPIHandler), 16 | (r"/users", xk_users.UsersHandler), 17 | (r"/users/logs", xk_users.LoginLogsHandler), 18 | (r"/test", xk_test.TestHandler), 19 | #(r"/(favicon\.ico)", tornado.web.StaticFileHandler, dict(path=settings['static_path']+"images/icon")), 20 | ] 21 | -------------------------------------------------------------------------------- /xk_db_sql/xk_dnsmasq.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.5.36-34.1, for Linux (x86_64) 2 | -- 3 | -- Host: localhost Database: xk_dnsmasq 4 | -- ------------------------------------------------------ 5 | -- Server version 5.5.36-34.1-log 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `xk_dhcp_host` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `xk_dhcp_host`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `xk_dhcp_host` ( 26 | `id` int(11) NOT NULL AUTO_INCREMENT, 27 | `hostname` varchar(50) DEFAULT NULL COMMENT '主机名', 28 | `mac` varchar(20) DEFAULT NULL COMMENT 'MAC地址', 29 | `ip` varchar(15) DEFAULT NULL COMMENT 'IP地址', 30 | `comment` varchar(30) DEFAULT NULL COMMENT '备注', 31 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 32 | `status` varchar(3) NOT NULL DEFAULT 'yes' COMMENT '规则状态', 33 | `action` varchar(10) NOT NULL DEFAULT 'allow' COMMENT '规则动作', 34 | PRIMARY KEY (`id`) 35 | ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; 36 | /*!40101 SET character_set_client = @saved_cs_client */; 37 | 38 | -- 39 | -- Dumping data for table `xk_dhcp_host` 40 | -- 41 | 42 | LOCK TABLES `xk_dhcp_host` WRITE; 43 | /*!40000 ALTER TABLE `xk_dhcp_host` DISABLE KEYS */; 44 | INSERT INTO `xk_dhcp_host` VALUES (3,'rhel65xx','00:0c:29:f6:04:34','192.168.1.60','rhel 6.5xfff','2014-11-26 14:17:12','yes','allow'),(4,'test','00:0c:29:f6:04:35','192.168.1.61','dd','2014-11-26 14:27:22','yes','allow'),(5,'win7','00:0c:29:f6:04:29','192.168.1.7','win7x64','2014-11-26 14:39:51','yes','allow'),(6,'winxp','00:0c:29:f6:04:33','192.168.1.33','33xp','2014-11-26 15:29:01','yes','allow'); 45 | /*!40000 ALTER TABLE `xk_dhcp_host` ENABLE KEYS */; 46 | UNLOCK TABLES; 47 | 48 | -- 49 | -- Table structure for table `xk_domain` 50 | -- 51 | 52 | DROP TABLE IF EXISTS `xk_domain`; 53 | /*!40101 SET @saved_cs_client = @@character_set_client */; 54 | /*!40101 SET character_set_client = utf8 */; 55 | CREATE TABLE `xk_domain` ( 56 | `id` int(11) NOT NULL AUTO_INCREMENT, 57 | `domain` varchar(60) NOT NULL COMMENT '域名', 58 | `file` varchar(200) NOT NULL COMMENT '配置文件', 59 | `file_md5` varchar(64) NOT NULL COMMENT 'MD5值', 60 | `create_time` datetime NOT NULL COMMENT '创建时间', 61 | `up_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 62 | `comment` varchar(200) NOT NULL COMMENT '备注', 63 | `status` varchar(3) NOT NULL DEFAULT 'yes' COMMENT '状态', 64 | PRIMARY KEY (`id`) 65 | ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; 66 | /*!40101 SET character_set_client = @saved_cs_client */; 67 | 68 | -- 69 | -- Dumping data for table `xk_domain` 70 | -- 71 | 72 | LOCK TABLES `xk_domain` WRITE; 73 | /*!40000 ALTER TABLE `xk_domain` DISABLE KEYS */; 74 | INSERT INTO `xk_domain` VALUES (1,'luxiaok.com','luxiaok.com.conf','5dcd3226a211b004c9376d864ab99d7f','2014-11-22 22:25:26','2014-11-24 14:28:25','测试域名','yes'),(2,'test.com','test.com.conf','6f91829bebd8663db73498090ab69557','2014-11-22 22:34:00','2014-11-22 19:47:02','测试域名2','yes'),(3,'qq.com','qq.com.conf','4228386eafc1a2f3dc4c908f84d34a6b','2014-11-23 17:58:32','2014-11-23 09:58:32','QQ域名','yes'),(4,'google.com','google.com.conf','1023883e5729868925ffca6032eb5300','2014-11-24 22:48:07','2014-11-24 15:34:25','Google','yes'); 75 | /*!40000 ALTER TABLE `xk_domain` ENABLE KEYS */; 76 | UNLOCK TABLES; 77 | 78 | -- 79 | -- Table structure for table `xk_login_logs` 80 | -- 81 | 82 | DROP TABLE IF EXISTS `xk_login_logs`; 83 | /*!40101 SET @saved_cs_client = @@character_set_client */; 84 | /*!40101 SET character_set_client = utf8 */; 85 | CREATE TABLE `xk_login_logs` ( 86 | `id` int(5) NOT NULL AUTO_INCREMENT, 87 | `uid` int(3) NOT NULL COMMENT '用户ID', 88 | `username` varchar(30) DEFAULT NULL COMMENT '登录用户名', 89 | `login_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '登录时间', 90 | `login_host` varchar(15) DEFAULT NULL COMMENT '登录IP', 91 | `login_location` varchar(20) DEFAULT NULL COMMENT '登录地区', 92 | `login_status` int(1) NOT NULL DEFAULT '0' COMMENT '0:成功,1:失败,2:用户被禁用,3:用户名错误,4:密码错误,5:异常,6:未知状态', 93 | `user_agent` varchar(200) DEFAULT NULL COMMENT '用户代理', 94 | PRIMARY KEY (`id`) 95 | ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8; 96 | /*!40101 SET character_set_client = @saved_cs_client */; 97 | 98 | -- 99 | -- Dumping data for table `xk_login_logs` 100 | -- 101 | 102 | LOCK TABLES `xk_login_logs` WRITE; 103 | /*!40000 ALTER TABLE `xk_login_logs` DISABLE KEYS */; 104 | INSERT INTO `xk_login_logs` VALUES (1,1,'luxiaok','2014-11-22 09:03:00','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(2,1,'luxiaok','2014-11-22 09:03:08','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(3,1,'luxiaok','2014-11-22 09:06:16','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(4,2,'admin','2014-11-22 09:06:40','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(5,1,'luxiaok','2014-11-22 09:58:03','192.168.1.7',NULL,0,'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'),(6,1,'luxiaok','2014-11-22 10:03:53','192.168.1.7',NULL,0,'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'),(7,1,'luxiaok','2014-11-22 10:05:19','192.168.1.7',NULL,0,'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'),(8,1,'luxiaok','2014-11-22 10:20:29','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(9,1,'luxiaok','2014-11-23 02:38:29','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(10,1,'luxiaok','2014-11-23 03:20:17','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(11,1,'luxiaok','2014-11-23 03:25:42','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(12,1,'luxiaok','2014-11-23 04:16:41','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(13,1,'luxiaok','2014-11-23 04:20:18','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(14,1,'luxiaok','2014-11-23 04:27:27','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(15,1,'luxiaok','2014-11-23 08:03:08','192.168.1.7',NULL,0,'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'),(16,1,'luxiaok','2014-11-23 12:23:00','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(17,1,'luxiaok','2014-11-24 14:20:54','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(18,1,'luxiaok','2014-11-25 14:34:16','192.168.1.7',NULL,0,'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'),(19,1,'luxiaok','2014-11-25 15:10:21','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'),(20,1,'luxiaok','2014-11-26 12:51:41','192.168.1.7',NULL,0,'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36'); 105 | /*!40000 ALTER TABLE `xk_login_logs` ENABLE KEYS */; 106 | UNLOCK TABLES; 107 | 108 | -- 109 | -- Table structure for table `xk_options` 110 | -- 111 | 112 | DROP TABLE IF EXISTS `xk_options`; 113 | /*!40101 SET @saved_cs_client = @@character_set_client */; 114 | /*!40101 SET character_set_client = utf8 */; 115 | CREATE TABLE `xk_options` ( 116 | `id` int(11) NOT NULL AUTO_INCREMENT, 117 | `type` varchar(20) DEFAULT NULL, 118 | `name` varchar(100) NOT NULL, 119 | `value` varchar(200) DEFAULT NULL, 120 | `comment` varchar(200) DEFAULT NULL, 121 | PRIMARY KEY (`id`), 122 | UNIQUE KEY `name` (`name`) 123 | ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8; 124 | /*!40101 SET character_set_client = @saved_cs_client */; 125 | 126 | -- 127 | -- Dumping data for table `xk_options` 128 | -- 129 | 130 | LOCK TABLES `xk_options` WRITE; 131 | /*!40000 ALTER TABLE `xk_options` DISABLE KEYS */; 132 | INSERT INTO `xk_options` VALUES (1,'dhcp','xk_dhcp_status','yes','DHCP开关'),(2,'dhcp','xk_dhcp_pool_start','192.168.1.11','DHCP地址池开始地址'),(3,'dhcp','xk_dhcp_pool_stop','192.168.1.101','DHCP地址池结束地址'),(4,'dhcp','xk_dhcp_pool_netmask','255.255.255.0','DHCP地址池子网掩码'),(5,'dhcp','xk_dhcp_pool_lease','6h','DHCP租约'),(6,'dhcp','xk_dhcp_pool_gw','192.168.1.254','DHCP默认网关'),(7,'dhcp','xk_dhcp_pool_dns1','114.114.114.114','DHCP主DNS服务器'),(8,'dhcp','xk_dhcp_pool_dns2','8.8.8.8','DHCP辅助DNS服务器'),(9,'dhcp','xk_dhcp_pool_domain','luxiaok.com','DHCP缺省域名'),(10,'dhcp','xk_dhcp_pool_ntp','','DHCP时间服务器'),(11,'dhcp','xk_dhcp_pool_comment','test','DHCP地址池备注'),(12,'dhcp','xk_dhcp_conf_md5','','DHCP配置文件的MD5值'); 133 | /*!40000 ALTER TABLE `xk_options` ENABLE KEYS */; 134 | UNLOCK TABLES; 135 | 136 | -- 137 | -- Table structure for table `xk_record` 138 | -- 139 | 140 | DROP TABLE IF EXISTS `xk_record`; 141 | /*!40101 SET @saved_cs_client = @@character_set_client */; 142 | /*!40101 SET character_set_client = utf8 */; 143 | CREATE TABLE `xk_record` ( 144 | `id` int(11) NOT NULL AUTO_INCREMENT, 145 | `did` int(11) NOT NULL COMMENT '域名ID', 146 | `record` varchar(50) NOT NULL COMMENT '主机记录', 147 | `type` varchar(10) NOT NULL COMMENT '记录类型', 148 | `value` varchar(50) NOT NULL COMMENT '记录值', 149 | `priority` int(11) DEFAULT NULL COMMENT 'MX优先级', 150 | `create_time` datetime NOT NULL COMMENT '创建时间', 151 | `up_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 152 | `comment` varchar(100) DEFAULT NULL COMMENT '备注', 153 | `status` varchar(3) NOT NULL DEFAULT 'yes' COMMENT '状态值', 154 | PRIMARY KEY (`id`) 155 | ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8; 156 | /*!40101 SET character_set_client = @saved_cs_client */; 157 | 158 | -- 159 | -- Dumping data for table `xk_record` 160 | -- 161 | 162 | LOCK TABLES `xk_record` WRITE; 163 | /*!40000 ALTER TABLE `xk_record` DISABLE KEYS */; 164 | INSERT INTO `xk_record` VALUES (1,1,'www2','A','192.168.1.12',NULL,'2014-11-22 23:23:00','2014-11-23 11:32:51','2222','yes'),(2,2,'blog','A','192.168.1.1',NULL,'2014-11-22 23:29:06','2014-11-22 16:59:01','测试博客','yes'),(4,2,'news','A','192.168.1.2',NULL,'2014-11-22 23:32:23','2014-11-22 17:02:03','新网页','yes'),(5,2,'mail','A','113.108.16.61',NULL,'2014-11-23 00:08:09','2014-11-22 16:08:09','','yes'),(7,2,'www','A','192.168.2.9',NULL,'2014-11-23 01:57:21','2014-11-22 17:57:21','','yes'),(8,1,'blog','A','192.168.1.99',NULL,'2014-11-23 02:06:54','2014-11-22 18:06:54','','yes'),(9,1,'@','MX','mail.luxiaok.com',50,'2014-11-23 03:05:35','2014-11-22 19:19:40','MX记录','yes'),(10,1,'mail','A','192.168.2.28',NULL,'2014-11-23 03:05:57','2014-11-22 19:05:57','','yes'),(11,2,'@','MX','mail.test.com',12,'2014-11-23 03:20:14','2014-11-22 19:20:14','','yes'),(12,2,'@','TXT','Hello World',NULL,'2014-11-23 03:24:57','2014-11-22 19:24:57','','yes'),(13,2,'hello','TXT','Hello Luxiaok',NULL,'2014-11-23 03:28:26','2014-11-22 19:28:26','','yes'),(14,2,'mail3','CNAME','mail.test.com',NULL,'2014-11-23 03:46:55','2014-11-22 19:46:55','','yes'),(15,1,'host1','CNAME','www.luxiaok.com',NULL,'2014-11-23 17:58:04','2014-11-24 14:28:24','','yes'),(16,3,'www','A','180.96.86.192',NULL,'2014-11-23 17:59:19','2014-11-23 09:59:19','','yes'),(17,1,'www','A','192.168.1.118',NULL,'2014-11-24 22:24:17','2014-11-24 14:24:17','','yes'),(18,4,'','A','192.88.1.88',NULL,'2014-11-24 22:48:33','2014-11-24 15:33:27','测试泛解析','yes'),(19,4,'www','A','192.168.1.1',NULL,'2014-11-24 23:34:21','2014-11-24 15:34:21','www','yes'); 165 | /*!40000 ALTER TABLE `xk_record` ENABLE KEYS */; 166 | UNLOCK TABLES; 167 | 168 | -- 169 | -- Table structure for table `xk_users` 170 | -- 171 | 172 | DROP TABLE IF EXISTS `xk_users`; 173 | /*!40101 SET @saved_cs_client = @@character_set_client */; 174 | /*!40101 SET character_set_client = utf8 */; 175 | CREATE TABLE `xk_users` ( 176 | `id` int(3) NOT NULL AUTO_INCREMENT, 177 | `username` varchar(20) NOT NULL, 178 | `name` varchar(30) NOT NULL, 179 | `password` varchar(64) NOT NULL, 180 | `mobile` varchar(15) DEFAULT NULL COMMENT '电话号码', 181 | `email` varchar(50) DEFAULT NULL COMMENT '电子邮件', 182 | `cdate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 183 | `mdate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 184 | `mask` varchar(3) NOT NULL DEFAULT '999', 185 | `is_admin` varchar(3) NOT NULL DEFAULT 'no' COMMENT '是否为管理员', 186 | `status` varchar(3) NOT NULL DEFAULT 'yes', 187 | `comment` varchar(50) DEFAULT NULL, 188 | PRIMARY KEY (`id`), 189 | UNIQUE KEY `username` (`username`) 190 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户'; 191 | /*!40101 SET character_set_client = @saved_cs_client */; 192 | 193 | -- 194 | -- Dumping data for table `xk_users` 195 | -- 196 | 197 | LOCK TABLES `xk_users` WRITE; 198 | /*!40000 ALTER TABLE `xk_users` DISABLE KEYS */; 199 | INSERT INTO `xk_users` VALUES (1,'luxiaok','陆小K','6b1230a362a507f432b56d4694cb7846',NULL,NULL,'2014-11-22 16:37:50','2014-11-22 08:37:50','999','yes','yes','陆小K'),(2,'admin','管理员','21232f297a57a5a743894a0e4a801fc3',NULL,NULL,'2014-11-22 16:37:50','2014-11-22 08:37:50','999','yes','yes','系统管理员'),(3,'test','测试用户','098f6bcd4621d373cade4e832627b4f6',NULL,NULL,'2014-11-22 16:46:18','2014-11-22 08:46:18','999','no','no','测试用户'); 200 | /*!40000 ALTER TABLE `xk_users` ENABLE KEYS */; 201 | UNLOCK TABLES; 202 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 203 | 204 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 205 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 206 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 207 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 208 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 209 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 210 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 211 | 212 | -- Dump completed on 2014-11-26 23:31:55 213 | -------------------------------------------------------------------------------- /xk_handler/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Desgin By Xiaok 3 | __all__ = [ 4 | 'xk_dhcp', 5 | 'xk_dns', 6 | 'xk_index', 7 | 'xk_login', 8 | 'xk_public', 9 | 'xk_test', 10 | 'xk_users' 11 | ] -------------------------------------------------------------------------------- /xk_handler/xk_dhcp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # Desgin By Xiaok 4 | from xk_application.xk_main import * 5 | 6 | class DhcpPoolHandler(BaseHandler): 7 | @Auth 8 | def get(self): 9 | # 10 | dhcp_options = self.db.query('''select * from xk_options where type = "dhcp"''') 11 | dhcp = {} 12 | for i in dhcp_options: 13 | dhcp[i['name']] = i['value'] 14 | self.render2("xk_dhcp_pool.html",dhcp=dhcp,dhcp_pool="active") 15 | 16 | @Auth 17 | def post(self): 18 | status = self.get_argument("status") 19 | range_start = self.get_argument("range_start") 20 | range_end = self.get_argument("range_end") 21 | netmask = self.get_argument("netmask") 22 | lease = self.get_argument("lease") 23 | router = self.get_argument("router") 24 | dns1 = self.get_argument("dns1") 25 | dns2 = self.get_argument("dns2") 26 | domain = self.get_argument("domain") 27 | ntp = self.get_argument("ntp",'') 28 | comment = self.get_argument("comment") 29 | #self.db.execute('''insert into xk_dhcp_pool ( name,range_start,range_end,netmask,router,dns1,dns2,domain,lease,comment ) 30 | # values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)''',name,range_start,range_end,netmask,router,dns1,dns2,domain,lease,comment) 31 | self.db.execute(''' 32 | insert into xk_options (name,value,comment) values 33 | ('xk_dhcp_status',%s,'DHCP开关'), 34 | ('xk_dhcp_pool_start',%s,'DHCP地址池开始地址'), 35 | ('xk_dhcp_pool_stop',%s,'DHCP地址池结束地址'), 36 | ('xk_dhcp_pool_netmask',%s,'DHCP地址池子网掩码'), 37 | ('xk_dhcp_pool_lease',%s,'DHCP租约'), 38 | ('xk_dhcp_pool_gw',%s,'DHCP默认网关'), 39 | ('xk_dhcp_pool_dns1',%s,'DHCP主DNS服务器'), 40 | ('xk_dhcp_pool_dns2',%s,'DHCP辅助DNS服务器'), 41 | ('xk_dhcp_pool_domain',%s,'DHCP缺省域名'), 42 | ('xk_dhcp_pool_ntp',%s,'DHCP时间服务器'), 43 | ('xk_dhcp_pool_comment',%s,'DHCP地址池备注') 44 | ON DUPLICATE KEY UPDATE name=values(name),value=values(value),comment=values(comment) 45 | ''',status,range_start,range_end,netmask,lease,router,dns1,dns2,domain,ntp,comment) 46 | self.write("1") 47 | 48 | class DhcpHostHandler(BaseHandler): 49 | @Auth 50 | def get(self): 51 | dhcp_hosts = self.db.query("select *,case when action = 'allow' then 'selected' else '' end as allow_selected, case when action = 'ignore' then 'selected' else '' end as ignore_selected from xk_dhcp_host") 52 | self.render2("xk_dhcp_host.html",dhcp_hosts=dhcp_hosts,dhcp_pool="active") 53 | 54 | @Auth 55 | def post(self): 56 | hostname = self.get_argument("hostname") 57 | mac = self.get_argument("mac") 58 | ip = self.get_argument("ip") 59 | action = self.get_argument("action") 60 | comment = self.get_argument("comment") 61 | fun = self.get_argument("fun","add") 62 | id_ = self.get_argument("id",0) # For Edit 63 | sql_mac = "select id,mac from xk_dhcp_host where mac = '%s'" % mac.lower() 64 | sql_ip = "select id,ip from xk_dhcp_host where ip = '%s'" % ip 65 | if fun == "edit": 66 | sql = " and id != %s" % id_ 67 | sql_mac += sql 68 | sql_ip += sql 69 | check_mac = self.db.query(sql_mac) 70 | check_ip = self.db.query(sql_ip) 71 | if check_mac: 72 | self.write("2") # MAC地址冲突 73 | return 74 | if check_ip: 75 | self.write("3") # IP地址冲突 76 | return 77 | if fun == "add": 78 | self.db.execute(" insert into xk_dhcp_host (hostname,mac,ip,action,comment) values (%s,%s,%s,%s,%s) ",hostname,mac.lower(),ip,action,comment) 79 | else: # For Edit 80 | self.db.execute("update xk_dhcp_host set hostname = %s, mac = %s, ip = %s, action = %s, comment = %s where id = %s",hostname,mac,ip,action,comment,id_) 81 | self.write("1") 82 | 83 | -------------------------------------------------------------------------------- /xk_handler/xk_dns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # Desgin By Xiaok 4 | from xk_application.xk_main import * 5 | 6 | class DnsDomainHandler(BaseHandler): 7 | @Auth 8 | def get(self): 9 | #print self.get_login_url() 10 | #print self.current_user 11 | #print self.user_info() 12 | domains = self.db.query("select * from xk_domain") 13 | self.render2("xk_domain.html",domain="active",domains=domains) 14 | 15 | @Auth 16 | def post(self): 17 | domain = self.get_argument("domain") 18 | file = self.get_argument("file") 19 | comment = self.get_argument("comment") 20 | fun = self.get_argument("fun","add") 21 | if fun == "add": 22 | if_domain = self.db.get("select id,domain from xk_domain where domain = %s",domain) 23 | if if_domain: 24 | self.write("2") 25 | return 26 | f = open("/etc/dnsmasq.d/" + file,'w') 27 | f.write("# "+domain+"\n") 28 | f.close() 29 | file_md5 = self.get_md5("/etc/dnsmasq.d/"+file) 30 | self.db.execute("insert into xk_domain (domain,file,file_md5,create_time,comment) values (%s,%s,%s,%s,%s)",domain,file,file_md5,self.get_time(),comment) 31 | self.write("1") 32 | elif fun == "edit": 33 | id_ = self.get_argument("id") 34 | self.db.execute("update xk_domain set domain = %s, file = %s, comment = %s where id = %s",domain,file,comment,id_) 35 | self.write("1") 36 | 37 | class DnsRecordHandler(BaseHandler): 38 | @Auth 39 | def get(self): 40 | did = self.get_argument("did",0) 41 | domains = self.db.query("select id,domain from xk_domain where status = 'yes'") 42 | cur_domain = self.db.get("select * from xk_domain where id = %s",did) 43 | records = self.db.query("select * from xk_record where did = %s",did) 44 | self.render2("xk_record.html",record="active",domains=domains,did=int(did),records=records,cur_domain=cur_domain) 45 | 46 | @Auth 47 | def post(self): 48 | did = self.get_argument("did") 49 | record = self.get_argument("record") 50 | type = self.get_argument("type") 51 | value = self.get_argument("value") 52 | priority = self.get_argument("priority") 53 | comment = self.get_argument("comment") 54 | fun = self.get_argument("fun","add") 55 | if type == "MX": 56 | priority = int(priority) 57 | else: 58 | priority = None 59 | if fun == "add": 60 | self.db.execute("insert into xk_record (did,record,type,value,priority,comment,create_time) values (%s,%s,%s,%s,%s,%s,%s)",did,record,type,value,priority,comment,self.get_time()) 61 | self.write("1") 62 | elif fun == "edit": 63 | id = self.get_argument("id") 64 | self.db.execute("update xk_record set record = %s, type = %s, value = %s, priority = %s, comment = %s where id = %s",record,type,value,priority,comment,id) 65 | self.write("1") -------------------------------------------------------------------------------- /xk_handler/xk_index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # Desgin By Xiaok 4 | from xk_application.xk_main import * 5 | import platform,os 6 | 7 | class IndexHandler(BaseHandler): 8 | def get_hostname(self): 9 | h = os.popen("hostname") 10 | return h.read().strip() 11 | 12 | # 获取服务器运行时间 13 | def get_uptime(self): 14 | f = open('/proc/uptime','r') 15 | r = f.read() 16 | u = r.split() 17 | f.close() 18 | uptime = self.format_seconds(int(float(u[0]))) 19 | cpu_num = os.popen("cat /proc/cpuinfo | grep processor | wc -l") 20 | cpu_num = int(cpu_num.read().strip()) 21 | free = int(float(u[1])) * 100 / (int(float(u[0]))*cpu_num) 22 | uptime['free'] = free 23 | return uptime 24 | 25 | def get_ip(self): 26 | r = os.popen("ip a | grep inet | grep -Ev 'inet6|127.0.0.1' | awk -F'[ /]+' '{print $3}'") 27 | r = r.read() 28 | ip = r.split() 29 | if len(ip) > 1: 30 | ip = ', '.join(ip) 31 | else: 32 | ip = ip[0] 33 | return ip 34 | 35 | def get_load(self): 36 | f = open('/proc/loadavg') 37 | l = f.read().split() 38 | f.close() 39 | loadavg_1 = l[0] 40 | loadavg_5 = l[1] 41 | loadavg_15 = l[2] 42 | return [loadavg_1,loadavg_5,loadavg_15] 43 | 44 | def get_mem(self): 45 | f = open('/proc/meminfo') 46 | m = f.readlines() 47 | f.close() 48 | mem = {} 49 | for n in m: 50 | if len(n) < 2 : continue 51 | name = n.split(':')[0] 52 | var = n.split()[1] 53 | mem[name] = int(var) * 1024 # 单位默认是K,乘以1024转换为字节 54 | mem['MemUsed'] = mem['MemTotal'] - mem['MemFree'] - mem['Buffers'] - mem['Cached'] 55 | MemUsedPercent = mem['MemUsed'] * 100 / mem['MemTotal'] 56 | mem['MemUsedPercent'] = MemUsedPercent 57 | return mem 58 | 59 | def get_hdd(self): 60 | d = os.statvfs('/') 61 | all = d.f_frsize * d.f_blocks 62 | free = d.f_frsize * d.f_bavail 63 | used = ( d.f_blocks - d.f_bavail ) * d.f_frsize 64 | usedPercent = ( d.f_blocks - d.f_bavail ) * 100 / d.f_blocks 65 | return {"all":all, "free":free, "used":used, "usedPercent":usedPercent} 66 | 67 | def net_stat(self): 68 | net = {} 69 | f = open("/proc/net/dev") 70 | lines = f.readlines() 71 | f.close() 72 | i = 1 73 | for line in lines: 74 | if i < 3 : 75 | i += 1 76 | continue 77 | con = line.split(':') 78 | name = con[0].split()[0] 79 | var = con[1].split() 80 | net[name] = var 81 | i += 1 82 | net_in = 0 83 | net_out = 0 84 | for i in net: 85 | if i == 'lo':continue 86 | net_in += int(net[i][0]) 87 | net_out += int(net[i][8]) 88 | #net_in = net_in / 1024 / 1024 89 | #net_out = net_out / 1024 /1024 90 | return {"in":net_in,"out":net_out} 91 | 92 | def get_os_version(self): 93 | OS = platform.linux_distribution() 94 | os_arch = platform.machine() 95 | if 'Red Hat Enterprise Linux Server' in OS: 96 | os_name = 'RHEL' 97 | else: 98 | os_name = OS[0] 99 | os_version = OS[1] 100 | return "%s %s %s" % ( os_name,os_version,os_arch) 101 | 102 | def get_dnsmasq(self): 103 | status = os.system("/etc/init.d/dnsmasq status") 104 | v1 = os.popen("dnsmasq --version | head -1 | awk '{print $3}'") 105 | version = v1.read().strip() 106 | return {"version":version, "status":status} 107 | 108 | def get_cpu(self): 109 | cpu = os.popen('top -bi -n 1').read().split('\n')[2] 110 | cpu = cpu.split(", ")[3].split('%')[0] 111 | return 100.0 - float(cpu) 112 | 113 | @Auth 114 | def get(self): 115 | #print self.get_login_url() 116 | #print self.current_user 117 | #print self.user_info() 118 | data = { 119 | "uptime":self.get_uptime(), 120 | "ip":self.get_ip(), 121 | "net":self.net_stat(), 122 | "mem":self.get_mem(), 123 | "load":self.get_load(), 124 | "os":self.get_os_version(), 125 | "hdd":self.get_hdd(), 126 | "dnsmasq":self.get_dnsmasq(), 127 | "hostname":self.get_hostname(), 128 | "cpu":self.get_cpu() 129 | } 130 | #print data 131 | self.render2("xk_index.html",dashboard="active",data=data) 132 | -------------------------------------------------------------------------------- /xk_handler/xk_login.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # Desgin By Xiaok 4 | from xk_application.xk_main import * 5 | 6 | class LoginHandler(BaseHandler): 7 | def get(self,*args): 8 | #print args # 传入了一个login的参数进来了 9 | if self.current_user: 10 | return self.redirect('/dashboard') 11 | self.render2("xk_login.html") 12 | 13 | def post(self,*args): 14 | username = self.get_argument('username') 15 | password = self.get_argument('password') 16 | remember = self.get_argument('remember','no') 17 | user = self.db.get('''select id,username,status from xk_users where username = %s and password = md5(%s)''',username,password) 18 | if user: 19 | if user['status'] == 'no': 20 | self.write('''''') 21 | return 22 | else: 23 | self.write('''''') 24 | return 25 | # 获取客户端信息,并写入登录日志 26 | headers = self.request.headers 27 | login_host = self.request.remote_ip 28 | #login_host = "210.75.225.254" # For Test and Debug 29 | user_agent = headers.get('User-Agent') 30 | # 写登录日志 31 | self.db.execute(''' insert into xk_login_logs (uid,username,login_host,user_agent) values (%s,%s,%s,%s) ''',user['id'],user['username'],login_host,user_agent) 32 | # 登录成功,客户端写cooike 33 | if remember == 'yes': 34 | expires = 30 35 | else: 36 | expires = None 37 | self.set_secure_cookie('xk_auth_token',username,expires_days=expires) 38 | # 跳转到登录前的页面 39 | referer_url = self.get_argument("next", "/dashboard") 40 | self.redirect(referer_url) 41 | 42 | class LogoutHandler(BaseHandler): 43 | def get(self): 44 | self.clear_cookie("xk_auth_token") 45 | self. redirect(self.get_login_url()) -------------------------------------------------------------------------------- /xk_handler/xk_public.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # Desgin By Xiaok 4 | from xk_application.xk_main import * 5 | import os 6 | 7 | class PublicAPIHandler(BaseHandler): 8 | def reload_dhcp(self,file,force=False): 9 | dhcp_conf = self.db.query("select * from xk_options where type = 'dhcp'") 10 | d = {} 11 | for i in dhcp_conf: 12 | d[i['name']] = i['value'] 13 | if d['xk_dhcp_status'] != 'yes': 14 | f = open(file,'w') 15 | f.write("# DHCP is stopped.") 16 | f.close() 17 | new_md5 = self.get_md5(file) 18 | self.db.execute("update xk_options set value = %s where name = 'xk_dhcp_conf_md5' and type = 'dhcp'",new_md5) 19 | sv_rt = os.system("/etc/init.d/dnsmasq restart") 20 | #return 5 # 停止DHCP 21 | return 2 # 返回成功状态码 22 | if force is False: 23 | check_md5 = self.get_md5(file) 24 | if check_md5 != d['xk_dhcp_conf_md5']: 25 | return 1 # MD5校验失败 26 | if not d['xk_dhcp_pool_domain']: 27 | d['xk_dhcp_pool_domain'] = 'luxiaok.com' 28 | if not d['xk_dhcp_pool_dns2']: 29 | d['xk_dhcp_pool_dns2'] = "8.8.8.8" 30 | conf = '''# Gen By Luxiaok 31 | # Address Pool 32 | dhcp-range=%s,%s,%s,%s 33 | # Gateway,3 34 | dhcp-option=option:router,%s 35 | # DNS Server 36 | dhcp-option=6,%s,%s 37 | # NTP Server,4 or 42 38 | #dhcp-option=42,202.120.2.101 39 | # DNS Domain 40 | dhcp-option=15,%s\n''' % (d['xk_dhcp_pool_start'],d['xk_dhcp_pool_stop'],d['xk_dhcp_pool_netmask'],d['xk_dhcp_pool_lease'],d['xk_dhcp_pool_gw'],d['xk_dhcp_pool_dns1'],d['xk_dhcp_pool_dns2'],d['xk_dhcp_pool_domain']) 41 | dhcp_hosts = self.db.query("select * from xk_dhcp_host where status = 'yes'") 42 | if dhcp_hosts: 43 | for i in dhcp_hosts: 44 | if i['action'] == 'allow': 45 | conf += "# %s\ndhcp-host=%s,%s\n" % (i['hostname'],i['mac'],i['ip']) 46 | else: 47 | conf += "# %s\ndhcp-host=%s,ignore\n" % (i['hostname'],i['mac']) 48 | try: 49 | f = open(file,'w') 50 | f.write(conf) 51 | except: 52 | return 4 # 写入配置失败 53 | finally: 54 | f.close() 55 | new_md5 = self.get_md5(file) 56 | #print new_md5 57 | self.db.execute("update xk_options set value = %s where name = 'xk_dhcp_conf_md5' and type = 'dhcp'",new_md5) 58 | sv_rt = os.system("/etc/init.d/dnsmasq restart") 59 | if sv_rt == 0: 60 | return 2 # 写入文件成功,重新加载配置成功 61 | else: 62 | return 3 # 重启服务失败 63 | 64 | @Auth 65 | def get(self): 66 | module = self.get_argument("module") 67 | fun = self.get_argument("fun") 68 | value = self.get_argument("value",None) 69 | id = self.get_argument("id",None) 70 | redirect_id = self.get_argument("did",None) 71 | if module == "record": 72 | if fun == "ch_status": 73 | self.db.execute("update xk_record set status = %s where id = %s",value,id) 74 | self.redirect("/dns/record?did="+redirect_id) 75 | elif fun == "del": 76 | self.db.execute("delete from xk_record where id = %s",id) 77 | self.redirect("/dns/record?did="+redirect_id) 78 | elif module == "domain": 79 | if fun == "ch_status": 80 | self.db.execute("update xk_domain set status = %s where id = %s",value,id) 81 | self.redirect("/dns/domain") 82 | elif fun == "del": 83 | # 获取该域名的配置文件 84 | domain = self.db.get("select * from xk_domain where id = %s",id) 85 | file = domain['file'] 86 | file_md5 = domain['file_md5'] 87 | # 删除该域名的DNSmasq的配置文件 88 | os.remove("/etc/dnsmasq.d/"+file) 89 | # 同时删除域名的所有记录 90 | self.db.execute("delete from xk_record where did = %s",id) 91 | # 删除域名 92 | self.db.execute("delete from xk_domain where id = %s",id) 93 | self.redirect("/domain") 94 | elif module == "dnsmasq": # 同步解析 95 | if fun == "update": # 从数据库更新配置文件并重新加载服务 96 | records = self.db.query("select r.record,d.domain,r.value,d.file,d.file_md5,r.type,r.priority from xk_record as r left join xk_domain as d on r.did = d.id where r.status = 'yes' and d.status = 'yes' and r.did = %s order by d.domain,inet_aton(r.value)",id) 97 | # A记录 98 | file_content = '' 99 | for i in records: 100 | if i['type'] == "A": 101 | file_content += "address=/" + i['record'] + "." + i['domain'] + "/" + i['value'] + "\n" 102 | elif i['type'] == "MX": 103 | file_content += "mx-host=" + i["domain"] + "," + i["value"] + "," + str(i['priority']) + "\n" 104 | elif i['type'] == "TXT": 105 | file_content += "txt-record=" 106 | if i["record"] != "@": 107 | file_content += i['record'] + "." 108 | file_content += i["domain"] + ',"' + i["value"] + '"\n' 109 | elif i['type'] == "CNAME": 110 | file_content += "cname=" + i['record'] + "." + i["domain"] + "," + i["value"] + "\n" 111 | 112 | force = self.get_argument("force","no") 113 | check_md5 = i['file_md5'] 114 | if force == "no": 115 | check_md5 = self.get_md5("/etc/dnsmasq.d/" + i['file']) 116 | if check_md5 == i['file_md5']: 117 | f = open("/etc/dnsmasq.d/" + i['file'],"w") 118 | f.write(file_content) 119 | f.close() 120 | sv_rt = os.system("/etc/init.d/dnsmasq restart") 121 | if sv_rt == 0: 122 | update_md5 = self.get_md5("/etc/dnsmasq.d/" + i['file']) 123 | self.db.execute("update xk_domain set file_md5 = %s where id = %s",update_md5,id) 124 | self.write("0") # 成功 125 | else: 126 | self.write("1") # 服务重启失败 127 | else: # md5匹配不上 128 | self.write("2") # 校验配置文件失败 129 | 130 | elif fun in ("reload","restart","start","stop"): 131 | sv_rt = os.system("/etc/init.d/dnsmasq " + fun) 132 | if sv_rt == 0: 133 | self.write("0") # 成功 134 | else: 135 | self.write("1") # 失败 136 | elif module == "dhcp_host": 137 | if fun == "ch_status": 138 | self.db.execute("update xk_dhcp_host set status = %s where id = %s",value,id) 139 | self.redirect("/dhcp/host") 140 | elif fun == "del": 141 | self.db.execute("delete from xk_dhcp_host where id = %s",id) 142 | self.redirect("/dhcp/host") 143 | elif fun == "ch_action": 144 | self.db.execute("update xk_dhcp_host set action = %s where id = %s",value,id) 145 | self.redirect("/dhcp/host") 146 | elif module == "dhcp": 147 | if fun == "reload": 148 | # Test URL: http://www.yourdomain.com:9886/public/api?module=dhcp&fun=reload&value=force 149 | if value == "force": 150 | force = True 151 | else: 152 | force = False 153 | rt = self.reload_dhcp("/etc/dnsmasq.d/dhcp.conf",force) 154 | self.write(str(rt)) 155 | return 156 | elif module == "users": 157 | if fun == "ch_status": 158 | self.db.execute("update xk_users set status = %s where id = %s",value,id) 159 | self.redirect("/users") 160 | elif fun == "del": 161 | self.db.execute("delete from xk_users where id = %s",id) 162 | self.redirect("/users") 163 | elif module == "login_logs": 164 | if fun == "clear": 165 | try: 166 | self.db.execute("truncate xk_login_logs") 167 | self.write("1") 168 | return 169 | except: 170 | self.write("2") 171 | 172 | 173 | -------------------------------------------------------------------------------- /xk_handler/xk_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # Desgin By Xiaok 4 | from xk_application.xk_main import * 5 | 6 | class TestHandler(BaseHandler): 7 | #@Auth 8 | def get(self): 9 | self.write("Hello,Test!") -------------------------------------------------------------------------------- /xk_handler/xk_users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- coding:utf8 -*- 3 | # Desgin By Xiaok 4 | from xk_application.xk_main import * 5 | 6 | class UsersHandler(BaseHandler): 7 | def get(self): 8 | users = self.db.query("select * from xk_users") 9 | self.render2("xk_users.html",users=users,users_admin="active") 10 | 11 | def post(self): 12 | username = self.get_argument("username",None) 13 | name = self.get_argument("name",None) 14 | email = self.get_argument("email",None) 15 | mobile = self.get_argument("mobile",None) 16 | password = self.get_argument("password",None) 17 | comment = self.get_argument("comment",None) 18 | id = self.get_argument("id",None) 19 | fun = self.get_argument("fun") 20 | if fun == "add": 21 | check_user = self.db.get("select id,username,name from xk_users where username = %s",username) 22 | if check_user: 23 | self.write("2") 24 | return 25 | self.db.execute("insert into xk_users (username,name,email,mobile,password,comment,cdate) values (%s,%s,%s,%s,md5(%s),%s,CURRENT_TIMESTAMP)",username,name,email,mobile,password,comment) 26 | self.write("1") 27 | return 28 | elif fun == "edit": 29 | self.db.execute("update xk_users set name=%s,email=%s,mobile=%s,comment=%s where id=%s",name,email,mobile,comment,id) 30 | self.write("1") 31 | elif fun == "pass": 32 | self.db.execute("update xk_users set password=md5(%s) where id=%s",password,id) 33 | self.write("1") 34 | 35 | class LoginLogsHandler(BaseHandler): 36 | def get(self): 37 | logs = self.db.query("select * from xk_login_logs") 38 | self.render2("xk_users_logs.html",logs=logs,users_logs="active") 39 | -------------------------------------------------------------------------------- /xk_html/xk_dhcp_host.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DHCP管理 - DnsMasqWeb - Desgin By Xiaok 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | {% include "xk_top_nav.html" %} 17 |
18 | {% include "xk_nav.html" %} 19 |
20 |

DHCP绑定规则 | 地址池管理

21 |
22 | 23 | 24 |


25 | 26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 | 38 |
39 |
40 |
41 | 42 |
43 | 44 |
45 |
46 |
47 | 48 |
49 | 53 |
54 |
55 |
56 | 57 |
58 | 59 |
60 |
61 |
62 | 取消  保存 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | {% for d in dhcp_hosts %} 81 | {% if d['status'] == 'yes' %} 82 | 83 | {% set st_msg = "暂停" %} 84 | {% set fun_value = "no" %} 85 | {% else %} 86 | 87 | {% set st_msg = "开启" %} 88 | {% set fun_value = "yes" %} 89 | {% endif %} 90 | {% if d['action'] == 'allow' %} 91 | {% set action = '已允许' %} 92 | {% set action_value = 'ignore' %} 93 | {% set action_msg = "禁止" %} 94 | {% else %} 95 | {% set action = '已禁止' %} 96 | {% set action_value = 'allow' %} 97 | {% set action_msg = "允许" %} 98 | {% endif %} 99 | 100 | 101 | 102 | 103 | 104 | 105 | 110 | 111 | {# 可编辑状态 #} 112 | 113 | 114 | 115 | 116 | 117 | 123 | 124 | 128 | 129 | {% endfor %} 130 | 131 |
#主机名MAC地址IP地址动作备注操作
{{ loop.index }}{{ d['hostname'] }}{{ d['mac'] }}{{ d['ip'] }}{{ action }}{{ d['comment'] or '' }} 106 | 编辑 | 107 | {{st_msg}} | 108 | 删除 109 |
{{ loop.index }} 118 | 122 | 125 | 保存 | 126 | 取消 127 |
132 |
133 |
134 |
135 | 136 | 137 | 138 | 139 | 241 | {% include "xk_footer.html" %} 242 | 243 | -------------------------------------------------------------------------------- /xk_html/xk_dhcp_pool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DHCP管理 - DnsMasqWeb - Desgin By Xiaok 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | {% include "xk_top_nav.html" %} 18 |
19 | {% include "xk_nav.html" %} 20 |
21 |

DHCP地址池管理 | 绑定规则

22 |
23 | 24 | 25 |

26 | 27 |
28 |
29 |
30 | 31 |
32 | {% if dhcp['xk_dhcp_status'] == 'yes' %} 33 | {% set status = 'checked' %} 34 | {% else %} 35 | {% set status = '' %} 36 | {% endif %} 37 |
38 |
39 |
40 |
41 | 42 |
43 | - 44 | 45 |
46 |
47 |
48 | 49 |
50 | 69 |
70 |
71 |
72 | 73 |
74 | 75 |
76 |
77 |
78 | 79 |
80 | 81 |
82 |
83 |
84 | 85 |
86 | 99 |
100 |
101 |
102 | 103 |
104 | 105 |
106 |
107 |
108 | 109 |
110 | 111 |
112 |
113 |
114 | 115 |
116 | 117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | 125 | 126 | 127 | 128 | 129 | 193 | {% include "xk_footer.html" %} 194 | 195 | -------------------------------------------------------------------------------- /xk_html/xk_domain.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 域名管理 - DnsMasqWeb - Desgin By Xiaok 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | {% include "xk_top_nav.html" %} 17 |
18 | {% include "xk_nav.html" %} 19 |
20 |

21 | 域名管理 22 |

23 |
24 | 25 | 26 |


27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | {% for d in domains %} 53 | {% if d['status'] == 'yes' %} 54 | 55 | {% set st_msg = "暂停" %} 56 | {% set fun_value = "no" %} 57 | {% else %} 58 | 59 | {% set st_msg = "开启" %} 60 | {% set fun_value = "yes" %} 61 | {% endif %} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 73 | 74 | {# 可编辑状态 #} 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 86 | 87 | {% endfor %} 88 | 89 |
#域名域名文件创建时间更新时间备注操作
{{get_time()}}{{get_time()}} 48 | 保存 | 49 | 取消 50 |
{{ loop.index }}{{ d['domain'] }}{{ d['file'] }}{{ d['create_time'] }}{{ d['up_time'] }}{{ d['comment'] or '' }} 69 | 编辑 | 70 | {{st_msg}} | 71 | 删除 72 |
{{loop.index}}{{d['create_time']}}{{d['up_time']}} 83 | 保存 | 84 | 取消 85 |
90 | 95 |
96 |
97 |
98 | 99 | 100 | 101 | 191 | {% include "xk_footer.html" %} 192 | 193 | -------------------------------------------------------------------------------- /xk_html/xk_footer.html: -------------------------------------------------------------------------------- 1 |
2 | DnsMasqWeb Project - Desgin By 3 | Xiaok 4 |
-------------------------------------------------------------------------------- /xk_html/xk_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DnsMasq控制台 - Desgin By Xiaok 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | {% include "xk_top_nav.html" %} 18 |
19 | {% include "xk_nav.html" %} 20 |
21 |

22 | 控制中心 23 |

24 | 40 |
41 |
42 |

系统信息

43 |
    44 |
  • 45 | 46 | 主机名 {{data['hostname']}} 47 | Refresh 48 | Reboot 49 | Shutdown 50 | 操作系统 {{data['os']}} 51 |
  • 52 |
  • 53 | IP地址 {{data['ip']}} 54 | 流出流量 {{format_size(data['net']['out'])}} | 流入流量 {{format_size(data['net']['in'])}} 55 |
  • 56 |
  • 57 | CPU使用率 {{data['cpu']}}% 58 |
    59 |
  • 60 |
  • 61 | 系统空闲率 {{data['uptime']['free']}}% 62 |
    63 |
  • 64 |
65 |
66 |
67 |

系统状态

68 |
    69 |
  • 70 | {% if data['dnsmasq']['status'] == 0 %} 71 | {% set st_color = 'green' %} 72 | {% set st_msg = '运行中 ...' %} 73 | {% set next_run = 'stop' %} 74 | {% set next_run_msg = '停止' %} 75 | {% else %} 76 | {% set st_color = 'red' %} 77 | {% set st_msg = '服务异常' %} 78 | {% set next_run = 'start' %} 79 | {% set next_run_msg = '启动' %} 80 | {% endif %} 81 | 82 | DNSmasq {{st_msg}}  83 | Relaod 84 | Restart 85 | {{next_run | title}} 86 | 版本 {{data['dnsmasq']['version']}} 87 |
  • 88 |
  • 89 | 系统负载 90 | 1分钟 {{data['load'][0]}} | 5分钟 {{data['load'][1]}} | 15分钟 {{data['load'][2]}} 91 |
  • 92 |
  • 93 | 磁盘使用 {{format_size(data['hdd']['used'])}} / {{format_size(data['hdd']['all'])}} | {{data['hdd']['usedPercent']}}% 94 |
    95 |
  • 96 |
  • 97 | 内存使用 {{format_size(data['mem']['MemUsed'])}} / {{format_size(data['mem']['MemTotal'])}} | {{data['mem']['MemUsedPercent']}}% 98 |
    99 |
  • 100 |
101 |
102 |
103 |
104 | 105 | 106 | 107 | {% include "xk_footer.html" %} 108 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /xk_html/xk_login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Login - DNSmasqWeb - Desgin By Xiaok 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |

DnsMasq Web

14 |

用户名

15 |

密 码

16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | DnsMasqWeb Project - Desgin By 25 | Xiaok 26 |
27 | 28 | -------------------------------------------------------------------------------- /xk_html/xk_nav.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 52 |
53 |
-------------------------------------------------------------------------------- /xk_html/xk_record.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 记录管理 - DnsMasqWeb - Desgin By Xiaok 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | {% include "xk_top_nav.html" %} 20 |
21 | {% include "xk_nav.html" %} 22 |
23 |

24 | 记录管理 25 |

26 |
27 |   33 | {% if did > 0 %} 34 | 35 | 36 | 37 | {% endif %} 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 68 | 69 | 70 | 71 | 72 | 73 | 77 | 78 | {% for r in records %} 79 | {% if r['status'] == 'yes' %} 80 | 81 | {% set st_msg = "暂停" %} 82 | {% set fun_value = "no" %} 83 | {% else %} 84 | 85 | {% set st_msg = "开启" %} 86 | {% set fun_value = "yes" %} 87 | {% endif %} 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 101 | 102 | {# 可编辑状态 #} 103 | 104 | 105 | 106 | 118 | 119 | 120 | 121 | 122 | 123 | 127 | 128 | {% endfor %} 129 | 130 |
#主机记录记录类型记录值MX优先级创建时间更新时间备注操作
58 | 67 | {{get_time()}}{{get_time()}} 74 | 保存 | 75 | 取消 76 |
{{loop.index}}{{r['record']}}{{r['type']}}{{r['value']}}{{r['priority'] or ''}}{{r['create_time']}}{{r['up_time']}}{{r['comment']}} 97 | 编辑 | 98 | {{st_msg}} | 99 | 删除 100 |
{{loop.index}} 107 | 116 | 117 | {{r['create_time']}}{{r['up_time']}} 124 | 保存 | 125 | 取消 126 |
131 | 136 |
137 |
138 |
139 | 265 | {% include "xk_footer.html" %} 266 | 267 | -------------------------------------------------------------------------------- /xk_html/xk_top_nav.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /xk_html/xk_users.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 用户管理 - DnsMasqWeb - Desgin By Xiaok 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | {% include "xk_top_nav.html" %} 17 |
18 | {% include "xk_nav.html" %} 19 |
20 |

21 | 用户管理 22 |

23 |
24 |
25 | 26 |


27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | {% for u in users %} 57 | {% if u['status'] == 'yes' %} 58 | 59 | {% set st_msg = "禁用" %} 60 | {% set fun_value = "no" %} 61 | {% else %} 62 | 63 | {% set st_msg = "启用" %} 64 | {% set fun_value = "yes" %} 65 | {% endif %} 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 80 | 81 | {# 可编辑状态 #} 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 95 | 96 | {% endfor %} 97 | 98 |
#用户名姓名邮件地址手机号创建时间更新时间备注操作
#密码 确认密码 52 | 保存 | 53 | 取消 54 |
{{ loop.index }}{{ u['username'] }}{{ u['name'] }}{{ u['email'] or '' }}{{ u['mobile'] or '' }}{{ u['cdate'] }}{{ u['mdate'] }}{{ u['comment'] or '' }} 75 | 编辑 | 76 | 密码 | 77 | {{st_msg}} | 78 | 删除 79 |
{{ loop.index }}{{u['username']}}{{u['cdate']}}{{u['mdate']}} 92 | 保存 | 93 | 取消 94 |
99 | 104 |
105 |
106 |
107 |
108 | 109 |
110 | 111 |
112 |
113 |
114 | 115 |
116 | 117 |
118 |
119 |
120 | 121 |
122 | 123 |
124 |
125 |
126 | 127 |
128 | 129 |
130 |
131 |
132 | 133 |
134 | 135 |
136 |
137 |
138 | 取 消   139 | 保 存 140 |
141 |
142 |
143 | 144 |
145 |
146 |
147 | 148 | 149 | 150 | 151 | {% include "xk_footer.html" %} 152 | 153 | -------------------------------------------------------------------------------- /xk_html/xk_users_logs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 用户登录日志管理 - DnsMasqWeb - Desgin By Xiaok 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | {% include "xk_top_nav.html" %} 17 |
18 | {% include "xk_nav.html" %} 19 |
20 |

21 | 用户登录日志 22 |

23 |
24 | 25 |


26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {% for log in logs %} 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {% endfor %} 52 | 53 |
#UID用户名姓名登录时间登录主机登录地区用户代理
{{ loop.index }}{{ log['uid'] }}{{ log['username'] }}{{ log['login_time'] }}{{ log['login_host'] }}{{ log['login_localtion'] or '' }}{{ log['user_agent'] | truncate(40)}}
54 | 59 |
60 |
61 |
62 | 63 | 64 | 65 | 91 | {% include "xk_footer.html" %} 92 | 93 | -------------------------------------------------------------------------------- /xk_screenshot/xk_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiaok/DNSmasqWeb/26989d1fd2583bdc74047a9119379aaf20c9b0f5/xk_screenshot/xk_dashboard.png -------------------------------------------------------------------------------- /xk_screenshot/xk_dhcp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiaok/DNSmasqWeb/26989d1fd2583bdc74047a9119379aaf20c9b0f5/xk_screenshot/xk_dhcp.png -------------------------------------------------------------------------------- /xk_screenshot/xk_domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiaok/DNSmasqWeb/26989d1fd2583bdc74047a9119379aaf20c9b0f5/xk_screenshot/xk_domain.png -------------------------------------------------------------------------------- /xk_screenshot/xk_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiaok/DNSmasqWeb/26989d1fd2583bdc74047a9119379aaf20c9b0f5/xk_screenshot/xk_login.png -------------------------------------------------------------------------------- /xk_screenshot/xk_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiaok/DNSmasqWeb/26989d1fd2583bdc74047a9119379aaf20c9b0f5/xk_screenshot/xk_record.png -------------------------------------------------------------------------------- /xk_static/bootstrap/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.2 3 | * 4 | * Copyright 2013 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /xk_static/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiaok/DNSmasqWeb/26989d1fd2583bdc74047a9119379aaf20c9b0f5/xk_static/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /xk_static/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiaok/DNSmasqWeb/26989d1fd2583bdc74047a9119379aaf20c9b0f5/xk_static/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /xk_static/bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('