Added blacklist ip table, uptime monitor interface

This commit is contained in:
Toby Chui 2023-04-15 23:30:34 +08:00
parent 44c1e60fb8
commit 2c586aee32
9 changed files with 473 additions and 28 deletions

View File

@ -315,6 +315,24 @@
</tbody>
</table>
<div class="ui divider"></div>
<h4>Visitor IP list</h4>
<button style="margin-top: -1em;" onclick="initBlacklistQuickBanTable();" class="ui green small right floated circular basic icon button"><i class="ui refresh icon"></i></button>
<p>Observe strange traffic on your sites? Ban them in the list below.</p>
<table class="ui celled unstackable table" id="ipTable">
<thead>
<tr>
<th>IP</th>
<th>Access Count</th>
<th>Blacklist</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="pagination"></div>
<script>
$(".dropdown").dropdown();
@ -427,13 +445,15 @@
icon = "home icon";
}
$('#blacklistIpTable').append(`
<tr>
<td><i class="${icon}"></i> ${ip}</td>
<td><button class="ui icon basic mini red button" onclick="removeIpBlacklist('${ip}');"><i class="trash alternate icon"></i></button></td>
<tr class="blacklistItem" ip="${encodeURIComponent(ip)}">
<td><i class="${icon}"></i> ${ip}</td>
<td><button class="ui icon basic mini red button" onclick="removeIpBlacklist('${ip}');"><i class="trash alternate icon"></i></button></td>
</tr>
`);
});
}
initBlacklistQuickBanTable();
});
}
initIpBanTable();
@ -563,6 +583,7 @@
error: function() {
alert("Failed to remove IP address from blacklist.");
}
});
}
}
@ -594,6 +615,99 @@
}
initBlacklistEnableState();
//Load the summary to ip access table
function initBlacklistQuickBanTable(){
$.get("/api/stats/summary", function(data){
initIpAccessTable(data.RequestClientIp);
})
}
initBlacklistQuickBanTable();
var blacklist_entriesPerPage = 30;
var blacklist_currentPage = 1;
var blacklist_totalPages = 0;
function initIpAccessTable(ipAccessCounts){
blacklist_totalPages = Math.ceil(Object.keys(ipAccessCounts).length / blacklist_entriesPerPage);
function sortkv(obj) {
var sortable = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
sortable.push([key, obj[key]]);
}
}
sortable.sort(function(a, b) {
return b[1] - a[1];
});
var sortedObj = {};
sortable.forEach(function(item) {
sortedObj[item[0]] = item[1];
});
return sortedObj;
}
ipAccessCounts = sortkv(ipAccessCounts);
function renderTable() {
var tableBody = $("#ipTable tbody");
tableBody.empty();
var startIndex = (blacklist_currentPage - 1) * blacklist_entriesPerPage;
var endIndex = startIndex + blacklist_entriesPerPage;
var slicedEntries = Object.entries(ipAccessCounts).slice(startIndex, endIndex);
slicedEntries.forEach(function([ip, accessCount]) {
var row = $("<tr>").appendTo(tableBody);
$("<td>").text(ip).appendTo(row);
$("<td>").text(accessCount).appendTo(row);
if (ipInBlacklist(ip)){
$("<td>").html(`<button class="ui basic green small icon button" title"Unban IP" onclick="handleUnban('${ip}');"><i class="green check icon"></i></button>`).appendTo(row);
}else{
$("<td>").html(`<button class="ui basic red small icon button" title"Ban IP" onclick="handleBanIp('${ip}');"><i class="red ban icon"></i></button>`).appendTo(row);
}
});
}
function renderPagination() {
var paginationDiv = $(".pagination");
paginationDiv.empty();
for (var i = 1; i <= blacklist_totalPages; i++) {
var button = $("<button>").text(i).addClass("ui small basic compact button");
if (i === blacklist_currentPage) {
button.addClass("disabled");
}
button.click(function() {
blacklist_currentPage = parseInt($(this).text());
renderTable();
renderPagination();
});
button.appendTo(paginationDiv);
}
}
renderTable();
renderPagination();
}
function ipInBlacklist(targetIp){
let inBlacklist = false;
$(".blacklistItem").each(function(){
if ($(this).attr("ip") == encodeURIComponent(targetIp)){
inBlacklist = true;
}
});
return inBlacklist;
}
function handleBanIp(targetIp){
$("#ipAddressInput").val(targetIp);
addIpBlacklist();
}
function handleUnban(targetIp){
removeIpBlacklist(targetIp);
}
</script>

