refactor: restructured js

This commit is contained in:
Tim Dreyer 2025-04-25 14:38:42 +02:00
parent e049761f36
commit 6353cc532a

View File

@ -31,8 +31,8 @@
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" id="showUnexposed" class="hidden" /> <input type="checkbox" id="showUnexposed" class="hidden" />
<label for="showUnexposed" <label for="showUnexposed"
>Show Containers with unexposed ports >Show Containers with unexposed ports</label
</label> >
</div> </div>
</div> </div>
</div> </div>
@ -50,6 +50,7 @@
<div id="networkedList" class="ui middle aligned divided list"> <div id="networkedList" class="ui middle aligned divided list">
<div class="ui active loader"></div> <div class="ui active loader"></div>
</div> </div>
<div class="ui horizontal divider"></div>
<!-- Host Mode Containers List --> <!-- Host Mode Containers List -->
<div id="hostmodeListHeader" class="ui header" hidden> <div id="hostmodeListHeader" class="ui header" hidden>
<div class="content"> <div class="content">
@ -81,20 +82,8 @@
</div> </div>
<div id="existingList" class="ui middle aligned divided list"></div> <div id="existingList" class="ui middle aligned divided list"></div>
</div> </div>
<script> <script>
// debounce function to prevent excessive calls to a function // DOM elements
function debounce(func, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), delay);
};
}
// wait until DOM is fully loaded before executing script
$(document).ready(() => {
// jQuery objects for UI elements
const $networkedList = $("#networkedList"); const $networkedList = $("#networkedList");
const $hostmodeListHeader = $("#hostmodeListHeader"); const $hostmodeListHeader = $("#hostmodeListHeader");
@ -110,77 +99,98 @@
const $showOnlyRunning = $("#showOnlyRunning"); const $showOnlyRunning = $("#showOnlyRunning");
const $showUnexposed = $("#showUnexposed"); const $showUnexposed = $("#showUnexposed");
// objects to store container entries // maps for containers
let networkedEntries = {}; // { name, ip, port } let networkedEntries = {};
let hostmodeEntries = {}; // { name, ip } let hostmodeEntries = {};
let othersEntries = {}; // { name, ips, ports } let othersEntries = {};
let existingEntries = {}; // { name, ip, port } let existingEntries = {};
// load showUnexposed checkbox state from local storage // initial load
function loadShowUnexposedState() { $(document).ready(() => {
const storedState = localStorage.getItem("showUnexposed"); loadCheckboxState("showUnexposed", $showUnexposed);
if (storedState !== null) { loadCheckboxState("showOnlyRunning", $showOnlyRunning);
$showUnexposed.prop("checked", storedState === "true"); initializeEventListeners();
} getDockerContainers();
} });
// save showUnexposed checkbox state to local storage // event listeners
function saveShowUnexposedState() { function initializeEventListeners() {
localStorage.setItem("showUnexposed", $showUnexposed.prop("checked")); $showUnexposed.on("change", () => {
} saveCheckboxState("showUnexposed", $showUnexposed);
getDockerContainers();
});
// load showOnlyRunning checkbox state from local storage $showOnlyRunning.on("change", () => {
function loadShowOnlyRunningState() { saveCheckboxState("showOnlyRunning", $showOnlyRunning);
const storedState = localStorage.getItem("showOnlyRunning"); getDockerContainers();
if (storedState !== null) { });
$showOnlyRunning.prop("checked", storedState === "true");
}
}
// save showOnlyRunning checkbox state to local storage // debounce searchbar input to prevent excessive filtering
function saveShowOnlyRunningState() { $searchbar.on(
localStorage.setItem( "input",
"showOnlyRunning", debounce(() => filterLists($searchbar.val().toLowerCase()), 300)
$showOnlyRunning.prop("checked")
); );
$networkedList.on("click", ".add-button", (event) => {
const key = $(event.currentTarget).data("key");
if (networkedEntries[key]) {
parent.addContainerItem(networkedEntries[key]);
}
});
$hostmodeList.on("click", ".add-button", (event) => {
const key = $(event.currentTarget).data("key");
if (hostmodeEntries[key]) {
parent.addContainerItem(hostmodeEntries[key]);
}
});
}
// filter lists by toggling item visibility
function filterLists(searchTerm) {
$(".list .item").each((_, item) => {
const content = $(item).text().toLowerCase();
$(item).toggle(content.includes(searchTerm));
});
} }
// fetch docker containers // reset UI and state
function getDockerContainers() { function reset() {
$networkedList.html('<div class="ui active loader"></div>');
$hostmodeListHeader.attr("hidden", true);
$hostmodeList.empty();
$othersListHeader.attr("hidden", true);
$othersList.empty();
$existingListHeader.attr("hidden", true);
$existingList.empty();
networkedEntries = {}; networkedEntries = {};
hostmodeEntries = {}; hostmodeEntries = {};
othersEntries = {}; othersEntries = {};
existingEntries = {}; existingEntries = {};
const hostRequest = $.get("/api/proxy/list?type=host"); $networkedList.empty();
const dockerRequest = $.get("/api/docker/containers"); $hostmodeList.empty();
$othersList.empty();
$existingList.empty();
Promise.all([hostRequest, dockerRequest]) $hostmodeListHeader.attr("hidden", true);
.then(([hostData, dockerData]) => { $othersListHeader.attr("hidden", true);
$existingListHeader.attr("hidden", true);
}
// process docker data
async function getDockerContainers() {
reset();
$networkedList.html('<div class="ui active loader"></div>');
try {
const [hostData, dockerData] = await Promise.all([
$.get("/api/proxy/list?type=host"),
$.get("/api/docker/containers"),
]);
if (!hostData.error && !dockerData.error) { if (!hostData.error && !dockerData.error) {
processDockerData(hostData, dockerData); processDockerData(hostData, dockerData);
} else { } else {
showError(hostData.error || dockerData.error); showError(hostData.error || dockerData.error);
} }
}) } catch (error) {
.catch((error) => {
console.error(error); console.error(error);
parent.msgbox("Error loading data: " + error.message, false); parent.msgbox("Error loading data: " + error.message, false);
}); }
} }
// process docker data and update ui
function processDockerData(hostData, dockerData) { function processDockerData(hostData, dockerData) {
const { containers } = dockerData; const { containers } = dockerData;
const existingTargets = new Set( const existingTargets = new Set(
@ -203,41 +213,41 @@
) )
: []; : [];
// process each container // iterate over all containers
containers.forEach((container) => { containers.forEach((container) => {
// skip containers that are not running, if the checkbox is checked
if (
$showOnlyRunning.is(":checked") &&
container.State !== "running"
) {
return;
}
// skip containers in network mode "none" // skip containers in network mode "none"
if (container.HostConfig.NetworkMode === "none") { if (container.HostConfig.NetworkMode === "none") {
return; return;
} }
const name = container.Names[0].replace(/^\//, ""); // skip containers not running, if the option is enabled
if (
container.State !== "running" &&
$showOnlyRunning.prop("checked")
) {
return;
}
// host mode containers resolve to host.docker.internal // sanitize container name
const containerName = container.Names[0].replace(/^\//, "");
// containers in network mode "host" should resolve to "host.docker.internal"
if ( if (
container.HostConfig.NetworkMode === "host" && container.HostConfig.NetworkMode === "host" &&
!hostmodeEntries[name] !hostmodeEntries[container.Id]
) { ) {
hostmodeEntries[name] = { hostmodeEntries[container.Id] = {
name, name: containerName,
ip: "host.docker.internal", ip: "host.docker.internal",
}; };
return; return;
} }
// check if the container shares a network with Zoraxy // networks that are shared with Zoraxy
const sharedNetworks = Object.values( const sharedNetworks = Object.values(
container.NetworkSettings.Networks container.NetworkSettings.Networks
).filter((network) => zoraxyNetworkIDs.includes(network.NetworkID)); ).filter((network) => zoraxyNetworkIDs.includes(network.NetworkID));
// if the container does not share a network with Zoraxy, add it to the others list
if (!sharedNetworks.length) { if (!sharedNetworks.length) {
const ips = Object.values(container.NetworkSettings.Networks).map( const ips = Object.values(container.NetworkSettings.Networks).map(
(network) => network.IPAddress (network) => network.IPAddress
@ -247,8 +257,8 @@
return portObject.PublicPort || portObject.PrivatePort; return portObject.PublicPort || portObject.PrivatePort;
}); });
othersEntries[name] = { othersEntries[container.Id] = {
name, name: containerName,
ips, ips,
ports, ports,
}; };
@ -263,28 +273,25 @@
} }
const port = portObject.PublicPort || portObject.PrivatePort; const port = portObject.PublicPort || portObject.PrivatePort;
const key = `${containerName}:${port}`;
const key = `${name}:${port}`; if (existingTargets.has(key) && !existingEntries[key]) {
if (
existingTargets.has(`${name}:${port}`) &&
!existingEntries[key]
) {
existingEntries[key] = { existingEntries[key] = {
name, name: containerName,
ip: name, ip: containerName,
port, port,
}; };
} else if (!networkedEntries[key]) { } else if (!networkedEntries[key]) {
networkedEntries[key] = { networkedEntries[key] = {
name, name: containerName,
ip: name, ip: containerName,
port, port,
}; };
} }
}); });
}); });
// update UI lists // finally update the UI
updateNetworkedList(); updateNetworkedList();
updateHostmodeList(); updateHostmodeList();
updateOthersList(); updateOthersList();
@ -380,7 +387,7 @@
} }
} }
// update existing list // update existing rules list
function updateExistingList() { function updateExistingList() {
$existingList.empty(); $existingList.empty();
let html = ""; let html = "";
@ -413,72 +420,29 @@
} }
// //
// event listeners // utils
// //
$showUnexposed.on("change", () => { // local storage handling
saveShowUnexposedState(); // save the new state to local storage function loadCheckboxState(id, $elem) {
getDockerContainers(); const state = localStorage.getItem(id);
}); if (state !== null) {
$elem.prop("checked", state === "true");
$showOnlyRunning.on("change", () => {
saveShowOnlyRunningState(); // save the new state to local storage
getDockerContainers();
});
// debounce searchbar input with 300ms delay, then filter list
// this prevents excessive calls to the filter function
$searchbar.on(
"input",
debounce(() => {
const search = $searchbar.val().toLowerCase();
$("#networkedList .item").each((index, item) => {
const content = $(item).text().toLowerCase();
$(item).toggle(content.includes(search));
});
$("#hostmodeList .item").each((index, item) => {
const content = $(item).text().toLowerCase();
$(item).toggle(content.includes(search));
});
$("#othersList .item").each((index, item) => {
const content = $(item).text().toLowerCase();
$(item).toggle(content.includes(search));
});
$("#existingList .item").each((index, item) => {
const content = $(item).text().toLowerCase();
$(item).toggle(content.includes(search));
});
}, 300)
);
$networkedList.on("click", ".add-button", (event) => {
const key = $(event.currentTarget).data("key");
if (networkedEntries[key]) {
parent.addContainerItem(networkedEntries[key]);
} }
});
$hostmodeList.on("click", ".add-button", (event) => {
const key = $(event.currentTarget).data("key");
if (hostmodeEntries[key]) {
parent.addContainerItem(hostmodeEntries[key]);
} }
});
// function saveCheckboxState(id, $elem) {
// initial calls localStorage.setItem(id, $elem.prop("checked"));
// }
// load state of showUnexposed checkbox // debounce function
loadShowUnexposedState(); function debounce(func, delay) {
let timeout;
// initial load of docker containers return (...args) => {
getDockerContainers(); clearTimeout(timeout);
}); timeout = setTimeout(() => func(...args), delay);
};
}
</script> </script>
</body> </body>
</html> </html>