diff --git a/README.md b/README.md
index 7fccca8c..4d4bce9a 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ Web interface(user-friendly web GUI, alerting, monitoring and secure) for managi

# Features:
-1. Configure HAproxy In a jiffy with haproxy-wi
+1. Configure HAProxy In a jiffy with haproxy-wi
2. View and analyse Status of all Frontend/backend server via haproxy-wi from a single control panel.
3. Enable/disable servers through stats page without rebooting HAProxy
4. View/Analyse HAproxy logs straight from the haproxy-wi web interface
@@ -28,7 +28,7 @@ Web interface(user-friendly web GUI, alerting, monitoring and secure) for managi
13. Multiple User Roles support for privileged based Viewing and editing of Config.
14. Create Groups and add /remove servers to ensure proper identification for your HAproxy Clusters
15. Send notifications to telegram directly from haproxy-wi.
-16. haproxy-wi supports high Availability to ensure uptime to all Master slave servers configured.
+16. HAProxy-WI supports high Availability to ensure uptime to all Master slave servers configured.
17. SSL certificate support.
18. SSH Key support for managing multiple HAproxy Servers straight from haproxy-wi
19. SYN flood protect
@@ -41,6 +41,7 @@ Web interface(user-friendly web GUI, alerting, monitoring and secure) for managi
26. Keep active HAProxy service
27. Ability to hide parts of the config with tags for users with "guest" role: "HideBlockStart" and "HideBlockEnd"
28. Mobile-ready desing
+29. REST API

