Aidaho 2024-09-03 12:09:03 +03:00
parent 69e5c2cca9
commit 6d11d04577
14 changed files with 242 additions and 97 deletions

View File

@ -521,7 +521,8 @@ def del_ssl_cert(server_ip: str, cert_id: str) -> str:
def upload_ssl_cert(server_ip: str, ssl_name: str, ssl_cont: str) -> str:
cert_path = sql.get_setting('cert_path')
tmp_path = sql.get_setting('tmp_config_path')
slave_output = ''
output = []
server_ip = str(server_ip)
if ssl_name is None:
return 'error: Please enter a desired name'
@ -539,12 +540,13 @@ def upload_ssl_cert(server_ip: str, ssl_name: str, ssl_cont: str) -> str:
for master in masters:
if master[0] is not None:
config_mod.upload(master[0], f'{cert_path}/{name}', path_to_file)
slave_output += f'success: the SSL file has been uploaded to {master[0]} into: {cert_path}/{name} \n'
output.append(f'success: the SSL file has been uploaded to {master[0]} into: {cert_path}/{name}')
try:
config_mod.upload(server_ip, f'{cert_path}/{name}', path_to_file)
output.append(f'success: the SSL file has been uploaded to {server_ip} into: {cert_path}/{name}')
except Exception as e:
roxywi_common.logging('Roxy-WI server', str(e), roxywi=1)
return f'error: cannot upload SSL cert: {e}'
roxywi_common.logging(server_ip, f"add#ssl uploaded a new SSL cert {name}", roxywi=1, login=1)
return f'success: the SSL file has been uploaded to {server_ip} into: {cert_path}/{name} \n {slave_output}'
return output

View File

@ -1,27 +1,24 @@
from app.modules.db.db_model import SavedServer, Option
from app.modules.db.common import out_error
from app.modules.roxywi.exception import RoxywiResourceNotFound
def update_saved_server(server, description, saved_id):
query_update = SavedServer.update(server=server, description=description).where(SavedServer.id == saved_id)
try:
query_update.execute()
SavedServer.update(server=server, description=description).where(SavedServer.id == saved_id).execute()
except SavedServer.DoesNotExist:
raise RoxywiResourceNotFound
except Exception as e:
out_error(e)
return False
else:
return True
def delete_saved_server(saved_id):
query = SavedServer.delete().where(SavedServer.id == saved_id)
try:
query.execute()
SavedServer.delete().where(SavedServer.id == saved_id).execute()
except SavedServer.DoesNotExist:
raise RoxywiResourceNotFound
except Exception as e:
out_error(e)
return False
else:
return True
def delete_option(option_id):

View File

@ -17,6 +17,8 @@ class EscapedString(str):
if isinstance(field_value, str):
if cls.pattern.search(field_value):
return re.sub(cls.pattern, '', field_value)
elif '..' in field_value:
raise ValueError('nice try')
elif field_value == '':
return field_value
else:
@ -275,3 +277,14 @@ class PortScannerRequest(BaseModel):
enabled: Optional[bool] = 1
history: Optional[bool] = 1
notify: Optional[bool] = 1
class SSLCertUploadRequest(BaseModel):
server_ip: Union[IPvAnyAddress, DomainName]
name: EscapedString
cert: EscapedString
class SavedServerRequest(BaseModel):
server: EscapedString
description: Optional[EscapedString] = None

View File

@ -36,6 +36,10 @@ def generate_udp_inv(listener_id: int, action: str) -> object:
"id": listener['id'],
"config": listener['config'],
"lb_algo": listener['lb_algo'],
"check_enabled": listener['check_enabled'],
"delay_before_retry": listener['delay_before_retry'],
"delay_loop": listener['delay_loop'],
"retry": listener['retry'],
}
return inv, server_ips

View File

