Update dockerContainersList.html

Merged PR into dark theme branch
This commit is contained in:
Toby Chui 2024-11-24 12:35:26 +08:00
parent 2dcf578cbe
commit f3591aa171

View File

@ -8,15 +8,22 @@
<script src="../script/semantic/semantic.min.js"></script> <script src="../script/semantic/semantic.min.js"></script>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br /> <br />
<div class="ui container"> <div class="ui container">
<div class="ui form">
<div class="field">
<input
id="searchbar"
type="text"
placeholder="Search..."
autocomplete="off"
/>
</div>
<div class="field"> <div class="field">
<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
<br /> <br />
<small <small
>Please make sure Zoraxy and the target container share a >Please make sure Zoraxy and the target container share a
@ -25,6 +32,7 @@
</label> </label>
</div> </div>
</div> </div>
</div>
<div class="ui header"> <div class="ui header">
<div class="content"> <div class="content">
List of Docker Containers List of Docker Containers
@ -48,131 +56,185 @@
</div> </div>
<script> <script>
// debounce function to prevent excessive calls to a function
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(() => {
const $containersList = $("#containersList");
const $containersAddedList = $("#containersAddedList");
const $containersAddedListHeader = $("#containersAddedListHeader");
const $searchbar = $("#searchbar");
const $showUnexposed = $("#showUnexposed");
let lines = {}; let lines = {};
let linesAdded = {}; let linesAdded = {};
document // load showUnexposed checkbox state from local storage
.getElementById("showUnexposed") function loadShowUnexposedState() {
.addEventListener("change", () => { const storedState = localStorage.getItem("showUnexposed");
console.log("showUnexposed", $("#showUnexposed").is(":checked")); if (storedState !== null) {
$("#containersList").html('<div class="ui loader active"></div>'); $showUnexposed.prop("checked", storedState === "true");
}
}
$("#containersAddedList").empty(); // save showUnexposed checkbox state to local storage
$("#containersAddedListHeader").attr("hidden", true); function saveShowUnexposedState() {
localStorage.setItem("showUnexposed", $showUnexposed.prop("checked"));
}
// fetch docker containers
function getDockerContainers() {
$containersList.html('<div class="ui loader active"></div>');
$containersAddedList.empty();
$containersAddedListHeader.attr("hidden", true);
lines = {}; lines = {};
linesAdded = {}; linesAdded = {};
getDockerContainers();
});
function getDockerContainers() {
const hostRequest = $.get("/api/proxy/list?type=host"); const hostRequest = $.get("/api/proxy/list?type=host");
const dockerRequest = $.get("/api/docker/containers"); const dockerRequest = $.get("/api/docker/containers");
Promise.all([hostRequest, dockerRequest]) Promise.all([hostRequest, dockerRequest])
.then(([hostData, dockerData]) => { .then(([hostData, dockerData]) => {
if (!dockerData.error && !hostData.error) { if (!hostData.error && !dockerData.error) {
const { containers, network } = dockerData; processDockerData(hostData, dockerData);
} else {
showError(hostData.error || dockerData.error);
}
})
.catch((error) => {
console.error(error);
parent.msgbox("Error loading data: " + error.message, false);
});
}
// process docker data and update ui
function processDockerData(hostData, dockerData) {
const { containers } = dockerData;
const existingTargets = new Set( const existingTargets = new Set(
hostData.flatMap(({ ActiveOrigins }) => hostData.flatMap(({ ActiveOrigins }) =>
ActiveOrigins.map(({ OriginIpOrDomain }) => OriginIpOrDomain) ActiveOrigins.map(({ OriginIpOrDomain }) => OriginIpOrDomain)
) )
); );
for (const container of containers) { containers.forEach((container) => {
const Ports = container.Ports;
const name = container.Names[0].replace(/^\//, ""); const name = container.Names[0].replace(/^\//, "");
container.Ports.forEach((portObject) => {
for (const portObject of Ports) { let port = portObject.PublicPort || portObject.PrivatePort;
let port = portObject.PublicPort; if (!portObject.PublicPort && !$showUnexposed.is(":checked"))
if (!port) { return;
if (!$("#showUnexposed").is(":checked")) {
continue;
}
port = portObject.PrivatePort;
}
const key = `${name}-${port}`;
// if port is not exposed, use container's name and let docker handle the routing // if port is not exposed, use container's name and let docker handle the routing
// BUT this will only work if the container is on the same network as Zoraxy // BUT this will only work if the container is on the same network as Zoraxy
const targetAddress = portObject.IP || name; const targetAddress = portObject.IP || name;
const key = `${name}-${port}`;
if ( if (
existingTargets.has(`${targetAddress}:${port}`) && existingTargets.has(`${targetAddress}:${port}`) &&
!linesAdded[key] !linesAdded[key]
) { ) {
linesAdded[key] = { linesAdded[key] = { name, ip: targetAddress, port };
name,
ip: targetAddress,
port,
};
} else if (!lines[key]) { } else if (!lines[key]) {
lines[key] = { lines[key] = { name, ip: targetAddress, port };
name,
ip: targetAddress,
port,
};
} }
}
}
for (const [key, line] of Object.entries(lines)) {
$("#containersList").append(
`<div class="item">
<div class="right floated content">
<div class="ui button" onclick="addContainerItem('${key}');">Add</div>
</div>
<div class="content">
<div class="header">${line.name}</div>
<div class="description">
${line.ip}:${line.port}
</div>
</div>`
);
}
for (const [key, line] of Object.entries(linesAdded)) {
$("#containersAddedList").append(
`<div class="item">
<div class="content">
<div class="header">${line.name}</div>
<div class="description">
${line.ip}:${line.port}
</div>
</div>`
);
}
Object.entries(linesAdded).length &&
$("#containersAddedListHeader").removeAttr("hidden");
$("#containersList .loader").removeClass("active");
} else {
parent.msgbox(
`Error loading data: ${dockerData.error || hostData.error}`,
false
);
$("#containersList").html(
`<div class="ui basic segment"><i class="ui red times icon"></i> ${
dockerData.error || hostData.error
}</div>`
);
}
})
.catch((error) => {
console.log(error.responseText);
parent.msgbox("Error loading data: " + error.message, false);
}); });
});
// update ui
updateContainersList();
updateAddedContainersList();
} }
// update containers list
function updateContainersList() {
$containersList.empty();
Object.entries(lines).forEach(([key, line]) => {
$containersList.append(`
<div class="item">
<div class="right floated content">
<div class="ui button add-button" data-key="${key}">Add</div>
</div>
<div class="content">
<div class="header">${line.name}</div>
<div class="description">${line.ip}:${line.port}</div>
</div>
</div>
`);
});
$containersList.find(".loader").removeClass("active");
}
// update the added containers list
function updateAddedContainersList() {
Object.entries(linesAdded).forEach(([key, line]) => {
$containersAddedList.append(`
<div class="item">
<div class="content">
<div class="header">${line.name}</div>
<div class="description">${line.ip}:${line.port}</div>
</div>
</div>
`);
});
if (Object.keys(linesAdded).length) {
$containersAddedListHeader.removeAttr("hidden");
}
}
// show error message
function showError(error) {
$containersList.html(
`<div class="ui basic segment"><i class="ui red times icon"></i> ${error}</div>`
);
parent.msgbox(`Error loading data: ${error}`, false);
}
//
// event listeners
//
$showUnexposed.on("change", () => {
saveShowUnexposedState(); // save the new state to local storage
getDockerContainers(); getDockerContainers();
});
function addContainerItem(item) { $searchbar.on(
if (lines[item]) { "input",
parent.addContainerItem(lines[item]); debounce(() => {
} // debounce searchbar input with 300ms delay, then filter list
// this prevents excessive calls to the filter function
const search = $searchbar.val().toLowerCase();
$("#containersList .item").each((index, item) => {
const content = $(item).text().toLowerCase();
$(item).toggle(content.includes(search));
});
}, 300)
);
$containersList.on("click", ".add-button", (event) => {
const key = $(event.currentTarget).data("key");
if (lines[key]) {
parent.addContainerItem(lines[key]);
} }
});
//
// initial calls
//
// load state of showUnexposed checkbox
loadShowUnexposedState();
// initial load of docker containers
getDockerContainers();
});
</script> </script>
</body> </body>
</html> </html>