mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-27 01:41:44 +02:00
Updates v2.6 experimental build
+ Basic auth + Support TLS verification skip (for self signed certs) + Added trend analysis + Added referer and file type analysis + Added cert expire day display + Moved subdomain proxy logic to dpcore
This commit is contained in:
@ -58,6 +58,7 @@
|
||||
<thead>
|
||||
<tr><th>Domain</th>
|
||||
<th>Last Update</th>
|
||||
<th>Expire At</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
</tr></thead>
|
||||
<tbody id="certifiedDomainList">
|
||||
@ -108,6 +109,7 @@
|
||||
$("#certifiedDomainList").append(`<tr>
|
||||
<td>${entry.Domain}</td>
|
||||
<td>${entry.LastModifiedDate}</td>
|
||||
<td>${entry.ExpireDate}</td>
|
||||
<td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
|
||||
</tr>`);
|
||||
})
|
||||
|
@ -20,7 +20,6 @@
|
||||
<div class="field">
|
||||
<label>Subdomain Matching Keyword / Virtual Directory Name</label>
|
||||
<input type="text" id="rootname" placeholder="s1.mydomain.com">
|
||||
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>IP Address or Domain Name with port</label>
|
||||
@ -33,6 +32,58 @@
|
||||
<label>Proxy Target require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Advance configs -->
|
||||
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||
<div id="advanceProxyRules" class="ui fluid accordion">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
Advance Settings
|
||||
</div>
|
||||
<div class="content">
|
||||
<p></p>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="skipTLSValidation">
|
||||
<label>Ignore TLS/SSL Verification Error<br><small>E.g. self-signed, expired certificate (Not Recommended)</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="requireBasicAuth">
|
||||
<label>Require Basic Auth<br><small>Require client to login in order to view the page</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="basicAuthCredentials" class="field">
|
||||
<p>Enter the username and password for allowing them to access this proxy endpoint</p>
|
||||
<table class="ui very basic celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Password</th>
|
||||
<th>Remove</th>
|
||||
</tr></thead>
|
||||
<tbody id="basicAuthCredentialTable">
|
||||
<tr>
|
||||
<td colspan="3"><i class="ui green circle check icon"></i> No Entered Credential</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="three small fields credentialEntry">
|
||||
<div class="field">
|
||||
<input id="basicAuthCredUsername" type="text" placeholder="Username" autocomplete="off">
|
||||
</div>
|
||||
<div class="field">
|
||||
<input id="basicAuthCredPassword" type="password" placeholder="Password" autocomplete="off">
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="ui basic button" onclick="addCredentials();"><i class="blue add icon"></i> Add Credential</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui basic button" onclick="newProxyEndpoint();"><i class="blue add icon"></i> Create Endpoint</button>
|
||||
<br><br>
|
||||
</div>
|
||||
@ -63,12 +114,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("#advanceProxyRules").accordion();
|
||||
|
||||
//New Proxy Endpoint
|
||||
function newProxyEndpoint(){
|
||||
var type = $("#ptype").val();
|
||||
var rootname = $("#rootname").val();
|
||||
var proxyDomain = $("#proxyDomain").val();
|
||||
var useTLS = $("#reqTls")[0].checked;
|
||||
var skipTLSValidation = $("#skipTLSValidation")[0].checked;
|
||||
var requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
||||
|
||||
if (type === "vdir") {
|
||||
if (!rootname.startsWith("/")) {
|
||||
@ -101,7 +156,15 @@
|
||||
//Create the endpoint by calling add
|
||||
$.ajax({
|
||||
url: "/api/proxy/add",
|
||||
data: {type: type, rootname: rootname, tls: useTLS, ep: proxyDomain},
|
||||
data: {
|
||||
type: type,
|
||||
rootname: rootname,
|
||||
tls: useTLS,
|
||||
ep: proxyDomain,
|
||||
tlsval: skipTLSValidation,
|
||||
bauth: requireBasicAuth,
|
||||
cred: JSON.stringify(credentials),
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
@ -114,6 +177,8 @@
|
||||
//Clear old data
|
||||
$("#rootname").val("");
|
||||
$("#proxyDomain").val("");
|
||||
credentials = [];
|
||||
updateTable();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -152,6 +217,83 @@
|
||||
}
|
||||
|
||||
|
||||
function toggleBasicAuth() {
|
||||
var basicAuthDiv = document.getElementById('basicAuthOnly');
|
||||
if ($("#requireBasicAuth").parent().checkbox("is checked")) {
|
||||
$("#basicAuthCredentials").removeClass("disabled");
|
||||
} else {
|
||||
$("#basicAuthCredentials").addClass("disabled");
|
||||
}
|
||||
}
|
||||
$("#requireBasicAuth").on('change', toggleBasicAuth);
|
||||
toggleBasicAuth();
|
||||
|
||||
|
||||
/*
|
||||
Credential Managements
|
||||
|
||||
*/
|
||||
let credentials = []; // Global variable to store credentials
|
||||
|
||||
function addCredentials() {
|
||||
// Retrieve the username and password input values
|
||||
var username = $('#basicAuthCredUsername').val();
|
||||
var password = $('#basicAuthCredPassword').val();
|
||||
|
||||
if(username == "" || password == ""){
|
||||
msgbox("Username or password cannot be empty", false, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new credential object
|
||||
var credential = {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
|
||||
// Add the credential to the global credentials array
|
||||
credentials.push(credential);
|
||||
|
||||
// Clear the input fields
|
||||
$('#basicAuthCredUsername').val('');
|
||||
$('#basicAuthCredPassword').val('');
|
||||
|
||||
// Update the table body with the credentials
|
||||
updateTable();
|
||||
}
|
||||
|
||||
function updateTable() {
|
||||
var tableBody = $('#basicAuthCredentialTable');
|
||||
tableBody.empty();
|
||||
|
||||
if (credentials.length === 0) {
|
||||
tableBody.append('<tr><td colspan="3"><i class="ui green circle check icon"></i> No Entered Credential</td></tr>');
|
||||
} else {
|
||||
for (var i = 0; i < credentials.length; i++) {
|
||||
var credential = credentials[i];
|
||||
var username = credential.username;
|
||||
var password = credential.password.replace(/./g, '*'); // Replace each character with '*'
|
||||
|
||||
var row = '<tr>' +
|
||||
'<td>' + username + '</td>' +
|
||||
'<td>' + password + '</td>' +
|
||||
'<td><button class="ui basic button" onclick="removeCredential(' + i + ');"><i class="red remove icon"></i> Remove</button></td>' +
|
||||
'</tr>';
|
||||
|
||||
tableBody.append(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeCredential(index) {
|
||||
// Remove the credential from the credentials array
|
||||
credentials.splice(index, 1);
|
||||
|
||||
// Update the table body
|
||||
updateTable();
|
||||
}
|
||||
|
||||
|
||||
//Check if a string is a valid subdomain
|
||||
function isSubdomainDomain(str) {
|
||||
const regex = /^(localhost|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}\.)$/i;
|
||||
|
@ -157,6 +157,42 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui stackable grid">
|
||||
<div class="eight wide column">
|
||||
<h3>Request File Types</h3>
|
||||
<p>The file types being served by this proxy</p>
|
||||
<div>
|
||||
<canvas id="stats_filetype"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<h3>Referring Sites</h3>
|
||||
<p>The Top 100 sources of traffic according to referer header</p>
|
||||
<div>
|
||||
<div style="height: 500px; overflow-y: auto;">
|
||||
<table class="ui unstackable striped celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="no-sort">Referer</th>
|
||||
<th class="no-sort">Requests</th>
|
||||
</tr></thead>
|
||||
<tbody id="stats_RefererTable">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" id="trendGraphs">
|
||||
<h3>Visitor Trend Analysis</h3>
|
||||
<p>Request trends in the selected time range</p>
|
||||
<div>
|
||||
<canvas id="requestTrends"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <button class="ui icon right floated basic button" onclick="initStatisticSummery();"><i class="green refresh icon"></i> Refresh</button> -->
|
||||
<br><br>
|
||||
@ -177,7 +213,6 @@
|
||||
//Two dates are given and they are not identical
|
||||
loadStatisticByRange(startdate, enddate);
|
||||
console.log(startdate, enddate);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,6 +254,15 @@
|
||||
|
||||
//Render user agent analysis
|
||||
renderUserAgentCharts(data.UserAgent);
|
||||
|
||||
//Render file type by analysising request URL paths
|
||||
renderFileTypeGraph(data.RequestURL);
|
||||
|
||||
//Render Referer header
|
||||
renderRefererTable(data.Referer);
|
||||
|
||||
//Hide the trend graphs
|
||||
$("#trendGraphs").hide();
|
||||
});
|
||||
}
|
||||
initStatisticSummery();
|
||||
@ -259,7 +303,16 @@
|
||||
|
||||
//Render user agent analysis
|
||||
renderUserAgentCharts(data.Summary.UserAgent);
|
||||
|
||||
//Render file type by analysising request URL paths
|
||||
renderFileTypeGraph(data.Summary.RequestURL);
|
||||
|
||||
//Render Referer header
|
||||
renderRefererTable(data.Summary.Referer);
|
||||
|
||||
//Render the trend graph
|
||||
$("#trendGraphs").show();
|
||||
renderTrendGraph(data.Records);
|
||||
});
|
||||
}
|
||||
|
||||
@ -313,6 +366,155 @@
|
||||
$("#statsRangeEnd").val("");
|
||||
}
|
||||
|
||||
function renderRefererTable(refererList){
|
||||
const sortedEntries = Object.entries(refererList).sort(([, valueA], [, valueB]) => valueB - valueA);
|
||||
console.log(sortedEntries);
|
||||
$("#stats_RefererTable").html("");
|
||||
let endStop = 100;
|
||||
if (sortedEntries.length < 100){
|
||||
endStop = sortedEntries.length;
|
||||
}
|
||||
for (var i = 0; i < endStop; i++) {
|
||||
let referer = (decodeURIComponent(sortedEntries[i][0])).replace(/(<([^>]+)>)/ig,"");
|
||||
if (sortedEntries[i][0] == ""){
|
||||
//Root
|
||||
referer = `<span style="color: #b5b5b5;">(<i class="eye slash outline icon"></i> Unknown or Hidden)</span>`;
|
||||
}
|
||||
$("#stats_RefererTable").append(`<tr>
|
||||
<td>${referer}</td>
|
||||
<td>${sortedEntries[i][1]}</td>
|
||||
</tr>`);
|
||||
}
|
||||
}
|
||||
|
||||
function renderFileTypeGraph(requestURLs){
|
||||
//Create the device chart
|
||||
let fileExtensions = {};
|
||||
for (const [url, count] of Object.entries(requestURLs)) {
|
||||
let filename = url.split("/").pop();
|
||||
let ext = "";
|
||||
if (filename == ""){
|
||||
//Loading from a folder
|
||||
ext = "Folder path"
|
||||
}else{
|
||||
if (filename.includes(".")){
|
||||
ext = filename.split(".").pop();
|
||||
}else{
|
||||
ext = "API call"
|
||||
}
|
||||
}
|
||||
|
||||
if (fileExtensions[ext] != undefined){
|
||||
fileExtensions[ext] = fileExtensions[ext] + count;
|
||||
}else{
|
||||
//First time this ext show up
|
||||
fileExtensions[ext] = count;
|
||||
}
|
||||
}
|
||||
|
||||
//Convert the key-value pairs to array for graph render
|
||||
let fileTypes = [];
|
||||
let fileCounts = [];
|
||||
let colors = [];
|
||||
for (const [ftype, count] of Object.entries(fileExtensions)) {
|
||||
fileTypes.push(ftype);
|
||||
fileCounts.push(count);
|
||||
colors.push(generateColorFromHash(ftype));
|
||||
}
|
||||
|
||||
let filetypeChart = new Chart(document.getElementById("stats_filetype"), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: fileTypes,
|
||||
datasets: [{
|
||||
data: fileCounts,
|
||||
backgroundColor: colors,
|
||||
hoverBackgroundColor: colors,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
}
|
||||
});
|
||||
|
||||
statisticCharts.push(filetypeChart);
|
||||
}
|
||||
|
||||
function renderTrendGraph(dailySummary){
|
||||
// Get the canvas element
|
||||
const canvas = document.getElementById('requestTrends');
|
||||
|
||||
//Generate the X axis labels
|
||||
let datesLabel = [];
|
||||
let succData = [];
|
||||
let errorData = [];
|
||||
let totalData = [];
|
||||
for (var i = 0; i < dailySummary.length; i++){
|
||||
let thisDayData = dailySummary[i];
|
||||
datesLabel.push("Day " + i);
|
||||
succData.push(thisDayData.ValidRequest);
|
||||
errorData.push(thisDayData.ErrorRequest);
|
||||
totalData.push(thisDayData.TotalRequest);
|
||||
}
|
||||
// Create the chart
|
||||
let TrendChart = new Chart(canvas, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: datesLabel,
|
||||
datasets: [
|
||||
{
|
||||
label: 'All Requests',
|
||||
data: totalData,
|
||||
borderColor: '#7d99f7',
|
||||
backgroundColor: 'rgba(125, 153, 247, 0.4)',
|
||||
fill: false
|
||||
},
|
||||
{
|
||||
label: 'Success Requests',
|
||||
data: succData,
|
||||
borderColor: '#6dad7c',
|
||||
backgroundColor: "rgba(109, 173, 124, 0.4)",
|
||||
fill: true
|
||||
},
|
||||
{
|
||||
label: 'Error Requests',
|
||||
data: errorData,
|
||||
borderColor: '#de7373',
|
||||
backgroundColor: "rgba(222, 115, 115, 0.4)",
|
||||
fill: true
|
||||
},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
title: {
|
||||
display: true,
|
||||
//text: 'Line Chart Example'
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
title: {
|
||||
display: false,
|
||||
text: 'Time'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Request Counts'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
statisticCharts.push(TrendChart);
|
||||
}
|
||||
|
||||
function renderUserAgentCharts(userAgentsEntries){
|
||||
let userAgents = Object.keys(userAgentsEntries);
|
||||
let requestCounts = Object.values(userAgentsEntries);
|
||||
|
@ -9,7 +9,9 @@
|
||||
<tr>
|
||||
<th>Matching Domain</th>
|
||||
<th>Proxy To</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
<th>TLS/SSL Verification</th>
|
||||
<th>Basic Auth</th>
|
||||
<th class="no-sort" style="min-width: 7.2em;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="subdList">
|
||||
@ -36,12 +38,17 @@
|
||||
data.forEach(subd => {
|
||||
let tlsIcon = "";
|
||||
if (subd.RequireTLS){
|
||||
tlsIcon = `<i class="lock icon"></i>`;
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
}
|
||||
$("#subdList").append(`<tr>
|
||||
<td data-label="">${subd.MatchingDomain}</td>
|
||||
<td data-label="">${subd.RootOrMatchingDomain}</td>
|
||||
<td data-label="">${subd.Domain} ${tlsIcon}</td>
|
||||
<td class="center aligned" data-label=""><button class="ui circular mini red basic icon button" onclick='deleteEndpoint("subd","${subd.MatchingDomain}")'><i class="trash icon"></i></button></td>
|
||||
<td data-label="">${!subd.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td>
|
||||
<td data-label="">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
|
||||
<td class="center aligned" data-label="">
|
||||
<button class="ui circular mini basic icon button" onclick='editEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
|
||||
<button class="ui circular mini red basic icon button" onclick='deleteEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="trash icon"></i></button>
|
||||
</td>
|
||||
</tr>`);
|
||||
});
|
||||
}
|
||||
|
@ -9,7 +9,9 @@
|
||||
<tr>
|
||||
<th>Virtual Directory</th>
|
||||
<th>Proxy To</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
<th>TLS/SSL Verification</th>
|
||||
<th>Basic Auth</th>
|
||||
<th class="no-sort" style="min-width: 7.2em;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vdirList">
|
||||
@ -39,12 +41,17 @@
|
||||
data.forEach(vdir => {
|
||||
let tlsIcon = "";
|
||||
if (vdir.RequireTLS){
|
||||
tlsIcon = `<i title="TLS mode" class="lock icon"></i>`;
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
}
|
||||
$("#vdirList").append(`<tr>
|
||||
<td data-label="">${vdir.Root}</td>
|
||||
<td data-label="">${vdir.RootOrMatchingDomain}</td>
|
||||
<td data-label="">${vdir.Domain} ${tlsIcon}</td>
|
||||
<td class="center aligned" data-label=""><button class="ui circular mini red basic icon button" onclick='deleteEndpoint("vdir","${vdir.Root}")'><i class="trash icon"></i></button></td>
|
||||
<td data-label="">${!subd.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td>
|
||||
<td data-label="">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
|
||||
<td class="center aligned" data-label="">
|
||||
<button class="ui circular mini basic icon button" onclick='editEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
|
||||
<button class="ui circular mini red basic icon button" onclick='deleteEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="trash icon"></i></button>
|
||||
</td>
|
||||
</tr>`);
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user