@ -2,7 +2,9 @@ import os
from flask import render_template, request, jsonify, redirect, url_for, g
from flask_jwt_extended import jwt_required, get_jwt
from flask_pydantic import validate
from app.modules.roxywi.class_models import SSLCertUploadRequest, DataStrResponse, SavedServerRequest, BaseResponse
from app.routes.add import bp
import app.modules.db.sql as sql
import app.modules.db.add as add_sql
@ -516,43 +518,40 @@ def delete_option(option_id):
@bp.route('/server/get/<int:group>')
def get_saved_server(group):
term = request.args.get('term')
term = common.checkAjaxInput(request.args.get('term'))
return jsonify(add_mod.get_saved_servers(group, term))
@bp.post('/server/save')
@bp.post('/server')
@get_user_params()
def save_saved_server():
server = common.checkAjaxInput(request.form.get('server'))
@validate(body=SavedServerRequest)
def saved_server(body: SavedServerRequest):
group = g.user_params['group_id']
desc = common.checkAjaxInput(request.form.get('desc'))
return add_mod.create_saved_server(server, group, desc)
@bp.post('/server/update')
def update_saved_server():
server = common.checkAjaxInput(request.form.get('server'))
server_id = int(request.form.get('id'))
desc = common.checkAjaxInput(request.form.get('desc'))
try:
add_sql.update_saved_server(server, desc, server_id)
data = add_mod.create_saved_server(body.server, group, body.description)
return DataStrResponse(data=data).model_dump(mode='json'), 201
except Exception as e:
return str(e)
else:
return 'ok'
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create server')
@bp.route('/server/delete/<int:server_id>')
@bp.put('/server/<int:server_id>')
@validate(body=SavedServerRequest)
def update_saved_server(server_id: int, body: SavedServerRequest):
try:
add_sql.update_saved_server(body.server, body.description, server_id)
return BaseResponse().model_dump(mode='json'), 201
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot update server')
@bp.delete('/server/<int:server_id>')
def delete_saved_server(server_id):
try:
add_sql.delete_saved_server(server_id)
return BaseResponse().model_dump(mode='json'), 204
except Exception as e:
return str(e)
else:
return 'ok'
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete server')
@bp.route('/certs/<server_ip>')
@ -572,12 +571,13 @@ def get_cert(server_ip, cert_id):
@bp.post('/cert/add')
def upload_cert():
server_ip = common.is_ip_or_dns(request.form.get('serv'))
ssl_name = request.form.get('ssl_name')
ssl_cont = request.form.get('ssl_cert')
return add_mod.upload_ssl_cert(server_ip, ssl_name, ssl_cont)
@validate(body=SSLCertUploadRequest)
def upload_cert(body: SSLCertUploadRequest):
try:
data = add_mod.upload_ssl_cert(body.server_ip, body.name, body.cert)
return jsonify(data), 201
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot upload SSL certificate')
@bp.route('/cert/get/raw/<server_ip>/<cert_id>')

View File

@ -2,18 +2,22 @@ virtual_server {{ vip }} {{ port }} {
lb_algo {{ lb_algo }}
lb_kind NAT
protocol UDP
delay_loop 10
delay_before_retry 10
retry 3
{% if check_enabled %}
delay_loop {{ delay_loop }}
delay_before_retry {{ delay_before_retry }}
retry {{ retry }}
{% endif %}
{% for server in config %}
real_server {{ server.backend_ip }} {{ server.port }} {
weight {{ server.weight }}
{% if check_enabled %}
MISC_CHECK {
misc_path "{{ service_dir }}/checks/udp_check.sh {{ server.backend_ip }} {{ server.port }}"
misc_timeout 5
}
{% endif %}
}
{% endfor %}
}

View File

