v8.1.3: Add dark theme support and update HAProxy management.

Introduce a dark theme for the web interface, enabling users to switch between themes seamlessly. Additionally, enhance HAProxy management by updating available versions and streamlining API routes for list management, ensuring smoother operations and integration.
pull/403/head
Aidaho 2024-12-03 15:28:55 +03:00
parent 5bf719ef1c
commit 91802b3cd6
31 changed files with 582 additions and 169 deletions

View File

@ -13,6 +13,7 @@ from app.views.service.views import (ServiceView, ServiceActionView, ServiceBack
from app.views.service.haproxy_section_views import ListenSectionView, UserListSectionView, PeersSectionView, \
GlobalSectionView, DefaultsSectionView
from app.views.service.lets_encrypt_views import LetsEncryptsView, LetsEncryptView
from app.views.service.haproxy_lists_views import HaproxyListView
from app.views.ha.views import HAView, HAVIPView, HAVIPsView
from app.views.user.views import UserView, UserGroupView, UserRoles
from app.views.udp.views import UDPListener, UDPListeners, UDPListenerActionView
@ -75,6 +76,8 @@ register_api_id_ip(PeersSectionView, 'haproxy_peers_post', '/section/peers', met
register_api_id_ip(PeersSectionView, 'haproxy_peers', '/section/peers/<section_name>', methods=['GET', 'PUT', 'DELETE'])
register_api_id_ip(GlobalSectionView, 'haproxy_global', '/section/global', methods=['GET', 'PUT'])
register_api_id_ip(DefaultsSectionView, 'haproxy_defaults', '/section/defaults', methods=['GET', 'PUT'])
bp.add_url_rule('/service/<any(haproxy):service>/list/<list_name>/<color>', view_func=HaproxyListView.as_view('list_get'), methods=['GET'])
bp.add_url_rule('/service/<any(haproxy):service>/list', view_func=HaproxyListView.as_view('list_post'), methods=['POST', 'PUT', 'DELETE'])
bp.add_url_rule('/service/<any(nginx, apache):service>/<server_id>/config/list', view_func=ServiceConfigList.as_view('config_list_ip'), methods=['GET'])
bp.add_url_rule('/service/<any(nginx, apache):service>/<int:server_id>/config/list', view_func=ServiceConfigList.as_view('config_list'), methods=['GET'])
register_api_id_ip(CheckerView, 'checker', '/tools')

View File

@ -692,7 +692,7 @@ def update_db_v_8_1_2():
def update_ver():
try:
Version.update(version='8.1.2').execute()
Version.update(version='8.1.3').execute()
except Exception:
print('Cannot update version')

View File

@ -4,7 +4,7 @@ from flask import g
import app.modules.db.server as server_sql
import app.modules.roxywi.common as roxywi_common
from app.modules.roxywi.class_models import ServerRequest, GroupQuery, CredRequest, ChannelRequest
from app.modules.roxywi.class_models import ServerRequest, GroupQuery, CredRequest, ChannelRequest, ListRequest
from app.middleware import get_user_params
@ -36,7 +36,7 @@ class SupportClass:
@staticmethod
@get_user_params()
def return_group_id(body: Union[ServerRequest, CredRequest, GroupQuery, ChannelRequest]):
def return_group_id(body: Union[ServerRequest, CredRequest, GroupQuery, ChannelRequest, ListRequest]):
if body.group_id:
if g.user_params['role'] == 1:
return body.group_id

View File

@ -86,26 +86,19 @@ def save_bwlist(list_name: str, list_con: str, color: str, group: str, server_ip
lib_path = get_config.get_config_var('main', 'lib_path')
list_path = f"{lib_path}/lists/{group}/{color}/{list_name}"
path = sql.get_setting('haproxy_dir') + "/" + color
servers = []
servers = [server_ip]
output = ''
try:
with open(list_path, "w") as file:
file.write(list_con)
except IOError as e:
return f'error: Cannot save {color} list. {e}'
raise Exception(f'Cannot save {color} list: {e}')
if server_ip != 'all':
servers.append(server_ip)
masters = server_sql.is_master(server_ip)
for master in masters:
if master[0] is not None:
servers.append(master[0])
else:
server = roxywi_common.get_dick_permit()
for s in server:
servers.append(s[2])
masters = server_sql.is_master(server_ip)
for master in masters:
if master[0] is not None:
servers.append(master[0])
for serv in servers:
server_mod.ssh_command(serv, f"sudo mkdir {path}")
@ -113,6 +106,7 @@ def save_bwlist(list_name: str, list_con: str, color: str, group: str, server_ip
try:
config_mod.upload(serv, f'{path}/{list_name}', list_path)
except Exception as e:
roxywi_common.logging(serv, f'error: Upload fail: to {serv}: {e}', roxywi=1, login=1)
output += f'error: Upload fail: to {serv}: {e} , '
output += f'success: Edited {color} list was uploaded to {serv} , '

View File

@ -517,3 +517,12 @@ class NettoolsRequest(BaseModel):
record_type: Optional[Literal['a', 'aaaa', 'caa', 'cname', 'mx', 'ns', 'ptr', 'sao', 'src', 'txt']] = None
ip: Optional[IPvAnyAddress] = None
netmask: Optional[int] = None
class ListRequest(BaseModel):
server_ip: Optional[Union[IPvAnyAddress, DomainName]] = None
color: Literal['white', 'black']
name: EscapedString
action: Optional[Literal['reload', 'restart', 'save']] = None
content: Optional[str] = ''
group_id: Optional[int] = None

View File

@ -47,7 +47,7 @@ def ping_from_server(server_from: str, server_to: str, action: str) -> Response:
return Response(stream_with_context(paint_output(ssh_generator.generate(action_for_sending))), mimetype='text/html')
def telnet_from_server(server_from: str, server_to: str, port_to: str) -> str:
def telnet_from_server(server_from: str, server_to: str, port_to: int) -> str:
count_string = 0
stderr = ''
output1 = ''

View File

@ -127,7 +127,7 @@ def generate_haproxy_inv(json_data: ServiceInstall, installed_service: str) -> o
stats_password = sql.get_setting('haproxy_stats_password')
haproxy_dir = sql.get_setting('haproxy_dir')
container_name = sql.get_setting('haproxy_container_name')
haproxy_ver = '2.9.6-1'
haproxy_ver = '3.1.0-1'
is_docker = json_data['services']['haproxy']['docker']
for v in json_data['servers']:

View File

@ -18,6 +18,7 @@ import app.modules.roxywi.common as roxywi_common
import app.modules.roxy_wi_tools as roxy_wi_tools
from app.views.service.haproxy_section_views import (GlobalSectionView, DefaultsSectionView, ListenSectionView,
UserListSectionView, PeersSectionView)
from app.views.service.haproxy_lists_views import HaproxyListView
from app.modules.roxywi.class_models import DomainName
get_config = roxy_wi_tools.GetConfigVar()
@ -38,6 +39,8 @@ register_api_id_ip(PeersSectionView, 'haproxy_peers_post_a', '/section/peers', m
register_api_id_ip(PeersSectionView, 'haproxy_peers_a', '/section/peers/<section_name>', methods=['GET', 'PUT', 'DELETE'])
register_api_id_ip(GlobalSectionView, 'haproxy_global_a', '/section/global', methods=['GET', 'PUT'])
register_api_id_ip(DefaultsSectionView, 'haproxy_defaults_a', '/section/defaults', methods=['GET', 'PUT'])
bp.add_url_rule('/<any(haproxy):service>/list/<list_name>/<color>', view_func=HaproxyListView.as_view('list_get'), methods=['GET'])
bp.add_url_rule('/<any(haproxy):service>/list', view_func=HaproxyListView.as_view('list_post'), methods=['POST', 'DELETE'])
@bp.before_request
@ -101,47 +104,6 @@ def get_section_html():
return render_template('ajax/config_show_add_sections.html', lang=lang)
@bp.post('/haproxy/bwlist/create')
@get_user_params()
def create_bwlist():
server_ip = common.is_ip_or_dns(request.form.get('serv'))
color = common.checkAjaxInput(request.form.get('color'))
group = g.user_params['group_id']
list_name = common.checkAjaxInput(request.form.get('bwlists_create'))
return add_mod.create_bwlist(server_ip, list_name, color, group)
@bp.post('/haproxy/bwlist/save')
@get_user_params()
def save_bwlist():
server_ip = common.is_ip_or_dns(request.form.get('serv'))
color = common.checkAjaxInput(request.form.get('color'))
group = g.user_params['group_id']
bwlists_save = common.checkAjaxInput(request.form.get('bwlists_save'))
list_con = request.form.get('bwlists_content')
action = common.checkAjaxInput(request.form.get('bwlists_restart'))
return add_mod.save_bwlist(bwlists_save, list_con, color, group, server_ip, action)
@bp.route('/haproxy/bwlist/delete/<server_ip>/<color>/<name>/<int:group>')
@validate()
def delete_bwlist(server_ip: Union[IPvAnyAddress, DomainName], color, name, group):
color = common.checkAjaxInput(color)
list_name = common.checkAjaxInput(name)
return add_mod.delete_bwlist(list_name, color, group, str(server_ip))
@bp.route('/haproxy/bwlist/<bwlists>/<color>/<int:group>')
def get_bwlist(bwlists, color, group):
color = common.checkAjaxInput(color)
bwlists = common.checkAjaxInput(bwlists)
return add_mod.get_bwlist(color, group, bwlists)
@bp.route('/haproxy/bwlists/<color>/<int:group>')
def get_bwlists(color, group):
color = common.checkAjaxInput(color)

View File

@ -199,7 +199,7 @@ def nettools_check(check, body: NettoolsRequest):
@jwt_required()
@get_user_params()
@validate()
def service_history(service: str, server_ip: Union[IPvAnyAddress, DomainName]):
def service_history(service: str, server_ip: Union[IPvAnyAddress, DomainName, int]):
history = ''
server_ip = str(server_ip)
@ -217,7 +217,7 @@ def service_history(service: str, server_ip: Union[IPvAnyAddress, DomainName]):
server_id = server_sql.get_server_by_ip(server_ip).server_id
history = history_sql.select_action_history_by_server_id(server_id)
elif service == 'user':
history = history_sql.select_action_history_by_user_id(server_ip)
history = history_sql.select_action_history_by_user_id(int(server_ip))
else:
abort(404, 'History not found')

85
app/static/css/dark.css Normal file
View File

@ -0,0 +1,85 @@
body, .container, .footer {
background-color: #171717 !important;
color: #F0F0F0 !important;
}
h2 {
color: #F0F0F0;
}
h3 {
background: #b2b2b2;
color: #F0F0F0;
}
.menu a, .v_menu a, .top-menu {
background-color: #1b1b1b !important;
color: #f0f0f0 !important;
}
.menu a:hover {
background: #1f1f1f !important;
color: #f0f0f0 !important;
border-left: none;
}
.overview-wi .overviewHead, .overviewHead {
background-color: #686868 !important;
}
.odd {
background-color: #171717 !important;
}
.even {
background-color: #1f1f1f !important;
}
.menu a {
background-color: #1b1b1b !important;
color: #f0f0f0 !important;
}
.addName {
background-color: #171717;
}
.addButton:hover {
background-color: #171717 !important;
}
.alert-info {
color: #f0f0f0 !important;
}
.form-login {
background-color: #1b1b1b !important;
}
#enter {
color: #F0F0F0;
background-color: #171717 !important;
}
.ui-widget-header {
background-color: #797878 !important;
}
.ui-tabs .ui-tabs-panel {
background-color: #171717 !important;
}
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button {
color: #F0F0EF;
}
.ui-dialog .ui-dialog-title {
color: #f0f0f0;
}
.ui-dialog .ui-dialog-content, .ui-widget-content {
background-color: #1f1f1f !important;
}
.ui-widget-header {
border: 1px solid #1f1f1f;
}
.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited, a.ui-button, a:link.ui-button, a:visited.ui-button, .ui-button {
color: #f0f0f0;
}
.ui-state-active {
background-color: #797878 !important;
border: none;
}
.ui-widget-content {
color: #f0f0f0 !important;
}
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default, .ui-button, html .ui-button.ui-state-disabled:hover, html .ui-button.ui-state-disabled:active {
border: 1px solid #797878 !important;
background: #1f1f1f !important;
color: #f0f0f0 !important;
}
.ui-menu, .ui-menu-item {
background: #797878 !important;
}

View File

@ -571,7 +571,7 @@ ul{
border-bottom: 1px solid var(--color-secondary);
}
.form-login {
background-color: var(--color-gray) !important;
background-color: var(--color-gray);
padding: 10px 10px 10px 30px;
width: 220px;
background-size: 0 !important;
@ -1379,3 +1379,12 @@ label {
background-color: #f5f5f5;
border: 1px solid #ccc;
}
#enter {
width: 220px;
padding: 10px;
background-color: var(--background);
font-weight: bold;
color: var(--color-wanring);
border: 1px solid var(--color-wanring);
border-radius: var(--border-radius);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -839,7 +839,7 @@ function resetProxySettings() {
$('.advance-show').fadeIn();
$('.advance').fadeOut();
$('[id^=https-hide]').hide();
$('[name=mode').val('http');
$('[name=mode]').val('http');
$('select').selectmenu('refresh');
$("#path-cert-listen" ).attr('required',false);
$("#path-cert-frontend" ).attr('required',false);
@ -1124,26 +1124,23 @@ function change_select_waf(id) {
});
}
function createList(color) {
if(color == 'white') {
let list = $('#new_blacklist_name').val()
if (color === 'white') {
list = $('#new_whitelist_name').val()
} else {
list = $('#new_blacklist_name').val()
}
list = escapeHtml(list);
$.ajax( {
url: "/add/haproxy/bwlist/create",
data: {
bwlists_create: list,
color: color
},
let jsonData = {
'name': escapeHtml(list),
'color': color
}
$.ajax({
url: "/add/haproxy/list",
data: JSON.stringify(jsonData),
type: "POST",
success: function( data ) {
if (data.indexOf('error:') != '-1' || data.indexOf('Failed') != '-1' || data.indexOf('Errno') != '-1') {
toastr.error(data);
} else if (data.indexOf('Info') != '-1' ){
toastr.clear();
toastr.info(data);
} else if (data.indexOf('success') != '-1' ) {
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.status === 'failed') {
toastr.error(data.error);
} else {
toastr.clear();
toastr.success('List has been created');
setTimeout(function () {
@ -1151,16 +1148,17 @@ function createList(color) {
}, 2500);
}
}
} );
});
}
function editList(list, color) {
$.ajax( {
url: "/add/haproxy/bwlist/" + list + "/" + color + "/" + $('#group_id').val(),
url: "/add/haproxy/list/" + list + "/" + color,
contentType: "application/json; charset=utf-8",
success: function( data ) {
if (data.indexOf('error:') != '-1') {
toastr.error(data);
if (data.status === 'failed') {
toastr.error(data.error);
} else {
$('#edit_lists').text(data);
$('#edit_lists').text(data.data.replaceAll('\n', '\r\n'));
$( "#dialog-confirm-cert-edit" ).dialog({
resizable: false,
height: "auto",
@ -1205,25 +1203,31 @@ function editList(list, color) {
function saveList(action, list, color) {
let serv = $("#serv-" + color + "-list option:selected").val();
if (!checkIsServerFiled($("#serv-" + color + "-list"))) return false;
let jsonData = {
name: list,
server_ip: serv,
content: $('#edit_lists').val(),
color: color,
action: action
}
$.ajax({
url: "/add/haproxy/bwlist/save",
data: {
bwlists_save: list,
serv: serv,
bwlists_content: $('#edit_lists').val(),
color: color,
bwlists_restart: action
},
url: "/add/haproxy/list",
data: JSON.stringify(jsonData),
type: "POST",
contentType: "application/json; charset=utf-8",
success: function (data) {
data = data.split(" , ");
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') {
toastr.success(data[i]);
if (data.status === 'failed') {
toastr.error(data.error)
} else {
data = data.data.split(" , ");
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') {
toastr.success(data[i]);
}
}
}
}
@ -1234,24 +1238,39 @@ function saveList(action, list, color) {
function deleteList(list, color) {
let serv = $( "#serv-"+color+"-list option:selected" ).val();
if(!checkIsServerFiled($("#serv-"+color+"-list"))) return false;
let jsonData = {
'name': list,
'color': color,
'server_ip': serv
}
$.ajax({
url: "/add/haproxy/bwlist/delete/" + serv + "/" + color + "/" + list + "/" + $('#group_id').val(),
success: function (data) {
if (data.indexOf('error:') != '-1' || data.indexOf('Failed') != '-1' || data.indexOf('Errno') != '-1') {
toastr.error(data);
} else if (data.indexOf('Info') != '-1' ){
toastr.clear();
toastr.info(data);
} else if (data.indexOf('success') != '-1' ) {
url: "/add/haproxy/list",
type: "DELETE",
data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8",
statusCode: {
204: function (xhr) {
toastr.clear();
toastr.success('List has been deleted');
setTimeout(function () {location.reload();}, 2500);
},
404: function (xhr) {
toastr.clear();
toastr.success('List has been deleted');
setTimeout(function () {location.reload();}, 2500);
}
},
success: function (data) {
if (data) {
if (data.status === 'failed') {
toastr.error(data);
}
}
}
});
}
function createMap() {
map_name = $('#new_map_name').val()
let map_name = $('#new_map_name').val()
map_name = escapeHtml(map_name);
$.ajax( {
url: "/add/map",

View File

@ -298,11 +298,7 @@ function change_pos(pos, id) {
}
function showBytes(serv) {
$.ajax( {
url: "/service/haproxy/bytes",
data: {
showBytes: serv
},
type: "POST",
url: "/service/haproxy/bytes/" + serv,
beforeSend: function() {
$("#show_bin_bout").html('<img class="loading_small_bin_bout" src="/static/images/loading.gif" />');
$("#sessions").html('<img class="loading_small_bin_bout" src="/static/images/loading.gif" />');
@ -320,11 +316,7 @@ function showBytes(serv) {
}
function showNginxConnections(serv) {
$.ajax( {
url: "/service/nginx/connections",
data: {
nginxConnections: serv
},
type: "POST",
url: "/service/nginx/connections/" + serv,
beforeSend: function() {
$("#sessions").html('<img class="loading_small_bin_bout" src="/static/images/loading.gif" />');
},
@ -341,11 +333,7 @@ function showNginxConnections(serv) {
}
function showApachekBytes(serv) {
$.ajax( {
url: "/service/apache/bytes",
data: {
apachekBytes: serv
},
type: "POST",
url: "/service/apache/bytes/" + serv,
beforeSend: function() {
$("#sessions").html('<img class="loading_small_bin_bout" src="/static/images/loading.gif" />');
},
@ -362,11 +350,7 @@ function showApachekBytes(serv) {
}
function keepalivedBecameMaster(serv) {
$.ajax( {
url: "/service/keepalived/become-master",
data: {
keepalivedBecameMaster: serv
},
type: "POST",
url: "/service/keepalived/become-master/" + serv,
beforeSend: function() {
$("#bin_bout").html('<img class="loading_small_bin_bout" src="/static/images/loading.gif" />');
},

View File

@ -29,6 +29,11 @@ function escapeHtml(unsafe) {
var wait_mess_word = translate_div.attr('data-wait_mess');
var wait_mess = '<div class="alert alert-warning">'+wait_mess_word+'</div>'
function show_current_page(id) {
let theme = localStorage.getItem('theme');
let correct_color = 'var(--color-gray-dark-alpha)';
if (theme === 'dark') {
correct_color = '#181818';
}
id.parent().css('display', 'contents');
id.parent().css('font-size', '13px');
id.parent().css('top', '0');
@ -36,7 +41,7 @@ function show_current_page(id) {
id.parent().children().css('margin-left', '-20px');
id.parent().find('a').css('padding-left', '20px');
id.find('a').css('border-left', '4px solid var(--color-wanring) !important');
id.find('a').css('background-color', 'var(--color-gray-dark-alpha) !important');
id.find('a').css('background-color', correct_color +' !important');
}
$( function() {
$('.menu li ul li').each(function () {
@ -244,9 +249,9 @@ function clearAllAjaxFields() {
function showMap() {
clearAllAjaxFields();
$('#ajax-config_file_name').empty();
$.ajax( {
$.ajax({
url: "/config/map/haproxy/" + $("#serv").val() + '/show',
success: function( data ) {
success: function (data) {
if (data.indexOf('error:') != '-1') {
toastr.error(data);
} else {
@ -255,17 +260,17 @@ function showMap() {
window.history.pushState("Show Map", "Show Map", '/config/map/' + $("#service").val() + '/' + $("#serv").val());
}
}
} );
});
}
function showCompare() {
$.ajax( {
$.ajax({
url: "/config/compare/" + $("#service").val() + "/" + $("#serv").val() + "/show",
data: {
left: $('#left').val(),
right: $("#right").val(),
},
type: "POST",
success: function( data ) {
success: function (data) {
if (data.indexOf('error:') != '-1') {
toastr.error(data);
} else {
@ -273,15 +278,15 @@ function showCompare() {
$("#ajax").html(data);
}
}
} );
});
}
function showCompareConfigs() {
clearAllAjaxFields();
$('#ajax-config_file_name').empty();
$.ajax( {
$.ajax({
url: "/config/compare/" + $("#service").val() + "/" + $("#serv").val() + "/files",
type: "GET",
success: function( data ) {
success: function (data) {
if (data.indexOf('error:') != '-1') {
toastr.error(data);
} else {
@ -292,7 +297,7 @@ function showCompareConfigs() {
window.history.pushState("Show compare config", "Show compare config", '/config/compare/' + $("#service").val() + '/' + $("#serv").val());
}
}
} );
});
}
function showConfig() {
let service = $('#service').val();
@ -315,12 +320,12 @@ function showConfig() {
"service": service,
"config_file_name": config_file_name
}
$.ajax( {
$.ajax({
url: "/config/" + service + "/show",
data: JSON.stringify(json_data),
type: "POST",
contentType: "application/json; charset=utf-8",
success: function( data ) {
success: function (data) {
if (data.status === 'failed') {
toastr.error(data);
} else {
@ -330,7 +335,7 @@ function showConfig() {
window.history.pushState("Show config", "Show config", "/config/" + service + "/" + $("#serv").val() + "/show/" + config_file_name);
}
}
} );
});
}
function showConfigFiles(not_redirect=false) {
var service = $('#service').val();
@ -477,6 +482,7 @@ function viewLogs() {
}
}
$( function() {
checkTheme();
$('a').click(function(e) {
try {
var cur_path = window.location.pathname;
@ -788,11 +794,29 @@ function saveUserSettings(user_id){
localStorage.setItem('disabled_alert', '1');
}
changeCurrentGroupF(user_id);
changeTheme($('#theme_select').val());
Cookies.set('lang', $('#lang_select').val(), { expires: 365, path: '/', secure: 'true' });
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function changeTheme(theme) {
localStorage.setItem('theme', theme);
if (theme === 'dark') {
$('#menu-overview').children().attr('src', '/static/images/roxy_icon_white.png');
$('#menu-haproxy').children().attr('src', '/static/images/HAProxy_icon_white.png');
$('#menu-nginx').children().attr('src', '/static/images/NGINX_icon_white.png');
$('#menu-keepalived').children().attr('src', '/static/images/keepalived_icon_white.png');
$('.menu-logo').attr('src', '/static/images/logo_menu_white.png');
$('head').append('<link rel="stylesheet" href="/static/css/dark.css" type="text/css" />');
} else {
$('link[rel=stylesheet][href~="/static/css/dark.css"]').remove();
}
}
function checkTheme() {
let theme = localStorage.getItem('theme');
changeTheme(theme);
}
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
@ -1296,6 +1320,12 @@ function openUserSettings(user_id) {
} else {
$('#disable_alerting').prop('checked', true).checkboxradio('refresh');
}
let theme = 'light';
if (localStorage.getItem('theme') != null) {
theme = localStorage.getItem('theme');
}
$('#theme_select').val(theme).change();
$('#theme_select').selectmenu('refresh');
$.ajax({
url: "/user/group",
success: function (data) {

View File

@ -66,11 +66,6 @@
</a>
</div>
<div id="version"></div>
{# <div id="logo_footer">#}
{# <a href="https://roxy-wi.org" title="Roxy-WI official site" target="_blank">#}
{# <img src="{{ url_for('static', filename='images/logo_footer.png')}}" alt="logo" id="logo_footer_img" />#}
{# </a>#}
{# </div>#}
<div class="footer-div">
<div id="useful-links">
<a href="https://roxy-wi.org" class="footer-link" target="_blank" title="{{lang.words.about|title()}} Roxy-WI">{{lang.words.about|title()}}</a>
@ -99,6 +94,13 @@
</td>
</tr>
<tr id="show-user-settings-group"></tr>
<tr id="show-user-settings-them">
<td class="padding20">{{lang.words.theme|title()}}</td>
<td>
{% set themes = {'dark': lang.words.dark|title(), 'light': lang.words.light|title()} %}
{{ select('theme_select', values=themes) }}
</td>
</tr>
</table>
</div>
<div id="statistic" style="display: none;">

View File

@ -10,7 +10,10 @@
<nav id="menu">
<ul class="menu">
<li>
<a href="{{ url_for('overview.index') }}" title="{{lang.menu_links.overview.title}}" class="{% if request.url_rule.endpoint == 'overview.index' %} menu-active{% endif %}">
<a href="{{ url_for('overview.index') }}" title="{{lang.menu_links.overview.title}}"
class="{% if request.url_rule.endpoint == 'overview.index' %} menu-active{% endif %}"
id="menu-overview"
>
<img src="{{ url_for('static', filename='images/roxy_icon.png') }}" class="menu-vendor-logo" style="width: 19px;">
{{lang.menu_links.overview.link}}
</a>
@ -22,7 +25,7 @@
{% endif %}
{% if '1' in g.user_params['user_services'] %}
<li class="p_menu">
<a href="{{ url_for('service.services', service='haproxy') }}" title="{{lang.menu_links.hapservers.haproxy.title}}">
<a href="{{ url_for('service.services', service='haproxy') }}" title="{{lang.menu_links.hapservers.haproxy.title}}" id="menu-haproxy">
<img src="{{ url_for('static', filename='images/HAProxy_icon.png') }}" class="menu-vendor-logo">
HAProxy
</a>
@ -45,7 +48,7 @@
{% endif %}
{% if '2' in g.user_params['user_services'] %}
<li class="p_menu">
<a href="{{ url_for('service.services', service='nginx') }}" title="{{lang.menu_links.hapservers.nginx.title}}">
<a href="{{ url_for('service.services', service='nginx') }}" title="{{lang.menu_links.hapservers.nginx.title}}" id="menu-nginx">
<img src="{{ url_for('static', filename='images/NGINX_icon.png') }}" class="menu-vendor-logo">
NGINX
</a>
@ -88,7 +91,7 @@
{% if '3' in g.user_params['user_services'] %}
{% if g.user_params['role'] <= 2 %}
<li class="p_menu">
<a href="{{ url_for('service.services', service='keepalived') }}" title="{{lang.menu_links.hapservers.keepalived.title}}">
<a href="{{ url_for('service.services', service='keepalived') }}" title="{{lang.menu_links.hapservers.keepalived.title}}" id="menu-keepalived">
<img src="{{ url_for('static', filename='images/keepalived_icon.png') }}" class="menu-vendor-logo" style="margin: 0 5px 0 0;">
Keepalived
</a>

View File

@ -39,8 +39,8 @@
</td>
<td class="padding10 first-collumn" style="width: 20%;">
{% set values = dict() %}
{% set values = {'2.4.23-1':'2.4.23-1','2.5.14-1':'2.5.14-1', '2.6.14-1':'2.6.14-1','2.7.9-1':'2.7.9-1','2.8.1-1':'2.8.1-1','2.9.6-1':'2.9.6-1'} %}
{{ select('hapver', values=values, selected='2.9.6-1', required='required') }}
{% set values = {'2.6.14-1':'2.6.14-1','2.7.9-1':'2.7.9-1','2.8.1-1':'2.8.1-1','2.9.6-1':'2.9.6-1','3.0.3-1':'3.0.3-1','3.1.0-1':'3.1.0-1'} %}
{{ select('hapver', values=values, selected='3.1.0-1', required='required') }}
</td>
<td class="padding10 first-collumn">
<select autofocus required name="haproxyaddserv" id="haproxyaddserv">

View File

@ -959,5 +959,8 @@
"calculate": "calculate",
"calculator": "calculator",
"netmask": "Netmask",
"theme": "theme",
"dark": "dark",
"light": "light",
}
%}

View File

@ -959,5 +959,8 @@
"calculate": "calculer",
"calculator": "calculatrice",
"netmask": "Masque de réseau",
"theme": "thème",
"dark": "sombre",
"light": "léger",
}
%}

View File

@ -959,5 +959,8 @@
"calculate": "calcular",
"calculator": "calculadora",
"netmask": "Máscara de rede",
"theme": "thema",
"dark": "escuro",
"light": "claro",
}
%}

View File

@ -959,5 +959,8 @@
"calculate": "рассчитать",
"calculator": "калькулятор",
"netmask": "Сетевая маска",
"theme": "тема",
"dark": "темная",
"light": "светлая",
}
%}

View File

@ -6,15 +6,6 @@
</head>
<body>
<style>
#enter {
width: 220px !important;
padding: 10px !important;
background-color: var(--background) !important;
font-weight: bold !important;
color: var(--color-wanring);
border: 1px solid var(--color-wanring) !important;
border-radius: var(--border-radius) !important;
}
.fontuser {
position: relative;
}
@ -42,7 +33,14 @@
cursor: pointer;
}
</style>
<script>
$( function() {
let theme = localStorage.getItem('theme');
if (theme === 'dark') {
$('#logo_span').children().attr('src', '/static/images/logo_white.png');
}
});
</script>
<div id="main_div">
<div id="login-form">
<span id="logo_span">

View File

@ -58,7 +58,7 @@
</td>
{% endif %}
<td>
<button type="submit" name="Enter" value="Enter" id="enter">{{lang.words.enter|title()}}</button>
<button type="submit" name="Enter" value="Enter">{{lang.words.enter|title()}}</button>
</td>
</form>
</tr>
@ -129,7 +129,7 @@
{{ input('maxconnint', title=lang.words.enter|title() + ' maxconn', type="number", required='required') }}
</td>
<td>
<button type="submit" name="Enter" value="Enter" id="enter">{{lang.words.enter|title()}}</button>
<button type="submit" name="Enter" value="Enter">{{lang.words.enter|title()}}</button>
</td>
</form>
</tr>
@ -288,7 +288,7 @@
</select>
</td>
<td>
<button type="submit" name="Enter" value="Enter" id="enter">{{lang.words.w_get|title()}} {{lang.words.table}}</button>
<button type="submit" name="Enter" value="Enter">{{lang.words.w_get|title()}} {{lang.words.table}}</button>
</td>
</form>
</tr>
@ -315,7 +315,7 @@
</select>
</td>
<td>
<button type="submit" name="Enter" value="Enter" id="enter">{{lang.words.w_get|title()}} {{lang.words.list}}</button>
<button type="submit" name="Enter" value="Enter">{{lang.words.w_get|title()}} {{lang.words.list}}</button>
</td>
</form>
</tr>
@ -337,7 +337,7 @@
{{ select('sessions_serv_select', values=g.user_params['servers'], is_servers='true') }}
</td>
<td>
<button type="submit" name="Enter" value="Enter" id="enter">{{lang.words.w_get|title()}} {{lang.words.sessions}}</button>
<button type="submit" name="Enter" value="Enter">{{lang.words.w_get|title()}} {{lang.words.sessions}}</button>
</td>
</form>
</tr>

View File

@ -82,6 +82,7 @@
showOverviewServer('{{s.1}}', server_ip, '{{s.0}}', '{{service}}');
{%- if service == 'nginx' %}
showNginxConnections(server_ip)
setInterval(showNginxConnections, 60000, server_ip)
{% if s.4.0.21 %}
getNginxChartData(server_ip)
{% endif %}

View File

@ -0,0 +1,302 @@
from typing import Literal
from flask import jsonify
from flask.views import MethodView
from flask_pydantic import validate
from flask_jwt_extended import jwt_required
import app.modules.config.add as add_mod
import app.modules.roxywi.common as roxywi_common
from app.middleware import get_user_params, page_for_admin, check_group, check_services
from app.modules.roxywi.class_models import IdDataStrResponse, GroupQuery, ListRequest, IdStrResponse, BaseResponse
from app.modules.common.common_classes import SupportClass
class HaproxyListView(MethodView):
methods = ['GET', 'POST', 'PUT', 'DELETE']
decorators = [jwt_required(), get_user_params(), check_services, page_for_admin(level=3), check_group()]
@validate(query=GroupQuery)
def get(self, service: str, list_name: str, color: Literal['white', 'black'], query: GroupQuery):
"""
Get the IP address list for HAProxy.
---
tags:
- HAProxy white and black lists
parameters:
- in: path
name: service
type: 'string'
required: true
description: 'The service for which the list is required. Can be only `haproxy`'
- in: path
name: name
type: 'string'
required: true
description: 'The name of the list.'
- in: path
name: color
type: 'string'
required: true
description: 'The color of the list, can be `white` or `black`.'
- in: query
name: group_id
type: 'integer'
required: false
description: 'The group ID, available only for the role superAdmin.'
responses:
200:
description: 'Successfully retrieved the list of IP addresses.'
schema:
type: 'object'
properties:
data:
type: 'string'
description: 'The list of IP addresses.'
example: "192.168.1.31\\n192.168.4.1/8"
id:
type: 'string'
description: 'The identifier of the list.'
example: '1-blackblacklist1.lst'
status:
type: 'string'
description: 'The status of the request.'
example: "Ok"
403:
description: Access forbidden, superAdmin role required.
404:
description: List not found.
"""
group_id = SupportClass.return_group_id(query)
try:
list_data = add_mod.get_bwlist(color, group_id, list_name)
json_data = {
'id': f'{group_id}-{color}-{list_name}.lst',
'data': list_data,
'name': f'{list_name}.lst',
'color': color,
'group_id': group_id,
}
return jsonify(json_data)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get list')
@validate(body=ListRequest)
def post(self, service: str, body: ListRequest):
"""
Create and add content to lists
---
tags:
- HAProxy white and black lists
parameters:
- name: service
in: path
type: string
required: true
enum: [haproxy]
description: The service for which the list is being submitted.
- name: name
in: path
type: string
required: true
description: The name of the list.
- in: body
name: body
description: JSON object containing details of the IP list.
required: true
schema:
type: object
required:
- name
- server_ip
- content
- color
- action
properties:
name:
type: string
description: The name of the list
example: "whitelist1.lst"
server_ip:
type: string
description: The IP address of the server
example: "127.0.0.1"
content:
type: string
description: The content of the IP list
example: "92.168.1.10\\n10.0.0.1"
color:
type: string
description: The color of the list
enum: [white, black]
example: "white"
action:
type: string
description: The action to perform
example: "save"
group_id:
type: integer
description: The group where list must be created. Only for `superAdmin` role.
responses:
201:
description: Successfully created the list.
schema:
type: object
properties:
message:
type: string
example: "List successfully created."
id:
type: string
example: "1-whitelist1.lst"
400:
description: Invalid input data.
403:
description: Access forbidden, superAdmin role required.
"""
group_id = SupportClass.return_group_id(body)
try:
add_mod.create_bwlist(body.server_ip, body.name, body.color, group_id)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create list')
if body.content == '':
return IdStrResponse(id=f'{group_id}-{body.color}-{body.name}.lst').model_dump(mode='json')
try:
data = add_mod.save_bwlist(body.name, body.content, body.color, group_id, str(body.server_ip), str(body.action))
return IdDataStrResponse(id=f'{group_id}-{body.color}-{body.name}.lst', data=data).model_dump(mode='json')
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot save list')
@validate(body=ListRequest)
def put(self, service: str, body: ListRequest):
"""
Update content to lists
---
tags:
- HAProxy white and black lists
parameters:
- name: service
in: path
type: string
required: true
enum: [haproxy]
description: The service for which the list is being submitted.
- name: name
in: path
type: string
required: true
description: The name of the list.
- in: body
name: body
description: JSON object containing details of the IP list.
required: true
schema:
type: object
required:
- name
- server_ip
- content
- color
- action
properties:
name:
type: string
description: The name of the list
example: "whitelist1.lst"
server_ip:
type: string
description: The IP address of the server
example: "127.0.0.1"
content:
type: string
description: The content of the IP list
example: "92.168.1.10\\n10.0.0.1"
color:
type: string
description: The color of the list
enum: [white, black]
example: "white"
action:
type: string
description: The action to perform
example: "save"
group_id:
type: integer
description: The group where list must be created. Only for `superAdmin` role.
responses:
201:
description: Successfully created the list.
schema:
type: object
properties:
message:
type: string
example: "List successfully created."
id:
type: string
example: "1-whitelist1.lst"
400:
description: Invalid input data.
403:
description: Access forbidden, superAdmin role required.
"""
group_id = SupportClass.return_group_id(body)
try:
add_mod.save_bwlist(body.name, body.content, body.color, group_id, body.server_ip, str(body.action))
return BaseResponse().model_dump(mode='json')
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot save list')
@validate(body=ListRequest)
def delete(self, service: str, body: ListRequest):
"""
Delete HAProxy white and black list.
---
tags:
- HAProxy white and black lists
parameters:
- in: path
name: service
type: 'string'
required: true
description: 'The service for which the list is required. Can be only `haproxy`'
- in: body
name: body
description: JSON object containing details of the IP list.
required: true
schema:
type: object
required:
- name
- color
properties:
name:
type: string
description: The name of the list
example: "whitelist1.lst"
color:
type: string
description: The color of the list
enum: [white, black]
example: "white"
group_id:
type: integer
description: The group where list must be created. Only for `superAdmin` role.
responses:
204:
description: 'Successfully delete list.'
403:
description: Access forbidden, superAdmin role required.
404:
description: List not found.
"""
group_id = SupportClass.return_group_id(body)
try:
add_mod.delete_bwlist(body.name, body.color, group_id, str(body.server_ip))
return BaseResponse().model_dump(mode='json'), 204
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete list')