@@ -103,7 +104,7 @@ For Apache do virtualhost with cgi-bin. Like this:
```
# vi /etc/httpd/conf.d/haproxy-wi.conf
- WSGIDaemonProcess api user=apache group=apache processes=1 threads=5
+ WSGIDaemonProcess api display-name=%{GROUP} user=apache group=apache processes=1 threads=5
WSGIScriptAlias /api /var/www/haproxy-wi/api/app.wsgi
diff --git a/api/api.py b/api/api.py
index 19ee0557..5286f439 100644
--- a/api/api.py
+++ b/api/api.py
@@ -5,7 +5,7 @@ sys.path.append(os.path.dirname(__file__))
sys.path.append(os.path.join(sys.path[0], '/var/www/haproxy-wi/app/'))
os.chdir(os.path.dirname(__file__))
-from bottle import route, run, template, hook, response, request
+from bottle import route, run, template, hook, response, request, error
import sql
import funct
import api_funct
@@ -46,6 +46,11 @@ def enable_cors():
response.headers['Access-Control-Allow-Headers'] = _allow_headers
+@error(500)
+def error_handler_500(error):
+ return json.dumps({"status": "error", "message": str(error.exception)})
+
+
@route('/', method=['GET', 'POST'])
@route('/help', method=['GET', 'POST'])
def index():
@@ -53,14 +58,19 @@ def index():
return dict(error=_error_auth)
data = {
+ 'help': 'show all available endpoints',
'servers':'show info about all servers',
- 'server/':'show info about server by id or hostname or ip',
+ 'servers/status':'show status all servers',
+ 'server/':'show info about the server by id or hostname or ip',
'server//status':'show HAProxy status by id or hostname or ip',
'server//runtime':'exec HAProxy runtime commands by id or hostname or ip',
'server//backends':'show backends by id or hostname or ip',
'server//action/start':'start HAProxy service by id or hostname or ip',
'server//action/stop':'stop HAProxy service by id or hostname or ip',
- 'server//action/restart':'restart HAProxy service by id or hostname or ip'
+ 'server//action/restart':'restart HAProxy service by id or hostname or ip',
+ 'server//config/get':'get HAProxy config from the server by id or hostname or ip',
+ 'server//config/send':'send HAProxy config to the server by id or hostname or ip. Has to have config header with config and action header for action after upload. Action header accepts next value: save, test, reload and restart. May be empty for just save',
+ 'server//config/add':'add section to the HAProxy config by id or hostname or ip. Has to have config header with section and action header for action after upload. Action header accepts next value: save, test, reload and restart. May be empty for just save'
}
return dict(help=data)
@@ -91,6 +101,12 @@ def get_servers():
return dict(servers=data)
+@route('/servers/status', method=['GET', 'POST'])
+def callback():
+ if not check_login():
+ return dict(error=_error_auth)
+ return api_funct.get_all_statuses()
+
@route('/server/', method=['GET', 'POST'])
@route('/server/', method=['GET', 'POST'])
def callback(id):
@@ -129,4 +145,28 @@ def callback(id):
if not check_login():
return dict(error=_error_auth)
return api_funct.show_backends(id)
+
+
+@route('/server//config/get', method=['GET', 'POST'])
+@route('/server//config/get', method=['GET', 'POST'])
+def callback(id):
+ if not check_login():
+ return dict(error=_error_auth)
+ return api_funct.get_config(id)
+
+
+@route('/server//config/send', method=['GET', 'POST'])
+@route('/server//config/send', method=['GET', 'POST'])
+def callback(id):
+ if not check_login():
+ return dict(error=_error_auth)
+ return api_funct.upload_config(id)
+
+
+@route('/server//config/add', method=['GET', 'POST'])
+@route('/server//config/add', method=['GET', 'POST'])
+def callback(id):
+ if not check_login():
+ return dict(error=_error_auth)
+ return api_funct.add_to_config(id)
\ No newline at end of file
diff --git a/api/api_funct.py b/api/api_funct.py
index 4f37f790..e257ec1e 100644
--- a/api/api_funct.py
+++ b/api/api_funct.py
@@ -4,7 +4,7 @@ os.chdir(os.path.dirname(__file__))
sys.path.append(os.path.dirname(__file__))
sys.path.append(os.path.join(sys.path[0], '/var/www/haproxy-wi/app/'))
-from bottle import route, run, template, hook, response, request
+from bottle import route, run, template, hook, response, request, post
import sql
import funct
@@ -73,6 +73,28 @@ def get_status(id):
return dict(status=data)
+def get_all_statuses():
+ data = {}
+ try:
+ servers = sql.select_servers()
+ login = request.headers.get('login')
+ sock_port = sql.get_setting('haproxy_sock_port')
+
+ for s in servers:
+ servers = sql.get_dick_permit(username=login)
+
+ for s in servers:
+ cmd = 'echo "show info" |nc %s %s -w 1|grep -e "Ver\|CurrConns\|Maxco\|MB\|Uptime:"' % (s[2], sock_port)
+ data[s[2]] = {}
+ out = funct.subprocess_execute(cmd)
+ data[s[2]] = return_dict_from_out(s[1], out[0])
+ except:
+ data = {"error":"Cannot find the server"}
+ return dict(error=data)
+
+ return dict(status=data)
+
+
def actions(id, action):
if action == 'start' or action == 'stop' or action == 'restart':
try:
@@ -130,6 +152,115 @@ def show_backends(id):
return dict(error=data)
return dict(backends=data)
+
+
+def get_config(id):
+ data = {}
+ try:
+ servers = check_permit_to_server(id)
+ for s in servers:
+ cfg = '/tmp/'+s[2]+'.cfg'
+ out = funct.get_config(s[2], cfg)
+ os.system("sed -i 's/\\n/\n/g' "+cfg)
+ try:
+ conf = open(cfg, "r")
+ config_read = conf.read()
+ conf.close
+
+ except IOError:
+ conf = '
Can\'t read import config file'
+
+ data = {id: config_read}
+
+ except:
+ data = {}
+ data[id] = {"error":"Cannot find the server"}
+ return dict(error=data)
+
+ return dict(config=data)
+
+
+def upload_config(id):
+ data = {}
+ body = request.body.getvalue().decode('utf-8')
+ save = request.headers.get('action')
+ login = request.headers.get('login')
+
+ if save == '':
+ save = 'save'
+ elif save == 'restart':
+ save = ''
+
+ try:
+ servers = check_permit_to_server(id)
+
+ for s in servers:
+ ip = s[2]
+ cfg = '/tmp/'+ip+'.cfg'
+ cfg_for_save = hap_configs_dir + ip + "-" + funct.get_data('config') + ".cfg"
+
+ try:
+ with open(cfg, "w") as conf:
+ conf.write(body)
+ return_mess = 'config was uploaded'
+ os.system("/bin/cp %s %s" % (cfg, cfg_for_save))
+ out = funct.upload_and_restart(ip, cfg, just_save=save)
+ funct.logging('localhost', " config was uploaded via REST API", login=login)
+
+ if out:
+ return_mess == out
+ except IOError:
+ return_mess = "cannot upload config"
+
+ data = {id: return_mess}
+ except:
+ data = {}
+ data[id] = {"error":"Cannot find the server"}
+ return dict(error=data)
+
+ return dict(config=data)
+
+
+def add_to_config(id):
+ data = {}
+ body = request.body.getvalue().decode('utf-8')
+ save = request.headers.get('action')
+ hap_configs_dir = funct.get_config_var('configs', 'haproxy_save_configs_dir')
+ login = request.headers.get('login')
+
+ if save == '':
+ save = 'save'
+ elif save == 'restart':
+ save = ''
+
+ try:
+ servers = check_permit_to_server(id)
+
+ for s in servers:
+ ip = s[2]
+ cfg = '/tmp/'+ip+'.cfg'
+ cfg_for_save = hap_configs_dir + ip + "-" + funct.get_data('config') + ".cfg"
+ out = funct.get_config(ip, cfg)
+ try:
+ with open(cfg, "a") as conf:
+ conf.write('\n'+body+'\n')
+ return_mess = 'section was added to the config'
+ os.system("/bin/cp %s %s" % (cfg, cfg_for_save))
+ funct.logging('localhost', " section was added via REST API", login=login)
+ out = funct.upload_and_restart(ip, cfg, just_save=save)
+
+ if out:
+ return_mess = out
+ except IOError:
+ return_mess = "cannot upload config"
+
+ data = {id: return_mess}
+ except:
+ data = {}
+ data[id] = {"error":"Cannot find the server"}
+ return dict(error=data)
+
+ return dict(config=data)
\ No newline at end of file
diff --git a/api/app.wsgi b/api/app.wsgi
index 558553fe..38360063 100644
--- a/api/app.wsgi
+++ b/api/app.wsgi
@@ -5,4 +5,5 @@ import api
import bottle
bottle.debug(True)
-application = bottle.default_app()
\ No newline at end of file
+application = bottle.default_app()
+application.catchall = False
\ No newline at end of file
diff --git a/app/options.py b/app/options.py
index 667c5427..0d3c33f3 100644
--- a/app/options.py
+++ b/app/options.py
@@ -537,8 +537,8 @@ if form.getvalue('servaction') is not None:
if act == "showCompareConfigs":
import glob
from jinja2 import Environment, FileSystemLoader
- env = Environment(loader=FileSystemLoader('templates/ajax'), autoescape=True)
- template = env.get_template('/show_compare_configs.html')
+ env = Environment(loader=FileSystemLoader('templates/'), autoescape=True)
+ template = env.get_template('ajax/show_compare_configs.html')
left = form.getvalue('left')
right = form.getvalue('right')
@@ -552,8 +552,8 @@ if serv is not None and form.getvalue('right') is not None:
right = form.getvalue('right')
hap_configs_dir = funct.get_config_var('configs', 'haproxy_save_configs_dir')
cmd='diff -ub %s%s %s%s' % (hap_configs_dir, left, hap_configs_dir, right)
- env = Environment(loader=FileSystemLoader('templates/ajax'), autoescape=True, extensions=['jinja2.ext.loopcontrols', "jinja2.ext.do"])
- template = env.get_template('compare.html')
+ env = Environment(loader=FileSystemLoader('templates/'), autoescape=True, extensions=['jinja2.ext.loopcontrols', "jinja2.ext.do"])
+ template = env.get_template('ajax/compare.html')
output, stderr = funct.subprocess_execute(cmd)
template = template.render(stdout=output)
@@ -685,8 +685,9 @@ if form.getvalue('new_metrics'):
for i in metric:
label = str(i[5])
label = label.split(' ')[1]
- label = label.split(':')
- labels += label[0]+':'+label[1]+','
+ #label = label.split(':')
+ #labels += label[0]+':'+label[1]+','
+ labels += label+','
curr_con += str(i[1])+','
curr_ssl_con += str(i[2])+','
sess_rate += str(i[3])+','
@@ -714,8 +715,8 @@ if form.getvalue('new_waf_metrics'):
for i in metric:
label = str(i[2])
label = label.split(' ')[1]
- label = label.split(':')
- labels += label[0]+':'+label[1]+','
+ # label = label.split(':')
+ labels += label[0]+','
curr_con += str(i[1])+','
metrics['chartData']['labels'] = labels
diff --git a/app/overview.py b/app/overview.py
index 68d110b7..46fbbfc8 100644
--- a/app/overview.py
+++ b/app/overview.py
@@ -30,6 +30,8 @@ try:
metrics_worker, stderr = funct.subprocess_execute(cmd)
cmd = "ps ax |grep -e 'keep_alive.py' |grep -v grep |wc -l"
keep_alive, stderr = funct.subprocess_execute(cmd)
+ cmd = "ps ax |grep '(wsgi:api)'|grep -v grep|wc -l"
+ api, stderr = funct.subprocess_execute(cmd)
except:
pass
@@ -47,6 +49,7 @@ template = template.render(h2 = 1,
checker_master = ''.join(checker_master),
checker_worker = ''.join(checker_worker),
keep_alive = ''.join(keep_alive),
+ api = ''.join(api),
date = funct.get_data('logs'),
error = stderr,
versions = funct.versions(),
diff --git a/app/templates/ajax/overview.html b/app/templates/ajax/overview.html
index 5e13c682..edd32f10 100644
--- a/app/templates/ajax/overview.html
+++ b/app/templates/ajax/overview.html
@@ -14,11 +14,11 @@
{% if service.5.0|length() == 0 %}
-
+
{% elif service.5.0 != '' and service.4|int() == 0 %}
-
+
{% elif service.5.0 != '' and service.4|int() >= 1 %}
-
+
{% endif %}
|
|
diff --git a/app/templates/ajax/show_compare_configs.html b/app/templates/ajax/show_compare_configs.html
index c790164f..b2bbfb60 100644
--- a/app/templates/ajax/show_compare_configs.html
+++ b/app/templates/ajax/show_compare_configs.html
@@ -23,7 +23,7 @@
{% endfor %}
- {{ input('serv', value=serv) }}
+ {{ input('serv', type='hidden', value=serv) }}
{{ input('open', type='hidden', value='open') }}
Show
diff --git a/app/templates/ovw.html b/app/templates/ovw.html
index 5c928b04..5d71916c 100644
--- a/app/templates/ovw.html
+++ b/app/templates/ovw.html
@@ -125,8 +125,15 @@
Checker workers
{% endif %}
- |
-
+
+ {% if api|int() == 0 %}
+
+ API
+ {% else %}
+
+ API
+ {% endif %}
+ |
{% if role <= 1 %}
diff --git a/app/tools/metrics_waf_worker.py b/app/tools/metrics_waf_worker.py
index 9ca195a7..bb6c480d 100644
--- a/app/tools/metrics_waf_worker.py
+++ b/app/tools/metrics_waf_worker.py
@@ -41,7 +41,7 @@ def main(serv, port):
for i in range(0,len(metric)):
sql.insert_waf_mentrics(serv, metric[i])
- time.sleep(60)
+ time.sleep(30)
if killer.kill_now:
break
diff --git a/app/tools/metrics_worker.py b/app/tools/metrics_worker.py
index b1336646..cfccec9a 100644
--- a/app/tools/metrics_worker.py
+++ b/app/tools/metrics_worker.py
@@ -41,7 +41,7 @@ def main(serv, port):
sql.insert_mentrics(serv, metrics[0], metrics[1], metrics[2], metrics[3])
- time.sleep(60)
+ time.sleep(30)
if killer.kill_now:
break
diff --git a/config_other/httpd/haproxy-wi.conf b/config_other/httpd/haproxy-wi.conf
index 2675ae58..3876672e 100644
--- a/config_other/httpd/haproxy-wi.conf
+++ b/config_other/httpd/haproxy-wi.conf
@@ -1,5 +1,5 @@
- WSGIDaemonProcess api user=apache group=apache processes=1 threads=5
+ WSGIDaemonProcess api display-name=%{GROUP} user=apache group=apache processes=1 threads=5
WSGIScriptAlias /api /var/www/haproxy-wi/api/app.wsgi
diff --git a/inc/metrics.js b/inc/metrics.js
index 22f24258..90335d90 100644
--- a/inc/metrics.js
+++ b/inc/metrics.js
@@ -47,6 +47,7 @@ function renderChart(data, labels, server) {
]
},
options: {
+ maintainAspectRatio: false,
title: {
display: true,
text: data[3],
@@ -63,7 +64,9 @@ function renderChart(data, labels, server) {
legend: {
display: true,
labels: {
- fontColor: 'rgb(255, 99, 132)'
+ fontColor: 'rgb(255, 99, 132)',
+ defaultFontSize: '10',
+ defaultFontFamily: 'BlinkMacSystemFont'
},
}
}
@@ -89,7 +92,6 @@ function getWafChartData(server) {
});
}
function renderWafChart(data, labels, server) {
- console.log(server)
var ctx = 's_'+server
var myChart = new Chart(ctx, {
type: 'line',
@@ -105,6 +107,7 @@ function renderWafChart(data, labels, server) {
]
},
options: {
+ maintainAspectRatio: false,
title: {
display: true,
text: "WAF "+data[1],
@@ -121,7 +124,9 @@ function renderWafChart(data, labels, server) {
legend: {
display: true,
labels: {
- fontColor: 'rgb(255, 99, 132)'
+ fontColor: 'rgb(255, 99, 132)',
+ defaultFontSize: '10',
+ defaultFontFamily: 'BlinkMacSystemFont'
},
}
}
@@ -138,7 +143,7 @@ function loadMetrics() {
token: $('#token').val()
},
beforeSend: function() {
- $('#table_metrics').prepend('
')
+ $('#table_metrics').html('
')
},
type: "GET",
success: function (data) {
diff --git a/inc/style.css b/inc/style.css
index 4e06f8c4..4a7b1df7 100644
--- a/inc/style.css
+++ b/inc/style.css
@@ -821,7 +821,7 @@ label {
}
.chart-container {
position: relative;
- height: 400px;
+ height: 300px;
width: 49%;
float: left;
margin-top: 20px;
@@ -831,10 +831,7 @@ label {
#logo_span {
margin-left: 17%;
}
- .chart-container {
- height: 290px;
- width: 32.4%;
- }
+
}
@media (max-width: 1900px) {
#logo_span {
@@ -844,6 +841,16 @@ label {
margin-right: -150px;
}
}
+@media (max-width: 1450px) {
+ .ajax-server {
+ clear: both !important;
+ margin-left: 20px !important;
+ width: 88% !important;
+ }
+ .div-server {
+ margin-bottom: 30px !important;
+ }
+}
@media (max-width: 1280px) {
.div-server {
margin-bottom: 30px !important;
@@ -870,7 +877,11 @@ label {
margin-right: -50px;
}
.ajax-server {
- width: 750px !important;
+ width: 88% !important;
+ }
+ .haproxy-info {
+ width: 120px;
+ padding-left: 10px;
}
}
@media (max-width: 1024px) {
@@ -889,6 +900,13 @@ label {
.wrong-login {
margin-right: -150px;
}
+ .ajax-server {
+ width: 88% !important;
+ }
+ .haproxy-info {
+ width: 120px;
+ padding-left: 10px;
+ }
}
@media (max-width: 768px) {
#logo_span {
@@ -913,6 +931,13 @@ label {
.overview h3 {
width: 46.4% !important;
}
+ .ajax-server {
+ width: 88% !important;
+ }
+ .haproxy-info {
+ width: 120px;
+ padding-left: 10px;
+ }
}
@media (max-width: 667px) {
#logo_span {
@@ -931,6 +956,13 @@ label {
.wrong-login {
margin-right: -210px;
}
+ .ajax-server {
+ width: 88% !important;
+ }
+ .haproxy-info {
+ width: 120px;
+ padding-left: 10px;
+ }
}
.loading, .loading_full_page, .loading_hapwi_overview {
width: 100px;