View File

@ -25,23 +25,27 @@
</div>
</div>
<div class="column">
<div id="connections" class="ui yellow statustab inverted segment">
<div id="connections" class="ui statustab segment" style="background-color: #f0ece1; border: 0px solid transparent;">
<h4 class="ui header">
<i class="exchange icon"></i>
<i class="arrows alternate horizontal icon"></i>
<div class="content">
<span></span>
<div class="sub header"></div>
<span id="forwardtype"></span>
<div class="sub header" id="forwardtypeList">
</div>
</div>
</h4>
</div>
</div>
<div class="column">
<div id="connections" class="ui pink statustab inverted segment">
<div id="connections" class="ui statustab inverted segment" style="background-color: #7d8e88;">
<h4 class="ui header">
<i class="exchange icon"></i>
<i class="map marker alternate icon"></i>
<div class="content">
<span></span>
<div class="sub header"></div>
<span id="country"></span>
<div class="sub header" id="countryList">
</div>
</div>
</h4>
</div>
@ -81,35 +85,39 @@
<div class="ui two column stackable grid">
<div class="column">
<p>Visitor Counts</p>
<table class="ui basic celled table">
<table class="ui unstackable celled table">
<thead>
<tr>
<th>Country ISO Code</th>
<th>Visitor Count</th>
<th>Unique Visitors</th>
</tr>
</thead>
<tbody>
<!-- insert table rows here -->
<tbody id="countryCodetable">
<tr>
<td colspan="2">No Data</td>
</tr>
</tbody>
</table>
</div>
<div class="column">
<p>Proxy Request Types</p>
<table class="ui basic celled table">
<table class="ui unstackable celled table">
<thead>
<tr>
<th>Proxy Type</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<!-- insert table rows here -->
<tbody id="forwardTypeTable">
<tr>
<td colspan="2">No Data</td>
</tr>
</tbody>
</table>
</div>
</div>
<br>
<button class="ui basic green button"><i class="refresh icon"></i> Refresh</button>
<button class="ui basic green button" onclick="getDailySummaryDetails();"><i class="refresh icon"></i> Refresh</button>
<script>
let loopbackProxiedInterface = false;
//Initial the start stop button if this is reverse proxied
@ -148,7 +156,7 @@
function abbreviateNumber(value) {
var newValue = value;
var suffixes = ["", "K", "M", "B", "T"];
var suffixes = ["", "k", "m", "b", "t"];
var suffixNum = 0;
while (newValue >= 1000 && suffixNum < suffixes.length - 1) {
newValue /= 1000;
@ -161,6 +169,90 @@
return newValue + suffixes[suffixNum];
}
function getDailySummaryDetails(){
function sortObjectByValue(obj) {
// Convert object to array of [key, value] pairs
const entries = Object.entries(obj);
// Sort array based on value of each pair
entries.sort((a, b) => {
return b[1] - a[1];
});
// Convert sorted array back to object
const sortedObj = {};
for (const [key, value] of entries) {
sortedObj[key] = value;
}
return sortedObj;
}
$.get("/api/stats/countries", function(data){
data = sortObjectByValue(data);
$("#country").html((Object.keys(data)[0])?Object.keys(data)[0]:"No Data");
$("#countryList").html(`
<div style="color: white;">
${(Object.keys(data)[1])?Object.keys(data)[1]:"No Data"}<br>
${(Object.keys(data)[2])?Object.keys(data)[2]:"No Data"}
</div>
`);
//populate the table
$("#countryCodetable").html("");
for (const [key, value] of Object.entries(data)) {
$("#countryCodetable").append(`<tr>
<td>${key}</td>
<td>${value}</td>
</tr>`);
}
if (Object.keys(data).length == 0){
$("#countryCodetable").append(`<tr>
<td colspan="2">No Data</td>
</tr>`);
}
});
//Filter forward type
function fft(ft){
if (ft.indexOf("-") >= 0){
ft = ft.replace("-", " (");
ft = ft + ")";
}
ft = ft.charAt(0).toUpperCase() + ft.slice(1);
return ft;
}
$.get("/api/stats/summary", function(data){
data = sortObjectByValue(data.ForwardTypes);
$("#forwardtype").html((Object.keys(data)[0])?fft(Object.keys(data)[0]) + ": " + abbreviateNumber(data[Object.keys(data)[0]]):"No Data");
$("#forwardtypeList").html(`
<div>
${(Object.keys(data)[1])?fft(Object.keys(data)[1]) + ": " + abbreviateNumber(data[Object.keys(data)[1]]):"No Data"}<br>
${(Object.keys(data)[2])?fft(Object.keys(data)[2]) + ": " + abbreviateNumber(data[Object.keys(data)[2]]):"No Data"}
</div>
`);
$("#forwardTypeTable").html("");
for (const [key, value] of Object.entries(data)) {
$("#forwardTypeTable").append(`<tr>
<td>${key}</td>
<td>${value}</td>
</tr>`);
}
if (Object.keys(data).length == 0){
$("#forwardTypeTable").append(`<tr>
<td colspan="2">No Data</td>
</tr>`);
}
});
}
getDailySummaryDetails();
function getDailySummary(){
$.get("/api/stats/summary?fast=true", function(data){

View File

@ -33,7 +33,7 @@
$("#subdList").append(`<tr>
<td data-label="">${subd.MatchingDomain}</td>
<td data-label="">${subd.Domain} ${tlsIcon}</td>
<td data-label=""><button class="ui circular mini red basic button" onclick='deleteEndpoint("subd","${subd.MatchingDomain}")'><i class="remove icon"></i> Remove Subdomain</button></td>
<td data-label=""><button class="ui circular mini red basic button" onclick='deleteEndpoint("subd","${subd.MatchingDomain}")'><i class="remove icon"></i> Delete</button></td>
</tr>`);
});
}

View File

@ -0,0 +1,165 @@
<h3><i class="clock green icon"></i> Uptime Monitor</h3>
<p>Check the online state of proxied targets</p>
<div class="ui toggle checkbox" id="utmEnable">
<input type="checkbox" name="utmEnable">
<label>Enable External Access</label>
</div>
<div class="ui message">
You can expose the uptime monitor interface to public by adding: <br>
<code>%uptime_monitor%</code>
<br>as a subdomain or virtual directory target URL in the "Create Proxy Rules" tab.
</div>
<div class="ui divider"></div>
<div id="utmrender" class="ui basic segment">
<div class="ui basic segment">
<h4 class="ui header">
<i class="red remove icon"></i>
<div class="content">
Uptime Monitoring service is currently unavailable
<div class="sub header">This might cause by an error in cluster communication within the host servers. Please wait for administrator to resolve the issue.</div>
</div>
</h4>
</div>
</div>
<div align="center">
<button class="ui basic circular green icon button" onclick="reloadUptimeList();"><i class="refresh icon"></i></button>
</div>
<script>
$('#utmEnable').checkbox({
onChange: function() {
var utmEnable = $('input[name="utmEnable"]').is(":checked");
$.post({
url: '/api/toggle-utm',
data: {utmEnable: utmEnable},
success: function(response) {
console.log(response);
},
error: function(error) {
console.log(error);
}
});
}
});
function initUptimeTable(){
$.get("/api/utm/list", function(data){
let records = data;
renderRecords(records);
})
}
initUptimeTable();
function reloadUptimeList(){
$("#utmrender").html(`<div class="ui segment">
<div class="ui active inverted dimmer" style="z-index: 2;">
<div class="ui text loader">Loading</div>
</div>
<br><br><br><br>
</div>`);
setTimeout(initUptimeTable, 300);
}
//For every 5 minutes
setInterval(function(){
$.get("/api/utm/list", function(data){
console.log("Status Updated");
records = data;
renderRecords(records);
});
}, (300 * 1000));
function renderRecords(records){
$("#utmrender").html("");
for (let [key, value] of Object.entries(records)) {
renderUptimeData(key, value);
}
}
function format_time(s) {
const date = new Date(s * 1e3);
return(date.toLocaleString());
}
function renderUptimeData(key, value){
if (value.length == 0){
return
}
let id = value[0].ID;
let name = value[0].Name;
let url = value[0].URL;
let protocol = value[0].Protocol;
//Generate the status dot
let statusDotList = ``;
for(var i = 0; i < (288 - value.length); i++){
//Padding
statusDotList += `<div class="padding statusDot"></div>`
}
let ontimeRate = 0;
for (var i = 0; i < value.length; i++){
//Render status to html
let thisStatus = value[i];
let dotType = "";
if (thisStatus.Online){
if (thisStatus.StatusCode < 200 || thisStatus.StatusCode >= 300){
dotType = "error";
}else{
dotType = "online";
}
ontimeRate++;
}else{
dotType = "offline";
}
let datetime = format_time(thisStatus.Timestamp);
statusDotList += `<div title="${datetime}" class="${dotType} statusDot"></div>`
}
ontimeRate = ontimeRate / value.length * 100;
let ontimeColor = "#df484a"
if (ontimeRate > 0.8){
ontimeColor = "#3bd671";
}else if(ontimeRate > 0.5) {
ontimeColor = "#f29030";
}
//Check of online status now
let currentOnlineStatus = "Unknown";
let onlineStatusCss = ``;
if (value[value.length - 1].Online){
currentOnlineStatus = `<i class="circle icon"></i> Online`;
onlineStatusCss = `color: #3bd671;`;
}else{
currentOnlineStatus = `<i class="circle icon"></i> Offline`;
onlineStatusCss = `color: #df484a;`;
}
//Generate the html
$("#utmrender").append(`<div class="ui basic segment statusbar">
<div class="domain">
<div style="position: absolute; top: 0; right: 0.4em;">
<p class="onlineStatus" style="display: inline-block; font-size: 1.3em; padding-right: 0.5em; padding-left: 0.3em; ${onlineStatusCss}">${currentOnlineStatus}</p>
</div>
<div>
<h3 class="ui header" style="margin-bottom: 0.2em;">${name}</h3>
<a href="${url}" target="_blank">${url}</a> | <span style="color: ${ontimeColor};">${(ontimeRate).toFixed(2)}%<span>
</div>
<div class="ui basic label protocol" style="position: absolute; bottom: 0; right: 0.2em; margin-bottom: -0.6em;">
proto: ${protocol}
</div>
</div>
<div class="status" style="marign-top: 1em;">
${statusDotList}
</div>
<div class="ui divider"></div>
</div>`);
}
</script>

View File

@ -38,7 +38,7 @@
$("#vdirList").append(`<tr>
<td data-label="">${vdir.Root}</td>
<td data-label="">${vdir.Domain} ${tlsIcon}</td>
<td data-label=""><button class="ui circular mini red basic button" onclick='deleteEndpoint("vdir","${vdir.Root}")'><i class="remove icon"></i> Remove Virtual Directory</button></td>
<td data-label=""><button class="ui circular mini red basic button" onclick='deleteEndpoint("vdir","${vdir.Root}")'><i class="remove icon"></i> Delete</button></td>
</tr>`);
});
}

