mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-01 13:17:21 +02:00
Updated tag system design
- Added search-able tag dropdown - Implemented realtime quick search - Added better tag coloring
This commit is contained in:
parent
70abfe6fcf
commit
05511ed4ca
@ -11,20 +11,47 @@
|
||||
.subdEntry td:not(.ignoremw){
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.httpProxyListTools{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tag-select{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag-select:hover{
|
||||
text-decoration: underline;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
<div class="ui action input" style="margin-bottom: 1em;">
|
||||
<input type="text" id="searchInput" placeholder="Search...">
|
||||
<button class="ui button" onclick="filterProxyList()">Search</button>
|
||||
</div>
|
||||
<div class="ui selection dropdown" id="tagFilterDropdown" style="margin-bottom: 1em;">
|
||||
<input type="hidden" name="tag">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Filter by Tag</div>
|
||||
<div class="menu">
|
||||
<div class="item" data-value="">All</div>
|
||||
<!-- Add more tag options dynamically -->
|
||||
<div class="httpProxyListTools" style="margin-bottom: 1em;">
|
||||
<div id="tagFilterDropdown" class="ui floating basic dropdown labeled icon button" style="min-width: 150px;">
|
||||
<i class="filter icon"></i>
|
||||
<span class="text">Filter by tags</span>
|
||||
<div class="menu">
|
||||
<div class="ui icon search input">
|
||||
<i class="search icon"></i>
|
||||
<input type="text" placeholder="Search tags...">
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="scrolling menu tagList">
|
||||
<!--
|
||||
Example:
|
||||
<div class="item">
|
||||
<div class="ui red empty circular label"></div>
|
||||
Important
|
||||
</div>
|
||||
-->
|
||||
<!-- Add more tag options dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui small input" style="width: 300px; height: 38px;">
|
||||
<input type="text" id="searchInput" placeholder="Quick Search" onkeydown="handleSearchInput(event);" onchange="handleSearchInput(event);" onblur="handleSearchInput(event);">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-height: 300px;">
|
||||
<table class="ui celled sortable unstackable compact table">
|
||||
<thead>
|
||||
@ -32,7 +59,7 @@
|
||||
<th>Host</th>
|
||||
<th>Destination</th>
|
||||
<th>Virtual Directory</th>
|
||||
<th>Tags</th> <!-- New column for tags -->
|
||||
<th>Tags</th>
|
||||
<th style="max-width: 300px;">Advanced Settings</th>
|
||||
<th class="no-sort" style="min-width:150px;">Actions</th>
|
||||
</tr>
|
||||
@ -138,8 +165,10 @@
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
||||
<td data-label="tags">
|
||||
${subd.Tags.map(tag => `<span class="ui tiny label tag-select">${tag}</span>`).join("")}
|
||||
<td data-label="tags" payload="${encodeURIComponent(JSON.stringify(subd.Tags))}" datatype="tags">
|
||||
<div class="tags-list">
|
||||
${subd.Tags.length >0 ? subd.Tags.map(tag => `<span class="ui tiny label tag-select" style="background-color: ${getTagColorByName(tag)}; color: ${getTagTextColor(tag)}">${tag}</span>`).join(""):"<small style='opacity: 0.3; pointer-events: none; user-select: none;'>No Tags</small>"}
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``}
|
||||
@ -166,36 +195,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Function to populate the tag filter dropdown
|
||||
function populateTagFilterDropdown(data) {
|
||||
let tags = new Set();
|
||||
data.forEach(subd => {
|
||||
subd.Tags.forEach(tag => tags.add(tag));
|
||||
});
|
||||
tags = Array.from(tags).sort((a, b) => a.localeCompare(b));
|
||||
let dropdownMenu = $("#tagFilterDropdown .menu");
|
||||
dropdownMenu.html('<div class="item tag-select" data-value="">All</div>');
|
||||
tags.forEach(tag => {
|
||||
dropdownMenu.append(`<div class="item tag-select" data-value="${tag}">${tag}</div>`);
|
||||
});
|
||||
$('#tagFilterDropdown').dropdown();
|
||||
}
|
||||
|
||||
// Function to filter the proxy list
|
||||
function filterProxyList() {
|
||||
let searchInput = $("#searchInput").val().toLowerCase();
|
||||
let selectedTag = $("#tagFilterDropdown").dropdown('get value');
|
||||
$("#httpProxyList tr").each(function() {
|
||||
let host = $(this).find("td[data-label='']").text().toLowerCase();
|
||||
let tags = $(this).find("td[data-label='tags']").text().toLowerCase();
|
||||
if ((host.includes(searchInput) || searchInput === "") && (tags.includes(selectedTag) || selectedTag === "")) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Perform realtime alias update without refreshing the whole page
|
||||
function updateAliasListForEndpoint(endpointName, newAliasDomainList){
|
||||
let targetEle = $(`.aliasDomains[eptuuid='${endpointName}']`);
|
||||
@ -333,7 +332,11 @@
|
||||
column.append(`<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="quickEditVdir('${uuid}');">
|
||||
<i class="ui yellow folder icon"></i> Edit Virtual Directories
|
||||
</button>`);
|
||||
|
||||
}else if (datatype == "tags"){
|
||||
column.append(`
|
||||
<div class="ui divider"></div>
|
||||
<button class="ui basic compact fluid tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editTags('${uuid}');"><i class="ui purple tag icon"></i> Edit tags</button>
|
||||
`);
|
||||
}else if (datatype == "advanced"){
|
||||
let authProvider = payload.AuthenticationProvider.AuthMethod;
|
||||
|
||||
@ -463,7 +466,6 @@
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAliasHostnames('${uuid}');"><i class=" blue at icon"></i> Alias</button>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAccessRule('${uuid}');"><i class="ui filter icon"></i> Access Rule</button>
|
||||
<button class="ui basic compact tiny ${enableQuickRequestButton?"":"disabled"} button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="requestCertificateForExistingHost('${uuid}', '${certificateDomains}', this);"><i class="green lock icon"></i> Get Certificate</button>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editTags('${uuid}');"><i class="ui tag icon"></i> Tags</button>
|
||||
`);
|
||||
|
||||
$(".hostAccessRuleSelector").dropdown();
|
||||
@ -506,8 +508,12 @@
|
||||
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
||||
let rateLimit = $(row).find(".RateLimit").val();
|
||||
let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
|
||||
let tags = $("#proxyTags").val().trim();
|
||||
|
||||
let tags = getTagsArrayFromEndpoint(uuid);
|
||||
if (tags.length > 0){
|
||||
tags = tags.join(",");
|
||||
}else{
|
||||
tags = "";
|
||||
}
|
||||
$.cjax({
|
||||
url: "/api/proxy/edit",
|
||||
method: "POST",
|
||||
@ -661,6 +667,75 @@
|
||||
listProxyEndpoints();
|
||||
}
|
||||
|
||||
/* Tags & Search */
|
||||
function handleSearchInput(event){
|
||||
if (event.key == "Escape"){
|
||||
$("#searchInput").val("");
|
||||
}
|
||||
filterProxyList();
|
||||
}
|
||||
|
||||
// Function to filter the proxy list
|
||||
function filterProxyList() {
|
||||
let searchInput = $("#searchInput").val().toLowerCase();
|
||||
let selectedTag = $("#tagFilterDropdown").dropdown('get value');
|
||||
$("#httpProxyList tr").each(function() {
|
||||
let host = $(this).find("td[data-label='']").text().toLowerCase();
|
||||
let tagElements = $(this).find("td[data-label='tags']");
|
||||
let tags = tagElements.attr("payload");
|
||||
tags = JSON.parse(decodeURIComponent(tags));
|
||||
if ((host.includes(searchInput) || searchInput === "") && (tags.includes(selectedTag) || selectedTag === "")) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to generate a color based on a tag name
|
||||
function getTagColorByName(tagName) {
|
||||
function hashCode(str) {
|
||||
return str.split('').reduce((prevHash, currVal) =>
|
||||
((prevHash << 5) - prevHash) + currVal.charCodeAt(0), 0);
|
||||
}
|
||||
let hash = hashCode(tagName);
|
||||
let color = '#' + ((hash >> 24) & 0xFF).toString(16).padStart(2, '0') +
|
||||
((hash >> 16) & 0xFF).toString(16).padStart(2, '0') +
|
||||
((hash >> 8) & 0xFF).toString(16).padStart(2, '0');
|
||||
return color;
|
||||
}
|
||||
|
||||
function getTagTextColor(tagName){
|
||||
let color = getTagColorByName(tagName);
|
||||
let r = parseInt(color.substr(1, 2), 16);
|
||||
let g = parseInt(color.substr(3, 2), 16);
|
||||
let b = parseInt(color.substr(5, 2), 16);
|
||||
let brightness = Math.round(((r * 299) + (g * 587) + (b * 114)) / 1000);
|
||||
return brightness > 125 ? "#000000" : "#ffffff";
|
||||
}
|
||||
|
||||
// Populate the tag filter dropdown
|
||||
function populateTagFilterDropdown(data) {
|
||||
let tags = new Set();
|
||||
data.forEach(subd => {
|
||||
subd.Tags.forEach(tag => tags.add(tag));
|
||||
});
|
||||
tags = Array.from(tags).sort((a, b) => a.localeCompare(b));
|
||||
let dropdownMenu = $("#tagFilterDropdown .tagList");
|
||||
dropdownMenu.html(`<div class="item tag-select" data-value="">
|
||||
<div class="ui grey empty circular label"></div>
|
||||
Show all
|
||||
</div>`);
|
||||
tags.forEach(tag => {
|
||||
let thisTagColor = getTagColorByName(tag);
|
||||
dropdownMenu.append(`<div class="item tag-select" data-value="${tag}">
|
||||
<div class="ui empty circular label" style="background-color: ${thisTagColor}; border-color: ${thisTagColor};" ></div>
|
||||
${tag}
|
||||
</div>`);
|
||||
});
|
||||
}
|
||||
|
||||
// Edit tags for a specific endpoint
|
||||
function editTags(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
@ -669,6 +744,23 @@
|
||||
showSideWrapper("snippet/tagEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
// Render the tags preview from tag editing snippet
|
||||
function renderTagsPreview(endpoint, tags){
|
||||
let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']");
|
||||
//Update the tag DOM
|
||||
let newTagDOM = tags.map(tag => `<span class="ui tiny label tag-select" style="background-color: ${getTagColorByName(tag)}; color: ${getTagTextColor(tag)}">${tag}</span>`).join("");
|
||||
$(targetProxyRuleEle).find(".tags-list").html(newTagDOM);
|
||||
|
||||
//Update the tag payload
|
||||
$(targetProxyRuleEle).attr("payload", encodeURIComponent(JSON.stringify(tags)));
|
||||
}
|
||||
|
||||
function getTagsArrayFromEndpoint(endpoint){
|
||||
let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']");
|
||||
let tags = $(targetProxyRuleEle).attr("payload");
|
||||
return JSON.parse(decodeURIComponent(tags));
|
||||
}
|
||||
|
||||
// Initialize the proxy list on page load
|
||||
$(document).ready(function() {
|
||||
listProxyEndpoints();
|
||||
|
@ -121,6 +121,9 @@ body.darkTheme .ui.basic.button:not(.red) {
|
||||
body.darkTheme .ui.basic.button:not(.red):hover {
|
||||
border: 1px solid var(--button_border_color) !important;
|
||||
background-color: var(--theme_bg) !important;
|
||||
}
|
||||
|
||||
body.darkTheme .ui.basic.button:not(.red):not(.dropdown):hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@ -549,6 +552,18 @@ body.darkTheme .RateLimit input {
|
||||
border-color: var(--theme_highlight) !important;
|
||||
}
|
||||
|
||||
body.darkTheme .menu.transition{
|
||||
background-color: var(--theme_bg) !important;
|
||||
color: var(--text_color) !important;
|
||||
}
|
||||
|
||||
body.darkTheme .ui.dropdown .menu{
|
||||
background: var(--theme_bg_primary) !important;
|
||||
}
|
||||
|
||||
body.darkTheme .ui.dropdown .menu .item{
|
||||
color: var(--text_color) !important;
|
||||
}
|
||||
/*
|
||||
Virtual Directorie Table
|
||||
*/
|
||||
|
@ -9,6 +9,9 @@
|
||||
<script src="../script/utils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
@ -67,7 +70,9 @@
|
||||
parent.msgbox(data.error, false);
|
||||
} else {
|
||||
parent.msgbox("Tags updated");
|
||||
parent.hideSideWrapper();
|
||||
//Update the preview on parent page
|
||||
parent.renderTagsPreview(editingEndpoint.ep, tags);
|
||||
//parent.hideSideWrapper();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user