diff --git a/.gitignore b/.gitignore
index 87a711097..dc5119205 100644
--- a/.gitignore
+++ b/.gitignore
@@ -163,7 +163,6 @@ plugins/l2tp
plugins/openlitespeed
plugins/tamper_proof
plugins/cryptocurrency_trade
-plugins/op_load_balance
plugins/gdrive
plugins/mtproxy
plugins/zimg
diff --git a/plugins/op_load_balance/conf/load_balance.conf b/plugins/op_load_balance/conf/load_balance.conf
new file mode 100644
index 000000000..0dff636c3
--- /dev/null
+++ b/plugins/op_load_balance/conf/load_balance.conf
@@ -0,0 +1,2 @@
+## 指定共享内存
+lua_shared_dict healthcheck 10m;
\ No newline at end of file
diff --git a/plugins/op_load_balance/conf/rewrite.tpl.conf b/plugins/op_load_balance/conf/rewrite.tpl.conf
new file mode 100644
index 000000000..1554bf2c7
--- /dev/null
+++ b/plugins/op_load_balance/conf/rewrite.tpl.conf
@@ -0,0 +1,64 @@
+location / {
+ proxy_pass http://{$UPSTREAM_NAME};
+
+ client_max_body_size 10m;
+ client_body_buffer_size 128k;
+
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $proxy_host;
+ proxy_connect_timeout 90;
+ proxy_send_timeout 90;
+ proxy_read_timeout 90;
+ proxy_buffer_size 4k;
+ proxy_buffers 4 32k;
+ proxy_busy_buffers_size 64k;
+ proxy_temp_file_write_size 64k;
+}
+
+
+#location /upstream_status {
+# allow 127.0.0.1;
+# deny all;
+# access_log off;
+# default_type text/plain;
+# content_by_lua_block {
+# local hc = require "resty.upstream.healthcheck"
+# ngx.say("OpenResty Worker PID: ", ngx.worker.pid())
+# ngx.print(hc.status_page())
+# }
+#}
+
+location /upstream_status_{$UPSTREAM_NAME} {
+ allow 127.0.0.1;
+ deny all;
+ access_log off;
+ default_type text/plain;
+ content_by_lua_block {
+ local json = require "cjson"
+ local ok, upstream = pcall(require, "ngx.upstream")
+ if not ok then
+ ngx.print("[]")
+ return
+ end
+
+ local get_primary_peers = upstream.get_primary_peers
+ local get_backup_peers = upstream.get_backup_peers
+ local upstream_name = "{$UPSTREAM_NAME}"
+
+ peers, err = get_primary_peers(upstream_name)
+ if not peers then
+ ngx.print("[]")
+ return
+ end
+
+ peers_backup, err = get_backup_peers(upstream_name)
+ if peers_backup then
+ for k, v in pairs(peers_backup) do
+ table.insert(peers,v)
+ end
+ end
+
+ ngx.print(json.encode(peers))
+ }
+}
\ No newline at end of file
diff --git a/plugins/op_load_balance/conf/upstream.tpl.conf b/plugins/op_load_balance/conf/upstream.tpl.conf
new file mode 100644
index 000000000..711bbec01
--- /dev/null
+++ b/plugins/op_load_balance/conf/upstream.tpl.conf
@@ -0,0 +1,5 @@
+upstream {$UPSTREAM_NAME}
+{
+ {$NODE_ALGO}
+ {$NODE_SERVER_LIST}
+}
\ No newline at end of file
diff --git a/plugins/op_load_balance/ico.png b/plugins/op_load_balance/ico.png
new file mode 100644
index 000000000..57850d8d4
Binary files /dev/null and b/plugins/op_load_balance/ico.png differ
diff --git a/plugins/op_load_balance/index.html b/plugins/op_load_balance/index.html
new file mode 100755
index 000000000..c789b699c
--- /dev/null
+++ b/plugins/op_load_balance/index.html
@@ -0,0 +1,21 @@
+
+
+
+
\ No newline at end of file
diff --git a/plugins/op_load_balance/index.py b/plugins/op_load_balance/index.py
new file mode 100755
index 000000000..59a646fb2
--- /dev/null
+++ b/plugins/op_load_balance/index.py
@@ -0,0 +1,474 @@
+# coding:utf-8
+
+import sys
+import io
+import os
+import time
+import subprocess
+import json
+import re
+
+sys.path.append(os.getcwd() + "/class/core")
+import mw
+
+
+app_debug = False
+if mw.isAppleSystem():
+ app_debug = True
+
+
+def getPluginName():
+ return 'op_load_balance'
+
+
+def getPluginDir():
+ return mw.getPluginDir() + '/' + getPluginName()
+
+
+def getServerDir():
+ return mw.getServerDir() + '/' + getPluginName()
+
+
+def getArgs():
+ args = sys.argv[2:]
+ tmp = {}
+ args_len = len(args)
+
+ if args_len == 1:
+ t = args[0].strip('{').strip('}')
+ if t.strip() == '':
+ tmp = []
+ else:
+ t = t.split(':')
+ tmp[t[0]] = t[1]
+ tmp[t[0]] = t[1]
+ elif args_len > 1:
+ for i in range(len(args)):
+ t = args[i].split(':')
+ tmp[t[0]] = t[1]
+ return tmp
+
+
+def checkArgs(data, ck=[]):
+ for i in range(len(ck)):
+ if not ck[i] in data:
+ return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
+ return (True, mw.returnJson(True, 'ok'))
+
+
+def getConf():
+ path = getServerDir() + "/cfg.json"
+
+ if not os.path.exists(path):
+ mw.writeFile(path, '[]')
+
+ c = mw.readFile(path)
+ return json.loads(c)
+
+
+def writeConf(data):
+ path = getServerDir() + "/cfg.json"
+ mw.writeFile(path, json.dumps(data))
+
+
+def contentReplace(content):
+ service_path = mw.getServerDir()
+ content = content.replace('{$ROOT_PATH}', mw.getRootDir())
+ content = content.replace('{$SERVER_PATH}', service_path)
+ content = content.replace('{$APP_PATH}', app_path)
+ return content
+
+
+def restartWeb():
+ mw.opWeb('stop')
+ mw.opWeb('start')
+
+
+def loadBalanceConf():
+ path = mw.getServerDir() + '/web_conf/nginx/vhost/load_balance.conf'
+ return path
+
+
+def initDreplace():
+
+ dst_conf_tpl = getPluginDir() + '/conf/load_balance.conf'
+ dst_conf = loadBalanceConf()
+
+ if not os.path.exists(dst_conf):
+ con = mw.readFile(dst_conf_tpl)
+ mw.writeFile(dst_conf, con)
+
+
+def status():
+ if not mw.getWebStatus():
+ return 'stop'
+
+ dst_conf = loadBalanceConf()
+ if not os.path.exists(dst_conf):
+ return 'stop'
+
+ return 'start'
+
+
+def start():
+ initDreplace()
+ restartWeb()
+ return 'ok'
+
+
+def stop():
+ dst_conf = loadBalanceConf()
+ os.remove(dst_conf)
+
+ deleteLoadBalanceAllCfg()
+ restartWeb()
+ return 'ok'
+
+
+def restart():
+ restartWeb()
+ return 'ok'
+
+
+def reload():
+ restartWeb()
+ return 'ok'
+
+
+def installPreInspection():
+ check_op = mw.getServerDir() + "/openresty"
+ if not os.path.exists(check_op):
+ return "请先安装OpenResty"
+ return 'ok'
+
+
+def deleteLoadBalanceAllCfg():
+ cfg = getConf()
+ upstream_dir = mw.getServerDir() + '/web_conf/nginx/upstream'
+ lua_dir = mw.getServerDir() + '/web_conf/nginx/lua/init_worker_by_lua_file'
+ rewrite_dir = mw.getServerDir() + '/web_conf/nginx/rewrite'
+ vhost_dir = mw.getServerDir() + '/web_conf/nginx/vhost'
+
+ for conf in cfg:
+ upstream_file = upstream_dir + '/' + conf['upstream_name'] + '.conf'
+ if os.path.exists(upstream_file):
+ os.remove(upstream_file)
+
+ lua_file = lua_dir + '/' + conf['upstream_name'] + '.lua'
+ if os.path.exists(lua_file):
+ os.remove(lua_file)
+
+ rewrite_file = rewrite_dir + '/' + conf['domain'] + '.conf'
+ mw.writeFile(rewrite_file, '')
+
+ path = vhost_dir + '/' + conf['domain'] + '.conf'
+
+ content = mw.readFile(path)
+ content = re.sub('include ' + upstream_file + ';' + "\n", '', content)
+ mw.writeFile(path, content)
+
+ mw.opLuaInitWorkerFile()
+
+
+def makeConfServerList(data):
+ slist = ''
+ for x in data:
+ slist += 'server '
+ slist += x['ip'] + ':' + x['port']
+
+ if x['state'] == '0':
+ slist += ' down;\n\t'
+ continue
+
+ if x['state'] == '2':
+ slist += ' backup;\n\t'
+ continue
+
+ slist += ' weight=' + x['weight']
+ slist += ' max_fails=' + x['max_fails']
+ slist += ' fail_timeout=' + x['fail_timeout'] + "s;\n\t"
+ return slist
+
+
+def makeLoadBalanceAllCfg(row):
+ # 生成所有配置
+ cfg = getConf()
+
+ upstream_dir = mw.getServerDir() + '/web_conf/nginx/upstream'
+ rewrite_dir = mw.getServerDir() + '/web_conf/nginx/rewrite'
+ vhost_dir = mw.getServerDir() + '/web_conf/nginx/vhost'
+ upstream_tpl = getPluginDir() + '/conf/upstream.tpl.conf'
+ rewrite_tpl = getPluginDir() + '/conf/rewrite.tpl.conf'
+
+ if not os.path.exists(upstream_dir):
+ os.makedirs(upstream_dir)
+
+ conf = cfg[row]
+
+ # replace vhost start
+ vhost_file = vhost_dir + '/' + conf['domain'] + '.conf'
+ vcontent = mw.readFile(vhost_file)
+
+ vhost_find_str = 'upstream/' + conf['upstream_name'] + '.conf'
+ vhead = 'include ' + mw.getServerDir() + '/web_conf/nginx/' + \
+ vhost_find_str + ';'
+
+ vpos = vcontent.find(vhost_find_str)
+ if vpos < 0:
+ vcontent = vhead + "\n" + vcontent
+ mw.writeFile(vhost_file, vcontent)
+ # replace vhost end
+
+ # make upstream start
+ upstream_file = upstream_dir + '/' + conf['upstream_name'] + '.conf'
+ content = ''
+ if len(conf['node_list']) > 0:
+ content = mw.readFile(upstream_tpl)
+ slist = makeConfServerList(conf['node_list'])
+ content = content.replace('{$NODE_SERVER_LIST}', slist)
+ content = content.replace('{$UPSTREAM_NAME}', conf['upstream_name'])
+ if conf['node_algo'] != 'polling':
+ content = content.replace('{$NODE_ALGO}', conf['node_algo'] + ';')
+ else:
+ content = content.replace('{$NODE_ALGO}', '')
+ mw.writeFile(upstream_file, content)
+ # make upstream end
+
+ # make rewrite start
+ rewrite_file = rewrite_dir + '/' + conf['domain'] + '.conf'
+ rcontent = ''
+ if len(conf['node_list']) > 0:
+ rcontent = mw.readFile(rewrite_tpl)
+ rcontent = rcontent.replace('{$UPSTREAM_NAME}', conf['upstream_name'])
+ mw.writeFile(rewrite_file, rcontent)
+ # make rewrite end
+
+ # health check start
+ lua_dir = mw.getServerDir() + '/web_conf/nginx/lua/init_worker_by_lua_file'
+ lua_init_worker_file = lua_dir + '/' + conf['upstream_name'] + '.lua'
+ if conf['node_health_check'] == 'ok':
+ lua_dir_tpl = getPluginDir() + '/lua/health_check.lua.tpl'
+ content = mw.readFile(lua_dir_tpl)
+ content = content.replace('{$UPSTREAM_NAME}', conf['upstream_name'])
+ content = content.replace('{$DOMAIN}', conf['domain'])
+ mw.writeFile(lua_init_worker_file, content)
+ else:
+ if os.path.exists(lua_init_worker_file):
+ os.remove(lua_init_worker_file)
+
+ mw.opLuaInitWorkerFile()
+ # health check end
+ return True
+
+
+def add_load_balance(args):
+
+ data = checkArgs(
+ args, ['domain', 'upstream_name', 'node_algo', 'node_list', 'node_health_check'])
+ if not data[0]:
+ return data[1]
+
+ domain_json = args['domain']
+
+ tmp = json.loads(domain_json)
+ domain = tmp['domain']
+
+ cfg = getConf()
+ cfg_len = len(cfg)
+ tmp = {}
+ tmp['domain'] = domain
+ tmp['data'] = args['domain']
+ tmp['upstream_name'] = args['upstream_name']
+ tmp['node_algo'] = args['node_algo']
+ tmp['node_list'] = args['node_list']
+ tmp['node_health_check'] = args['node_health_check']
+ cfg.append(tmp)
+ writeConf(cfg)
+
+ import site_api
+ sobj = site_api.site_api()
+ domain_path = mw.getWwwDir() + '/' + domain
+
+ ps = '负载均衡[' + domain + ']'
+ data = sobj.add(domain_json, '80', ps, domain_path, '00')
+
+ makeLoadBalanceAllCfg(cfg_len)
+ mw.restartWeb()
+ return mw.returnJson(True, '添加成功', data)
+
+
+def edit_load_balance(args):
+ data = checkArgs(
+ args, ['row', 'node_algo', 'node_list', 'node_health_check'])
+ if not data[0]:
+ return data[1]
+
+ row = int(args['row'])
+
+ cfg = getConf()
+ tmp = cfg[row]
+ tmp['node_algo'] = args['node_algo']
+ tmp['node_list'] = args['node_list']
+ tmp['node_health_check'] = args['node_health_check']
+ cfg[row] = tmp
+ writeConf(cfg)
+
+ makeLoadBalanceAllCfg(row)
+ mw.restartWeb()
+ return mw.returnJson(True, '修改成功', data)
+
+
+def loadBalanceList():
+ cfg = getConf()
+ return mw.returnJson(True, 'ok', cfg)
+
+
+def loadBalanceDelete():
+ args = getArgs()
+ data = checkArgs(args, ['row'])
+ if not data[0]:
+ return data[1]
+
+ row = int(args['row'])
+
+ cfg = getConf()
+ data = cfg[row]
+
+ import site_api
+ sobj = site_api.site_api()
+
+ sid = mw.M('sites').where('name=?', (data['domain'],)).getField('id')
+
+ if type(sid) == list:
+ del(cfg[row])
+ writeConf(cfg)
+ return mw.returnJson(False, '已经删除了!')
+
+ status = sobj.delete(sid, data['domain'], 1)
+ status_data = json.loads(status)
+
+ if status_data['status']:
+ del(cfg[row])
+ writeConf(cfg)
+
+ upstream_dir = mw.getServerDir() + '/web_conf/nginx/upstream'
+ rewrite_dir = mw.getServerDir() + '/web_conf/nginx/rewrite'
+
+ upstream_file = upstream_dir + '/' + data['upstream_name'] + '.conf'
+ if os.path.exists(upstream_file):
+ os.remove(upstream_file)
+
+ rewrite_file = rewrite_dir + '/' + data['domain'] + '.conf'
+ if os.path.exists(rewrite_file):
+ mw.writeFile(rewrite_file, '')
+
+ return mw.returnJson(status_data['status'], status_data['msg'])
+
+
+def http_get(url):
+ ret = re.search(r'https://', url)
+ if ret:
+ try:
+ from gevent import monkey
+ monkey.patch_ssl()
+ import requests
+ ret = requests.get(url=str(url), verify=False, timeout=10)
+ status = [200, 301, 302, 404, 403]
+ if ret.status_code in status:
+ return True
+ else:
+ return False
+ except:
+ return False
+ else:
+ try:
+ if sys.version_info[0] == 2:
+ import urllib2
+ rec = urllib2.urlopen(url, timeout=3)
+ else:
+ import urllib.request
+ rec = urllib.request.urlopen(url, timeout=3)
+ status = [200, 301, 302, 404, 403]
+ if rec.getcode() in status:
+ return True
+ return False
+ except:
+ return False
+
+
+def checkUrl():
+ args = getArgs()
+ data = checkArgs(args, ['ip', 'port', 'path'])
+ if not data[0]:
+ return data[1]
+
+ ip = args['ip']
+ port = args['port']
+ path = args['path']
+
+ if port == '443':
+ url = 'https://' + str(ip) + ':' + str(port) + str(path.strip())
+ else:
+ url = 'http://' + str(ip) + ':' + str(port) + str(path.strip())
+ ret = http_get(url)
+ if not ret:
+ return mw.returnJson(False, '访问节点[%s]失败' % url)
+ return mw.returnJson(True, '访问节点[%s]成功' % url)
+
+
+def getHealthStatus():
+ args = getArgs()
+ data = checkArgs(args, ['row'])
+ if not data[0]:
+ return data[1]
+
+ row = int(args['row'])
+
+ cfg = getConf()
+ data = cfg[row]
+
+ url = 'http://' + data['domain'] + \
+ '/upstream_status_' + data['upstream_name']
+
+ url_data = mw.httpGet(url)
+ return mw.returnJson(True, 'ok', json.loads(url_data))
+
+
+def getLogs():
+ args = getArgs()
+ data = checkArgs(args, ['domain'])
+ if not data[0]:
+ return data[1]
+
+ domain = args['domain']
+ logs = mw.getLogsDir() + '/' + domain + '.log'
+ return logs
+
+if __name__ == "__main__":
+ func = sys.argv[1]
+ if func == 'status':
+ print(status())
+ elif func == 'start':
+ print(start())
+ elif func == 'stop':
+ print(stop())
+ elif func == 'restart':
+ print(restart())
+ elif func == 'reload':
+ print(reload())
+ elif func == 'install_pre_inspection':
+ print(installPreInspection())
+ elif func == 'add_load_balance':
+ print(addLoadBalance())
+ elif func == 'load_balance_list':
+ print(loadBalanceList())
+ elif func == 'load_balance_delete':
+ print(loadBalanceDelete())
+ elif func == 'check_url':
+ print(checkUrl())
+ elif func == 'get_logs':
+ print(getLogs())
+ elif func == 'get_health_status':
+ print(getHealthStatus())
+ else:
+ print('error')
diff --git a/plugins/op_load_balance/info.json b/plugins/op_load_balance/info.json
new file mode 100755
index 000000000..185572878
--- /dev/null
+++ b/plugins/op_load_balance/info.json
@@ -0,0 +1,16 @@
+{
+ "title":"OP负载均衡",
+ "tip":"soft",
+ "name":"op_load_balance",
+ "type":"其他插件",
+ "install_pre_inspection":true,
+ "ps":"基于OpenResty的负载均衡",
+ "shell":"install.sh",
+ "checks":"server/op_load_balance",
+ "path":"server/op_load_balance",
+ "author":"midoks",
+ "home":"https://github.com/midoks",
+ "date":"2023-02-02",
+ "pid": "1",
+ "versions": ["1.0"]
+}
\ No newline at end of file
diff --git a/plugins/op_load_balance/install.sh b/plugins/op_load_balance/install.sh
new file mode 100755
index 000000000..d78bf73fb
--- /dev/null
+++ b/plugins/op_load_balance/install.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
+export PATH
+
+curPath=`pwd`
+rootPath=$(dirname "$curPath")
+rootPath=$(dirname "$rootPath")
+serverPath=$(dirname "$rootPath")
+
+install_tmp=${rootPath}/tmp/mw_install.pl
+
+action=$1
+version=$2
+sys_os=`uname`
+
+if [ -f ${rootPath}/bin/activate ];then
+ source ${rootPath}/bin/activate
+fi
+
+if [ "$sys_os" == "Darwin" ];then
+ BAK='_bak'
+else
+ BAK=''
+fi
+
+Install_App(){
+ echo '正在安装脚本文件...' > $install_tmp
+ mkdir -p $serverPath/op_load_balance
+ echo "${version}" > $serverPath/op_load_balance/version.pl
+ cd ${rootPath} && python3 ${rootPath}/plugins/op_load_balance/index.py start
+ echo 'install ok' > $install_tmp
+}
+
+Uninstall_App(){
+ cd ${rootPath} && python3 ${rootPath}/plugins/op_load_balance/index.py stop
+ rm -rf $serverPath/op_load_balance
+}
+
+
+action=$1
+if [ "${1}" == 'install' ];then
+ Install_App
+else
+ Uninstall_App
+fi
diff --git a/plugins/op_load_balance/js/app.js b/plugins/op_load_balance/js/app.js
new file mode 100644
index 000000000..3ffca2292
--- /dev/null
+++ b/plugins/op_load_balance/js/app.js
@@ -0,0 +1,606 @@
+function ooPost(method,args,callback){
+ var _args = null;
+ if (typeof(args) == 'string'){
+ _args = JSON.stringify(toArrayObject(args));
+ } else {
+ _args = JSON.stringify(args);
+ }
+
+ var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
+ $.post('/plugins/run', {name:'op_load_balance', func:method, args:_args}, function(data) {
+ layer.close(loadT);
+ if (!data.status){
+ layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
+ return;
+ }
+
+ if(typeof(callback) == 'function'){
+ callback(data);
+ }
+ },'json');
+}
+
+function ooAsyncPost(method,args){
+ var _args = null;
+ if (typeof(args) == 'string'){
+ _args = JSON.stringify(toArrayObject(args));
+ } else {
+ _args = JSON.stringify(args);
+ }
+ return syncPost('/plugins/run', {name:'op_load_balance', func:method, args:_args});
+}
+
+function ooPostCallbak(method, args, callback){
+ var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
+
+ var req_data = {};
+ req_data['name'] = 'op_load_balance';
+ req_data['func'] = method;
+ args['version'] = '1.0';
+
+ if (typeof(args) == 'string'){
+ req_data['args'] = JSON.stringify(toArrayObject(args));
+ } else {
+ req_data['args'] = JSON.stringify(args);
+ }
+
+ $.post('/plugins/callback', req_data, function(data) {
+ layer.close(loadT);
+ if (!data.status){
+ layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
+ return;
+ }
+
+ if(typeof(callback) == 'function'){
+ callback(data);
+ }
+ },'json');
+}
+
+function addNode(){
+ layer.open({
+ type: 1,
+ area: ['450px','580px'],
+ title: '添加节点',
+ closeBtn: 1,
+ shift: 5,
+ shadeClose: true,
+ btn:['提交','关闭'],
+ content: "",
+ success:function(){
+ },
+ yes:function(index) {
+
+ var ip = $('input[name="ip"]').val();
+ var port = $('input[name="port"]').val();
+ var path = $('input[name="path"]').val();
+ var state = $('select[name="state"]').val();
+ var weight = $('input[name="weight"]').val();
+ var max_fails = $('input[name="max_fails"]').val();
+ var fail_timeout = $('input[name="fail_timeout"]').val();
+
+ ooPost('check_url', {ip:ip,port:port,path:path},function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+ showMsg(rdata.msg, function(){
+ if (rdata.status){
+ layer.close(index);
+ $('#nodecon .nulltr').hide();
+
+ var tbody = '';
+ tbody +=''+ip+' | ';
+ tbody +=''+port+' | ';
+ tbody +=''+path+' | ';
+
+ tbody +=" | ";
+
+ tbody +=' | ';
+ tbody +=' | ';
+ tbody +=' | ';
+ tbody +='删除 | ';
+ tbody += '
';
+ $('#nodecon').append(tbody);
+
+ $('#nodecon .delete').click(function(){
+ $(this).parent().parent().remove();
+ if ($('#nodecon tr').length == 1 ){
+ $('#nodecon .nulltr').show();
+ }
+ });
+ }
+ },{ icon: rdata.status ? 1 : 2 }, 2000);
+ });
+ }
+ });
+}
+
+function addBalance(){
+ layer.open({
+ type: 1,
+ area: ['750px','460px'],
+ title: '创建负载',
+ closeBtn: 1,
+ shift: 5,
+ shadeClose: true,
+ btn:['提交','关闭'],
+ content: "",
+ success:function(){
+ $('textarea[name="load_domain"]').attr('placeholder','每行填写一个域名,默认为80端口。\n泛解析添加方法 *.domain.com\n如另加端口格式为 www.domain.com:88');
+ var rval = getRandomString(6);
+ $('input[name="upstream_name"]').val('load_balance_'+rval);
+
+ $('.add_node').click(function(){
+ addNode();
+ });
+ },
+ yes:function(index) {
+ var data = {};
+
+ var upstream_name = $('input[name="upstream_name"]').val();
+ if (upstream_name == ''){
+ layer.msg('负载名称不能为空!',{icon:0,time:2000,shade: [0.3, '#000']});
+ return;
+ }
+
+ var domain = $('textarea[name="load_domain"]').val().replace('http://','').replace('https://','').split("\n");
+ if (domain[0] == ''){
+ layer.msg('域名不能为空!',{icon:0,time:2000,shade: [0.3, '#000']});
+ return;
+ }
+
+ var domainlist = '';
+ for(var i=1; i\
+ \
+ \
+
节点调度\
+
\
+ \
+
\
+
\
+ \
+
节点健康检查\
+
\
+ \
+
\
+
\
+ \
+
节点\
+
\
+
\
+
\
+ \
+ \
+ IP地址 | \
+ 端口 | \
+ 验证路径 | \
+ 状态 | \
+ 权重 | \
+ 阀值 | \
+ 恢复时间 | \
+ 操作 | \
+
\
+ \
+ \
+ \
+ 当前节点为空,请至少添加一个普通节点 | \
+
\
+ \
+
\
+
\
+
添加节点\
+
\
+
\
+ ",
+ success:function(){
+ $('input[name="upstream_name"]').val(data['upstream_name']);
+ $('select[name="node_algo"]').val(data['node_algo']);
+
+ $('input[name="node_health_check"]').prop('checked',false);
+ if (data['node_health_check'] == 'ok'){
+ $('input[name="node_health_check"]').prop('checked',true);
+ }
+
+ var node_list = data['node_list'];
+ if (node_list.length>0){
+ $('#nodecon .nulltr').hide();
+ }
+
+ var state_option_list = {
+ '1':'参与者',
+ '2':'备份',
+ '0':'停用',
+ }
+
+ for (var n in node_list) {
+
+ var tbody = '';
+ tbody +=''+node_list[n]['ip']+' | ';
+ tbody +=''+node_list[n]['port']+' | ';
+ tbody +=''+node_list[n]['path']+' | ';
+
+ tbody +=" | ";
+
+ tbody +=' | ';
+ tbody +=' | ';
+ tbody +=' | ';
+ tbody +='删除 | ';
+ tbody += '
';
+ $('#nodecon').append(tbody);
+ }
+
+ $('#nodecon .delete').click(function(){
+ $(this).parent().parent().remove();
+ if ($('#nodecon tr').length == 1 ){
+ $('#nodecon .nulltr').show();
+ }
+ });
+
+ $('.add_node').click(function(){
+ addNode();
+ });
+ },
+ yes:function(index) {
+ var data = {};
+
+ data['node_algo'] = $('select[name="node_algo"]').val();
+ data['node_health_check'] = 'fail';
+ if ($('input[name="node_health_check"]').prop('checked')){
+ data['node_health_check'] = 'ok';
+ }
+
+ var node_list = [];
+ $('#nodecon tr').each(function(){
+
+ var ip = $(this).find('td').eq(0).text();
+ var port = $(this).find('td').eq(1).text();
+
+ if (port == ''){return;}
+
+ var path = $(this).find('td').eq(2).text();
+ var state = $(this).find('select[name="state"]').val();
+ var weight = $(this).find('input[name="weight"]').val();
+ var max_fails = $(this).find('input[name="max_fails"]').val();
+ var fail_timeout = $(this).find('input[name="fail_timeout"]').val();
+
+ var tmp = {
+ ip:ip,
+ port:port,
+ path:path,
+ state:state,
+ weight:weight,
+ max_fails:max_fails,
+ fail_timeout:fail_timeout,
+ }
+ node_list.push(tmp);
+ });
+ data['node_list'] = node_list;
+ data['row'] = row;
+ ooPostCallbak('edit_load_balance', data, function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+ showMsg(rdata.msg, function(){
+ layer.close(index);
+ loadBalanceListRender();
+ },{ icon: rdata.status ? 1 : 2 }, 2000);
+ });
+ }
+ });
+}
+
+function loadBalanceListRender(){
+ ooPost('load_balance_list', {}, function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+ var alist = rdata.data;
+
+ var tbody = '';
+ for (var i = 0; i < alist.length; i++) {
+ tbody += '';
+ tbody += ''+alist[i]['domain']+' | ';
+ tbody += ''+alist[i]['upstream_name']+' | ';
+ tbody += ''+alist[i]['node_list'].length+' | ';
+ tbody += '查看 | ';
+ tbody += '查看 | ';
+ tbody += '修改 | 删除 | ';
+ tbody += '
';
+ }
+
+ $('#nodeTable').html(tbody);
+ $('.nodeTablePage .Pcount').text('共'+alist.length+'条');
+ $('#nodeTable .edit').click(function(){
+ var row = $(this).data('row');
+ editBalance(alist[row],row);
+ });
+
+ $('#nodeTable .log_look').click(function(){
+ var row = $(this).data('row');
+ var args = {'domain':alist[row]['domain']};
+ pluginRollingLogs('op_load_balance','','get_logs',JSON.stringify(args),20);
+ });
+
+ $('#nodeTable .health_status').click(function(){
+ var row = $(this).data('row');
+ ooPost('get_health_status', {row:row}, function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+
+ var tval = '';
+ for (var i = 0; i < rdata.data.length; i++) {
+ tval += '';
+ tval += ''+rdata.data[i]['name']+' | ';
+
+ if (typeof(rdata.data[i]['down']) != 'undefined' && rdata.data[i]['down']){
+ tval += '不正常 | ';
+ } else{
+ tval += '正常 | ';
+ }
+ tval += '
';
+ }
+
+ var tbody = "";
+
+ layer.open({
+ type: 1,
+ area: ['500px','300px'],
+ title: '节点状态',
+ closeBtn: 1,
+ shift: 5,
+ shadeClose: true,
+ btn:['提交','关闭'],
+ content:tbody,
+ });
+ });
+ });
+
+ $('#nodeTable .delete').click(function(){
+ var row = $(this).data('row');
+ ooPost('load_balance_delete', {row:row}, function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+ showMsg(rdata.msg, function(){
+ loadBalanceListRender();
+ },{ icon: rdata.status ? 1 : 2 }, 2000);
+ });
+ });
+
+ });
+}
+
+function loadBalanceList() {
+ var body = '\
+
\
+
\
+
\
+
\
+ \
+ \
+ 网站 | \
+ 负载名称 | \
+ 节点 | \
+ 日志 | \
+ 状态 | \
+ 操作 | \
+
\
+ \
+ \
+
\
+
\
+
\
+
\
+
\
+ ';
+ $(".soft-man-con").html(body);
+ loadBalanceListRender();
+}
diff --git a/plugins/op_load_balance/lua/health_check.lua.tpl b/plugins/op_load_balance/lua/health_check.lua.tpl
new file mode 100644
index 000000000..94411c942
--- /dev/null
+++ b/plugins/op_load_balance/lua/health_check.lua.tpl
@@ -0,0 +1,18 @@
+local hc = require "resty.upstream.healthcheck"
+local ok, err = hc.spawn_checker {
+ shm = "healthcheck",
+ type = "http",
+ upstream = "{$UPSTREAM_NAME}",
+ http_req = "GET / HTTP/1.0\r\nHost: {$UPSTREAM_NAME}\r\n\r\n",
+ interval = 2000,
+ timeout = 6000,
+ fall = 3,
+ rise = 2,
+ valid_statuses = {200, 302},
+ concurrency = 20,
+}
+
+if not ok then
+ ngx.log(ngx.ERR, "=======> load balance health checker error: ", err)
+ return
+end
\ No newline at end of file