View File

@ -145,7 +145,7 @@
</div>
<div class="ui divider"></div>
<div class="ui container" style="color: grey; font-size: 90%">
<p>Reverse Proxy by imuslab, Licensed under MIT</p>
<p>Powered by Zoraxy</p>
</div>
<br><br>

View File

@ -20,6 +20,9 @@
<img class="logo" src="img/logo.svg">
</div>
<div class="ui right floated buttons menutoggle" style="padding-top: 2px;">
<button class="ui basic icon button" onclick="$('.toolbar').fadeToggle('fast');"><i class="content icon"></i></button>
</div>
<div class="ui right floated buttons" style="padding-top: 2px;">
<button class="ui basic icon button" onclick="logout();"><i class="sign-out icon"></i></button>
</div>
@ -63,8 +66,8 @@
<i class="remove icon"></i> HTTP over Websocket
</a>
<div class="ui divider menudivider">Others</div>
<a class="item" tag="">
<i class="remove icon"></i> Uptime Monitor
<a class="item" tag="utm">
<i class="green time icon"></i> Uptime Monitor
</a>
<a class="item" tag="">
<i class="remove icon"></i> Network Tools
@ -105,6 +108,9 @@
<!-- UPnP based port fowarding -->
<div id="upnp" class="functiontab" target="upnp.html"></div>
<!-- Up Time Monitor -->
<div id="utm" class="functiontab" target="uptime.html"></div>
<!-- Utilities -->
<div id="utils" class="functiontab" target="utils.html"></div>
</div>
@ -207,6 +213,11 @@
alert("Invalid tabid given");
return;
}
if (window.innerWidth < 750){
//RWD mode, hide toolbar
$(".toolbar").fadeOut('fast');
}
$("#mainmenu").find(".item").removeClass("active");
$(targetBtn).addClass("active");
$(".functiontab").hide();
@ -215,6 +226,12 @@
window.location.hash = tabID;
}
$(window).on("resize", function(){
if (window.innerWidth >= 750 && $(".toolbar").is(":visible") == false){
$(".toolbar").show();
}
});
</script>
</body>

