refactor: docker container list

This commit is contained in:
Tim Dreyer 2025-04-24 22:32:58 +02:00
parent 72b100aab0
commit 4dc7175588
4 changed files with 294 additions and 77 deletions

View File

@ -72,6 +72,8 @@ ENV WEBROOT="./www"
VOLUME [ "/opt/zoraxy/config/" ] VOLUME [ "/opt/zoraxy/config/" ]
LABEL com.imuslab.zoraxy.container-identifier="Zoraxy"
ENTRYPOINT [ "/opt/zoraxy/entrypoint.sh" ] ENTRYPOINT [ "/opt/zoraxy/entrypoint.sh" ]
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 CMD nc -vz 127.0.0.1 $PORT || exit 1 HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 CMD nc -vz 127.0.0.1 $PORT || exit 1

View File

@ -19,6 +19,7 @@ Once setup, access the webui at `http://<host-ip>:8000` to configure Zoraxy. Cha
docker run -d \ docker run -d \
--name zoraxy \ --name zoraxy \
--restart unless-stopped \ --restart unless-stopped \
--add-host=host.docker.internal:host-gateway \
-p 80:80 \ -p 80:80 \
-p 443:443 \ -p 443:443 \
-p 8000:8000 \ -p 8000:8000 \
@ -47,6 +48,8 @@ services:
- /path/to/zoraxy/plugin/:/opt/zoraxy/plugin/ - /path/to/zoraxy/plugin/:/opt/zoraxy/plugin/
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime - /etc/localtime:/etc/localtime
extra_hosts:
- "host.docker.internal:host-gateway"
environment: environment:
FASTGEOIP: "true" FASTGEOIP: "true"
``` ```
@ -68,6 +71,11 @@ services:
| `/var/run/docker.sock` | Docker socket. Used for additional functionality with Zoraxy. | | `/var/run/docker.sock` | Docker socket. Used for additional functionality with Zoraxy. |
| `/etc/localtime` | Localtime. Set to ensure the host and container are synchronized. | | `/etc/localtime` | Localtime. Set to ensure the host and container are synchronized. |
### Extra Hosts
| Host | Details |
|:-|:-|
| `host.docker.internal:host-gateway` | Resolves host.docker.internal to the hosts gateway IP on the Docker bridge network, allowing containers to access services running on the host machine. |
### Environment ### Environment
Variables are the same as those in [Start Parameters](https://github.com/tobychui/zoraxy?tab=readme-ov-file#start-paramters). Variables are the same as those in [Start Parameters](https://github.com/tobychui/zoraxy?tab=readme-ov-file#start-paramters).

View File

@ -12,5 +12,7 @@ services:
- /path/to/zoraxy/plugin/:/opt/zoraxy/plugin/ - /path/to/zoraxy/plugin/:/opt/zoraxy/plugin/
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime - /etc/localtime:/etc/localtime
extra_hosts:
- "host.docker.internal:host-gateway"
environment: environment:
FASTGEOIP: "true" FASTGEOIP: "true"

View File

@ -1,14 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<!-- Notes: This should be open in its original path--> <!-- Notes: This should be open in its original path -->
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" href="../script/semantic/semantic.min.css" /> <link rel="stylesheet" href="../script/semantic/semantic.min.css" />
<script src="../script/jquery-3.6.0.min.js"></script> <script src="../script/jquery-3.6.0.min.js"></script>
<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"> <link rel="stylesheet" href="../darktheme.css" />
<script src="../script/darktheme.js"></script> <script src="../script/darktheme.js"></script>
<br /> <br />
<div class="ui container"> <div class="ui container">
@ -26,35 +26,54 @@
<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 />
<small
>Please make sure Zoraxy and the target container share a
network</small
>
</label> </label>
</div> </div>
</div> </div>
</div> </div>
<!-- Networked Containers Lists -->
<div class="ui header"> <div class="ui header">
<div class="content"> <div class="content">
List of Docker Containers Networked Containers
<div class="sub header"> <div class="sub header">
Below is a list of all detected Docker containers currently running Containers sharing a network with Zoraxy.<br />
on the system. Docker DNS based name resolution has to be supported by your
network.
</div> </div>
</div> </div>
</div> </div>
<div id="containersList" class="ui middle aligned divided list active"> <div id="networkedList" class="ui middle aligned divided list">
<div class="ui loader active"></div> <div class="ui active loader"></div>
</div> </div>
<!-- Host Mode Containers List -->
<div id="hostmodeListHeader" class="ui header" hidden>
<div class="content">
Host Mode Containers
<div class="sub header">
Containers using the host network.<br />Ports need to be added
manually.
</div>
</div>
</div>
<div id="hostmodeList" class="ui middle aligned divided list"></div>
<div class="ui horizontal divider"></div> <div class="ui horizontal divider"></div>
<div id="containersAddedListHeader" class="ui header" hidden> <!-- Other Containers List -->
Already added containers: <div id="othersListHeader" class="ui header" hidden>
<div class="content">
Other Containers
<div class="sub header">
Containers that do not share a network with Zoraxy.<br />
Manual addition is required.
</div>
</div>
</div> </div>
<div <div id="othersList" class="ui middle aligned divided list"></div>
id="containersAddedList" <div class="ui horizontal divider"></div>
class="ui middle aligned divided list" <!-- Existing List -->
></div> <div id="existingListHeader" class="ui header" hidden>
Existing Rules
<div class="sub header">Containers already in proxy rules.</div>
</div>
<div id="existingList" class="ui middle aligned divided list"></div>
</div> </div>
<script> <script>
@ -69,14 +88,26 @@
// wait until DOM is fully loaded before executing script // wait until DOM is fully loaded before executing script
$(document).ready(() => { $(document).ready(() => {
const $containersList = $("#containersList"); // jQuery objects for UI elements
const $containersAddedList = $("#containersAddedList"); const $networkedList = $("#networkedList");
const $containersAddedListHeader = $("#containersAddedListHeader");
const $hostmodeListHeader = $("#hostmodeListHeader");
const $hostmodeList = $("#hostmodeList");
const $othersListHeader = $("#othersListHeader");
const $othersList = $("#othersList");
const $existingListHeader = $("#existingListHeader");
const $existingList = $("#existingList");
const $searchbar = $("#searchbar"); const $searchbar = $("#searchbar");
const $showUnexposed = $("#showUnexposed"); const $showUnexposed = $("#showUnexposed");
let lines = {}; // objects to store container entries
let linesAdded = {}; let networkedEntries = {}; // { name, ip, port }
let hostmodeEntries = {}; // { name, ip }
let othersEntries = {}; // { name, ips, ports }
let existingEntries = {}; // { name, ip, port }
// load showUnexposed checkbox state from local storage // load showUnexposed checkbox state from local storage
function loadShowUnexposedState() { function loadShowUnexposedState() {
@ -93,12 +124,21 @@
// fetch docker containers // fetch docker containers
function getDockerContainers() { function getDockerContainers() {
$containersList.html('<div class="ui loader active"></div>'); $networkedList.html('<div class="ui active loader"></div>');
$containersAddedList.empty();
$containersAddedListHeader.attr("hidden", true);
lines = {}; $hostmodeListHeader.attr("hidden", true);
linesAdded = {}; $hostmodeList.empty();
$othersListHeader.attr("hidden", true);
$othersList.empty();
$existingListHeader.attr("hidden", true);
$existingList.empty();
networkedEntries = {};
hostmodeEntries = {};
othersEntries = {};
existingEntries = {};
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");
@ -126,73 +166,215 @@
) )
); );
// identify the Zoraxy container to determine shared networks
const zoraxyContainer = containers.find(
(container) =>
container.Labels &&
container.Labels["com.imuslab.zoraxy.container-identifier"] ===
"Zoraxy"
);
const zoraxyNetworkIDs = zoraxyContainer
? Object.values(zoraxyContainer.NetworkSettings.Networks).map(
(network) => network.NetworkID
)
: [];
// process each container
containers.forEach((container) => { containers.forEach((container) => {
// skip containers in network mode "none"
if (container.HostConfig.NetworkMode === "none") {
return;
}
const name = container.Names[0].replace(/^\//, ""); const name = container.Names[0].replace(/^\//, "");
// host mode containers resolve to host.docker.internal
if (
container.HostConfig.NetworkMode === "host" &&
!hostmodeEntries[name]
) {
hostmodeEntries[name] = {
name,
ip: "host.docker.internal",
};
return;
}
// check if the container shares a network with Zoraxy
const sharedNetworks = Object.values(
container.NetworkSettings.Networks
).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) {
const ips = Object.values(container.NetworkSettings.Networks).map(
(network) => network.IPAddress
);
const ports = container.Ports.map((portObject) => {
return portObject.PublicPort || portObject.PrivatePort;
});
othersEntries[name] = {
name,
ips,
ports,
};
return;
}
// add the container to the networked list, using it's name as address
container.Ports.forEach((portObject) => { container.Ports.forEach((portObject) => {
let port = portObject.PublicPort || portObject.PrivatePort; // skip unexposed ports if the checkbox is not checked
if (!portObject.PublicPort && !$showUnexposed.is(":checked")) if (!portObject.PublicPort && !$showUnexposed.is(":checked"))
return; return;
// if port is not exposed, use container's name and let docker handle the routing const port = portObject.PublicPort || portObject.PrivatePort;
// BUT this will only work if the container is on the same network as Zoraxy
const targetAddress = portObject.IP || name;
const key = `${name}-${port}`;
const key = `${name}:${port}`;
if ( if (
existingTargets.has(`${targetAddress}:${port}`) && existingTargets.has(`${name}:${port}`) &&
!linesAdded[key] !existingEntries[key]
) { ) {
linesAdded[key] = { name, ip: targetAddress, port }; existingEntries[key] = {
} else if (!lines[key]) { name,
lines[key] = { name, ip: targetAddress, port }; ip: name,
port,
};
} else if (!networkedEntries[key]) {
networkedEntries[key] = {
name,
ip: name,
port,
};
} }
}); });
}); });
// update ui // update UI lists
updateContainersList(); updateNetworkedList();
updateAddedContainersList(); updateHostmodeList();
updateOthersList();
updateExistingList();
} }
// update containers list // update networked list
function updateContainersList() { function updateNetworkedList() {
$containersList.empty(); $networkedList.empty();
Object.entries(lines).forEach(([key, line]) => { let html = "";
$containersList.append(` Object.entries(networkedEntries)
<div class="item"> .sort()
<div class="right floated content"> .forEach(([key, entry]) => {
<div class="ui button add-button" data-key="${key}">Add</div> html += `
<div class="item">
<div class="content" style="display: flex; justify-content: space-between;">
<div>
<div class="header">${entry.name}</div>
<div class="description">
<p>${entry.ip}:${entry.port}</p>
</div>
</div>
<button class="ui button add-button" data-key="${key}">
Add
</button>
</div>
</div> </div>
<div class="content"> `;
<div class="header">${line.name}</div> });
<div class="description">${line.ip}:${line.port}</div> $networkedList.append(html);
</div>
</div>
`);
});
$containersList.find(".loader").removeClass("active");
} }
// update the added containers list // update hostmode list
function updateAddedContainersList() { function updateHostmodeList() {
Object.entries(linesAdded).forEach(([key, line]) => { $hostmodeList.empty();
$containersAddedList.append(` let html = "";
<div class="item"> Object.entries(hostmodeEntries)
<div class="content"> .sort()
<div class="header">${line.name}</div> .forEach(([key, entry]) => {
<div class="description">${line.ip}:${line.port}</div> html += `
<div class="item">
<div class="content" style="display: flex; justify-content: space-between;">
<div>
<div class="header">${entry.name}</div>
<div class="description">
<p>${entry.ip}</p>
</div>
</div>
<button class="ui right floated button add-button" data-key="${key}">
Add
</button>
</div>
</div> </div>
</div> `;
`); });
}); $hostmodeList.append(html);
if (Object.keys(linesAdded).length) { if (Object.keys(hostmodeEntries).length) {
$containersAddedListHeader.removeAttr("hidden"); $hostmodeListHeader.removeAttr("hidden");
}
}
// update others list
function updateOthersList() {
$othersList.empty();
let html = "";
Object.entries(othersEntries)
.sort()
.forEach(([key, entry]) => {
html += `
<div class="item">
<div class="header">${entry.name}</div>
${
entry.ips.length === 0 ||
entry.ips.every((ip) => ip === "") ||
entry.ports.length === 0 ||
entry.ports.every((port) => port === "")
? `<div class="description">
<p>No IPs or Ports</p>
</div>`
: `<div class="description">
<p>
IPs: ${entry.ips.join(", ")}<br />
Ports: ${entry.ports.join(", ")}
</p>
</div>`
}
</div>
`;
});
$othersList.append(html);
if (Object.keys(othersEntries).length) {
$othersListHeader.removeAttr("hidden");
}
}
// update existing list
function updateExistingList() {
$existingList.empty();
let html = "";
Object.entries(existingEntries)
.sort()
.forEach(([key, entry]) => {
html += `
<div class="item">
<div class="content">
<div class="header">${entry.name}</div>
<div class="description">
<p>${entry.ip}:${entry.port}</p>
</div>
</div>
</div>
`;
});
$existingList.append(html);
if (Object.keys(existingEntries).length) {
$existingListHeader.removeAttr("hidden");
} }
} }
// show error message // show error message
function showError(error) { function showError(error) {
$containersList.html( $networkedList.html(
`<div class="ui basic segment"><i class="ui red times icon"></i> ${error}</div>` `<div class="ui basic segment"><i class="ui red times icon"></i> ${error}</div>`
); );
parent.msgbox(`Error loading data: ${error}`, false); parent.msgbox(`Error loading data: ${error}`, false);
@ -207,23 +389,46 @@
getDockerContainers(); getDockerContainers();
}); });
// debounce searchbar input with 300ms delay, then filter list
// this prevents excessive calls to the filter function
$searchbar.on( $searchbar.on(
"input", "input",
debounce(() => { debounce(() => {
// debounce searchbar input with 300ms delay, then filter list
// this prevents excessive calls to the filter function
const search = $searchbar.val().toLowerCase(); const search = $searchbar.val().toLowerCase();
$("#containersList .item").each((index, item) => {
$("#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(); const content = $(item).text().toLowerCase();
$(item).toggle(content.includes(search)); $(item).toggle(content.includes(search));
}); });
}, 300) }, 300)
); );
$containersList.on("click", ".add-button", (event) => { $networkedList.on("click", ".add-button", (event) => {
const key = $(event.currentTarget).data("key"); const key = $(event.currentTarget).data("key");
if (lines[key]) { if (networkedEntries[key]) {
parent.addContainerItem(lines[key]); parent.addContainerItem(networkedEntries[key]);
}
});
$hostmodeList.on("click", ".add-button", (event) => {
const key = $(event.currentTarget).data("key");
if (hostmodeEntries[key]) {
parent.addContainerItem(hostmodeEntries[key]);
} }
}); });