@ -501,17 +501,18 @@ $( function() {
});
$('#add-saved-server-new').click(function () {
$.ajax({
url: "/add/server/save",
data: {
url: "/add/server",
data: JSON.stringify({
server: $('#new-saved-servers').val(),
desc: $('#new-saved-servers-description').val()
},
description: $('#new-saved-servers-description').val()
}),
type: "POST",
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.indexOf('error:') != '-1') {
if (data.status === 'failed') {
toastr.error(data);
} else {
$("#servers_table").append(data);
$("#servers_table").append(data.data);
setTimeout(function () {
$(".newsavedserver").removeClass("update");
}, 2500);
@ -866,22 +867,25 @@ $( function() {
if (!checkIsServerFiled('#serv4')) return false;
if (!checkIsServerFiled('#ssl_name', 'Enter the Certificate name')) return false;
if (!checkIsServerFiled('#ssl_cert', 'Paste the contents of the certificate file')) return false;
let jsonData = {
server_ip: $('#serv4').val(),
cert: $('#ssl_cert').val(),
name: $('#ssl_name').val()
}
$.ajax({
url: "/add/cert/add",
data: {
serv: $('#serv4').val(),
ssl_cert: $('#ssl_cert').val(),
ssl_name: $('#ssl_name').val()
},
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
type: "POST",
success: function (data) {
data = data.split("\n");
for (i = 0; i < data.length; i++) {
if (data[i]) {
if (data[i].indexOf('error: ') != '-1' || data[i].indexOf('Errno') != '-1') {
toastr.error(data[i]);
} else {
if (data[i] != '\n') {
if (data.error === 'failed') {
toastr.error(data.error);
} else {
for (let i = 0; i < data.length; i++) {
if (data[i]) {
if (data[i].indexOf('error: ') != '-1' || data[i].indexOf('Errno') != '-1') {
toastr.error(data[i]);
} else {
toastr.success(data[i]);
}
}
@ -1379,31 +1383,38 @@ function confirmDeleteSavedServer(id) {
});
}
function removeSavedServer(id) {
$("#servers-saved-"+id).css("background-color", "#f2dede");
$.ajax( {
url: "/add/server/delete/"+id,
success: function( data ) {
data = data.replace(/\s+/g,' ');
if(data.indexOf('ok') != '-1') {
$("#servers-saved-"+id).remove();
$("#servers-saved-" + id).css("background-color", "#f2dede");
$.ajax({
url: "/add/server/" + id,
type: "DELETE",
contentType: "application/json; charset=utf-8",
statusCode: {
204: function (xhr) {
$("#servers-saved-" + id).remove();
},
404: function (xhr) {
$("#servers-saved-" + id).remove();
}
},
success: function (data) {
if (data) {
if (data.status === "failed") {
toastr.error(data);
}
}
}
} );
});
}
function updateSavedServer(id) {
toastr.clear();
$.ajax( {
url: "/add/server/update",
data: {
server: $('#servers-ip-'+id).val(),
desc: $('#servers-desc-'+id).val(),
id: id,
},
type: "POST",
url: "/add/server/" + id,
type: "PUT",
data: JSON.stringify({"server": $('#servers-ip-'+id).val(), description: $('#servers-desc-'+id).val(),}),
contentType: "application/json; charset=utf-8",
success: function( data ) {
data = data.replace(/\s+/g,' ');
if (data.indexOf('error:') != '-1') {
toastr.error(data);
if (data.status === 'failed') {
toastr.error(data.error);
} else {
$("#servers-saved-"+id).addClass( "update", 1000 );
setTimeout(function() {

View File

@ -28,6 +28,13 @@ $( function() {
$('#new-listener-port').focus();
}
});
$('#check_enabled').click(function () {
if ($('#check_enabled').is(':checked')) {
$('.check_backends').show();
} else {
$('.check_backends').hide();
}
});
});
function getHAClusterVIPS(cluster_id) {
let vip_id = $('#vip');
@ -76,9 +83,20 @@ function createUDPListener(edited=false, listener_id=0, clean=true) {
$('#name').val(data.name.replaceAll("'", ""));
$('#new-listener-type').val(place);
$('#port').val(data.port);
$('#delay_loop').val(data.delay_loop);
$('#delay_before_retry').val(data.delay_before_retry);
$('#retry').val(data.retry);
$('#lb_algo').val(data.lb_algo).change();
$('#lb_algo').selectmenu('refresh');
$('#desc').val(data.description.replaceAll("'", ""));
if (data.check_enabled) {
$('#check_enabled').prop('checked', true);
$('.check_backends').show();
} else {
$('#check_enabled').prop('checked', false);
$('.check_backends').hide();
}
$("#check_enabled").checkboxradio("refresh");
if (place === 'cluster') {
$.when(getHAClusterVIPS(data.cluster_id)).done(function () {
$("#vip option").filter(function () {
@ -99,7 +117,7 @@ function createUDPListener(edited=false, listener_id=0, clean=true) {
$('#new-udp-servers-td').append('<a class="link add-server" title="Add backend server" onclick="createBackendServer()"></a>');
data.config = JSON.stringify(data.config);
let config = JSON.parse(data.config)
for(let server of config) {
for (let server of config) {
createBackendServer(server.backend_ip, server.port, server.weight);
}
}
@ -262,10 +280,15 @@ function getFormData($form) {
let unindexed_array = $form.serializeArray();
let indexed_array = {};
indexed_array['config'] = [];
indexed_array['check_enabled'] = 0;
$.map(unindexed_array, function (n, i) {
if (n['name'] === 'serv') {
indexed_array['server_id'] = n['value'];
} else if (n['name'] === 'check_enabled') {
if ($('#check_enabled').is(':checked')) {
indexed_array['check_enabled'] = 1;
}
} else {
indexed_array[n['name']] = n['value'];
}
@ -324,15 +347,7 @@ function saveUdpListener(jsonData, dialog_id, listener_id=0, edited=0, reconfigu
listener_id = data.id;
getUDPListener(listener_id, true);
}
// if (reconfigure) {
// NProgress.start();
// $.when(Reconfigure(listener_id)).done(function () {
// dialog_id.dialog("close");
// NProgress.done();
// });
// } else {
dialog_id.dialog("close");
// }
dialog_id.dialog("close");
toastr.success('Listener ' + data.status);
}
}
@ -421,9 +436,15 @@ function clearListenerDialog(edited=0) {
$('#new-listener-desc').val('');
$('#new-udp-ip').val('');
$('#vrrp-ip').prop("readonly", false);
$('#delay_loop').val(10);
$('#delay_before_retry').val(10);
$('#retry').val(3);
$('#new-listener-port').val('');
$("#cluster_id").attr('disabled', false);
$("#serv").attr('disabled', false);
$('#check_enabled').prop('checked', true);
$('.check_backends').show();
$("#check_enabled").checkboxradio("refresh");
clearUdpVip()
$('#new-udp-servers-td').empty();
$('#new-udp-servers-td').append('<a class="link add-server" title="Add backend server" onclick="createBackendServer()"></a>');

View File

@ -519,6 +519,13 @@
"listener_ip": "IP for binding UDP Listener. Start typing",
"listener_port": "Port for binding UDP Listener",
"balancing_type": "Balancing type",
"check_backends": "Enable backends checking",
"retry": "Number of retries",
"retry_title": "Maximum number of retries",
"delay_before_retry_title": "Delay between two successive retries",
"delay_before_retry": "Delay between retries",
"delay_loop_title": "Specify in seconds the interval between checks",
"delay_loop": "Interval between checks",
}
%}
{% set nettools_page = {

View File

@ -519,6 +519,13 @@
"listener_ip": "IP pour lier lécouteur UDP. Commencer à écrire",
"listener_port": "Port pour lier l'écouteur UDP",
"balancing_type": "Type d'équilibrage",
"check_backends": "Enable backends checking",
"retry": "Nombre de tentatives",
"retry_title": "Nombre maximal de tentatives",
"delay_before_retry_title": "Délai entre deux tentatives successives",
"delay_before_retry": "Délai entre les tentatives",
"delay_loop_title": "Spécifiez en secondes l'intervalle entre les contrôles",
"delay_loop": "Intervalle entre les contrôles",
}
%}
{% set nettools_page = {

View File

@ -519,6 +519,13 @@
"listener_ip": "IP para vincular o ouvinte UDP. Começe a digitar",
"listener_port": "Porta para ligação do ouvinte UDP",
"balancing_type": "Tipo de balanceamento",
"check_backends": "Enable backends checking",
"retry": "Número de tentativas",
"retry_title": "Número máximo de tentativas",
"delay_before_retry_title": "Atraso entre duas tentativas sucessivas",
"delay_before_retry": "Atraso entre tentativas",
"delay_loop_title": "Especifique em segundos o intervalo entre verificações",
"delay_loop": "Intervalo entre verificações",
}
%}
{% set nettools_page = {

View File

@ -519,6 +519,13 @@
"listener_ip": "IP для привязки UDP Listener. Начните печатать",
"listener_port": "Порт для привязки UDP Listener",
"balancing_type": "Тип балансировки",
"check_backends": "Enable backends checking",
"retry": "Количество повторных попыток",
"retry_title": "Максимальное количество повторных попыток",
"delay_before_retry_title": "Задержка между двумя последовательными попытками",
"delay_before_retry": "Задержка между повторными попытками",
"delay_loop_title": "Укажите в секундах интервал между проверками",
"delay_loop": "Интервал между проверками",
}
%}
{% set nettools_page = {

View File

@ -128,6 +128,39 @@
<a class="link add-server" id="frontend_add_acl" title="{{lang.words.add|title()}} {{ lang.words.backend }} {{ lang.words.server }}" style="cursor: pointer; display: none;"></a>
</td>
</tr>
<tr>
<td class="padding20">
{{ lang.udp_page.check_backends }}
<span class="need-field">*</span>
</td>
<td>
{{ checkbox('check_enabled', title=lang.udp_page.check_backends, checked='checked') }}
</td>
</tr>
<tr class="check_backends">
<td class="padding20">
{{ lang.udp_page.delay_loop }}
</td>
<td>
{{ input('delay_loop', title=lang.udp_page.delay_loop_title, value='10', type='number') }}
</td>
</tr>
<tr class="check_backends">
<td class="padding20">
{{ lang.udp_page.delay_before_retry }}
</td>
<td>
{{ input('delay_before_retry', title=lang.udp_page.delay_before_retry_title, value='10', type='number') }}
</td>
</tr>
<tr class="check_backends">
<td class="padding20">
{{ lang.udp_page.retry }}
</td>
<td>
{{ input('retry', title=lang.udp_page.retry_title, value='3', type='number') }}
</td>
</tr>
<tr>
<td class="padding20">
{{lang.words.desc|title()}}

View File

@ -166,10 +166,10 @@ class UDPListener(MethodView):
name:
type: string
cluster_id:
type: int
type: integer
description: Cluster ID where the UDP listener is located. Must be determined if server_id empty
server_id:
type: int
type: integer
description: Standalone mode. Server ID where the UDP listener is located. Must be determined if cluster_id empty
group_id:
type: string
@ -180,6 +180,22 @@ class UDPListener(MethodView):
description: "'rr': 'Round robin', 'wrr': 'Weighted Round Robin', 'lc': 'Least Connection', 'wlc': 'Weighted Least Connection', 'sh': 'Source Hashing', 'dh': 'Destination Hashing', 'lblc': 'Locality-Based Least Connection'"
schema:
enum: [rr, wrr, lc, wlc, sh, dh, wlc, lblc]
check_enabled:
type: integer
default: 1
description: Enable backend servers checking
delay_before_retry:
type: integer
default: 10
description: Delay between two successive retries
delay_loop:
type: integer
default: 10
description: Specify in seconds the interval between checks
retry:
type: integer
default: 3
description: Maximum number of retries before mark a backend server as down
description:
type: string
vip:
@ -254,10 +270,10 @@ class UDPListener(MethodView):
name:
type: string
cluster_id:
type: int
type: integer
description: Cluster ID where the UDP listener is located. Must be determined if server_id empty
server_id:
type: int
type: integer
description: Standalone mode. Server ID where the UDP listener is located. Must be determined if cluster_id empty
group_id:
type: string
@ -268,6 +284,22 @@ class UDPListener(MethodView):
description: "'rr': 'Round robin', 'wrr': 'Weighted Round Robin', 'lc': 'Least Connection', 'wlc': 'Weighted Least Connection', 'sh': 'Source Hashing', 'dh': 'Destination Hashing', 'lblc': 'Locality-Based Least Connection'"
schema:
enum: [rr, wrr, lc, wlc, sh, dh, wlc, lblc]
check_enabled:
type: integer
default: 1
description: Enable backend servers checking
delay_before_retry:
type: integer
default: 10
description: Delay between two successive retries
delay_loop:
type: integer
default: 10
description: Specify in seconds the interval between checks
retry:
type: integer
default: 3
description: Maximum number of retries before mark a backend server as down
description:
type: string
vip:
@ -418,11 +450,11 @@ class UDPListeners(MethodView):
try:
group_id = SupportClass.return_group_id(query)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, f'Cannot get UDP listeners')
return roxywi_common.handle_json_exceptions(e, 'Cannot get UDP listeners')
try:
listeners = udp_sql.select_listeners(group_id)
except Exception as e:
return roxywi_common.handle_json_exceptions(e, f'Cannot get UDP listeners')
return roxywi_common.handle_json_exceptions(e, 'Cannot get UDP listeners')
return jsonify([model_to_dict(listener, recurse=False) for listener in listeners])