View File

@ -41,6 +41,7 @@ body{
.toolbar{
width: 240px;
min-width: 240px;
}
.contentWindow{
@ -48,11 +49,28 @@ body{
flex: 1;
}
.menutoggle{
display: none !important;
}
@media screen and (max-width: 750px) {
.toolbar {
position: fixed;
display: inline-block;
width: 100%;
width: 50%;
background-color: white;
top: 3.6em;
right: 0;
height: 100%;
margin-bottom: 1em;
z-index: 9;
padding: 1em;
display:none;
border-left: 1px solid rgb(206, 206, 206);
}
.menutoggle{
display: inline-block !important;
}
#mainmenu{
@ -63,6 +81,8 @@ body{
display: inline-block;
width: 100%;
}
}
.menudivider{
@ -95,7 +115,7 @@ body{
}
.statustab{
min-height: 5em;
min-height: 5.5em;
}
#summaryTotalCount{
@ -111,3 +131,40 @@ body{
.statustab.summary span, .statustab.summary i{
color: rgb(37, 37, 37);
}
/*
Uptime Monitor
*/
#utm{
background-color: white;
border-radius: 1em;
}
.domain{
margin-bottom: 1em;
position: relative;
}
.statusDot{
height: 1.8em;
border-radius: 0.4em;
width: 0.4em;
background-color: #e8e8e8;
display:inline-block;
cursor: pointer;
margin-left: 0.1em;
}
.online.statusDot{
background-color: #3bd671;
}
.error.statusDot{
background-color: #f29030;
}
.offline.statusDot{
background-color: #df484a;
}
.padding.statusDot{
cursor: auto;
}

View File

@ -146,7 +146,7 @@
</div>
<div class="ui divider"></div>
<div class="ui container" style="color: grey; font-size: 90%">
<p>Reverse Proxy by imuslab, Licensed under MIT</p>
<p>Powered by Zoraxy</p>
</div>
<br><br>