|
|
|
@@ -1,14 +1,14 @@
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
|
|
|
|
<!-- Notes: This should be open in its original path-->
|
|
|
|
|
<!-- Notes: This should be open in its original path -->
|
|
|
|
|
<meta charset="utf-8" />
|
|
|
|
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css" />
|
|
|
|
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
|
|
|
|
<script src="../script/semantic/semantic.min.js"></script>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<link rel="stylesheet" href="../darktheme.css">
|
|
|
|
|
<link rel="stylesheet" href="../darktheme.css" />
|
|
|
|
|
<script src="../script/darktheme.js"></script>
|
|
|
|
|
<br />
|
|
|
|
|
<div class="ui container">
|
|
|
|
@@ -17,48 +17,428 @@
|
|
|
|
|
<input
|
|
|
|
|
id="searchbar"
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="Search..."
|
|
|
|
|
placeholder="Search Containers ..."
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="field">
|
|
|
|
|
<div class="ui checkbox">
|
|
|
|
|
<input type="checkbox" id="showOnlyRunning" class="hidden" />
|
|
|
|
|
<label for="showOnlyRunning">Show Only Running Containers</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="field">
|
|
|
|
|
<div class="ui checkbox">
|
|
|
|
|
<input type="checkbox" id="showUnexposed" class="hidden" />
|
|
|
|
|
<label for="showUnexposed"
|
|
|
|
|
>Show Containers with unexposed ports
|
|
|
|
|
<br />
|
|
|
|
|
<small
|
|
|
|
|
>Please make sure Zoraxy and the target container share a
|
|
|
|
|
network</small
|
|
|
|
|
>
|
|
|
|
|
</label>
|
|
|
|
|
>Show Containers with Unexposed Ports</label
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- Networked Containers Lists -->
|
|
|
|
|
<div class="ui header">
|
|
|
|
|
<div class="content">
|
|
|
|
|
List of Docker Containers
|
|
|
|
|
Containers on Zoraxy's Networks
|
|
|
|
|
<div class="sub header">
|
|
|
|
|
Below is a list of all detected Docker containers currently running
|
|
|
|
|
on the system.
|
|
|
|
|
These containers share a network with Zoraxy.<br />
|
|
|
|
|
Your networks must support Docker DNS-based name resolution.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="containersList" class="ui middle aligned divided list active">
|
|
|
|
|
<div class="ui loader active"></div>
|
|
|
|
|
<div id="networkedList" class="ui middle aligned divided list">
|
|
|
|
|
<div class="ui active loader"></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="ui horizontal divider"></div>
|
|
|
|
|
<div id="containersAddedListHeader" class="ui header" hidden>
|
|
|
|
|
Already added containers:
|
|
|
|
|
<!-- Host Mode Containers List -->
|
|
|
|
|
<div id="hostmodeListHeader" class="ui header" hidden>
|
|
|
|
|
<div class="content">
|
|
|
|
|
Containers using Host Network
|
|
|
|
|
<div class="sub header">
|
|
|
|
|
These containers use the host network configuration.<br />
|
|
|
|
|
Ports must be manually configured.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
id="containersAddedList"
|
|
|
|
|
class="ui middle aligned divided list"
|
|
|
|
|
></div>
|
|
|
|
|
<div id="hostmodeList" class="ui middle aligned divided list"></div>
|
|
|
|
|
<div class="ui horizontal divider"></div>
|
|
|
|
|
<!-- Other Containers List -->
|
|
|
|
|
<div id="othersListHeader" class="ui header" hidden>
|
|
|
|
|
<div class="content">
|
|
|
|
|
Containers on different Networks
|
|
|
|
|
<div class="sub header">
|
|
|
|
|
These containers are not connected to Zoraxy's networks.<br />
|
|
|
|
|
Manual configuration is required.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="othersList" class="ui middle aligned divided list"></div>
|
|
|
|
|
<div class="ui horizontal divider"></div>
|
|
|
|
|
<!-- Existing List -->
|
|
|
|
|
<div id="existingListHeader" class="ui header" hidden>
|
|
|
|
|
Containers with existing Proxy Rules
|
|
|
|
|
<div class="sub header">
|
|
|
|
|
These containers are already configured in the proxy rules.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="existingList" class="ui middle aligned divided list"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
// debounce function to prevent excessive calls to a function
|
|
|
|
|
// DOM elements
|
|
|
|
|
const $networkedList = $("#networkedList");
|
|
|
|
|
|
|
|
|
|
const $hostmodeListHeader = $("#hostmodeListHeader");
|
|
|
|
|
const $hostmodeList = $("#hostmodeList");
|
|
|
|
|
|
|
|
|
|
const $othersListHeader = $("#othersListHeader");
|
|
|
|
|
const $othersList = $("#othersList");
|
|
|
|
|
|
|
|
|
|
const $existingListHeader = $("#existingListHeader");
|
|
|
|
|
const $existingList = $("#existingList");
|
|
|
|
|
|
|
|
|
|
const $searchbar = $("#searchbar");
|
|
|
|
|
const $showOnlyRunning = $("#showOnlyRunning");
|
|
|
|
|
const $showUnexposed = $("#showUnexposed");
|
|
|
|
|
|
|
|
|
|
// maps for containers
|
|
|
|
|
let networkedEntries = {};
|
|
|
|
|
let hostmodeEntries = {};
|
|
|
|
|
let othersEntries = {};
|
|
|
|
|
let existingEntries = {};
|
|
|
|
|
|
|
|
|
|
// initial load
|
|
|
|
|
$(document).ready(() => {
|
|
|
|
|
loadCheckboxState("showUnexposed", $showUnexposed);
|
|
|
|
|
loadCheckboxState("showOnlyRunning", $showOnlyRunning);
|
|
|
|
|
initializeEventListeners();
|
|
|
|
|
getDockerContainers();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// event listeners
|
|
|
|
|
function initializeEventListeners() {
|
|
|
|
|
$showUnexposed.on("change", () => {
|
|
|
|
|
saveCheckboxState("showUnexposed", $showUnexposed);
|
|
|
|
|
getDockerContainers();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$showOnlyRunning.on("change", () => {
|
|
|
|
|
saveCheckboxState("showOnlyRunning", $showOnlyRunning);
|
|
|
|
|
getDockerContainers();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// debounce searchbar input to prevent excessive filtering
|
|
|
|
|
$searchbar.on(
|
|
|
|
|
"input",
|
|
|
|
|
debounce(() => filterLists($searchbar.val().toLowerCase()), 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]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// filter lists by toggling item visibility
|
|
|
|
|
function filterLists(searchTerm) {
|
|
|
|
|
$(".list .item").each((_, item) => {
|
|
|
|
|
const content = $(item).text().toLowerCase();
|
|
|
|
|
$(item).toggle(content.includes(searchTerm));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reset UI and state
|
|
|
|
|
function reset() {
|
|
|
|
|
networkedEntries = {};
|
|
|
|
|
hostmodeEntries = {};
|
|
|
|
|
othersEntries = {};
|
|
|
|
|
existingEntries = {};
|
|
|
|
|
|
|
|
|
|
$networkedList.empty();
|
|
|
|
|
$hostmodeList.empty();
|
|
|
|
|
$othersList.empty();
|
|
|
|
|
$existingList.empty();
|
|
|
|
|
|
|
|
|
|
$hostmodeListHeader.attr("hidden", true);
|
|
|
|
|
$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) {
|
|
|
|
|
processDockerData(hostData, dockerData);
|
|
|
|
|
} else {
|
|
|
|
|
showError(hostData.error || dockerData.error);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
parent.msgbox("Error loading data: " + error.message, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function processDockerData(hostData, dockerData) {
|
|
|
|
|
const { containers } = dockerData;
|
|
|
|
|
const existingTargets = new Set(
|
|
|
|
|
hostData.flatMap(({ ActiveOrigins }) =>
|
|
|
|
|
ActiveOrigins.map(({ OriginIpOrDomain }) => OriginIpOrDomain)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
)
|
|
|
|
|
: [];
|
|
|
|
|
|
|
|
|
|
// iterate over all containers
|
|
|
|
|
containers.forEach((container) => {
|
|
|
|
|
// skip containers in network mode "none"
|
|
|
|
|
if (container.HostConfig.NetworkMode === "none") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// skip containers not running, if the option is enabled
|
|
|
|
|
if (
|
|
|
|
|
container.State !== "running" &&
|
|
|
|
|
$showOnlyRunning.prop("checked")
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sanitize container name
|
|
|
|
|
const containerName = container.Names[0].replace(/^\//, "");
|
|
|
|
|
|
|
|
|
|
// containers in network mode "host" should resolve to "host.docker.internal"
|
|
|
|
|
if (
|
|
|
|
|
container.HostConfig.NetworkMode === "host" &&
|
|
|
|
|
!hostmodeEntries[container.Id]
|
|
|
|
|
) {
|
|
|
|
|
hostmodeEntries[container.Id] = {
|
|
|
|
|
name: containerName,
|
|
|
|
|
ip: "host.docker.internal",
|
|
|
|
|
};
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// networks that are shared with Zoraxy
|
|
|
|
|
const sharedNetworks = Object.values(
|
|
|
|
|
container.NetworkSettings.Networks
|
|
|
|
|
).filter((network) => zoraxyNetworkIDs.includes(network.NetworkID));
|
|
|
|
|
|
|
|
|
|
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[container.Id] = {
|
|
|
|
|
name: containerName,
|
|
|
|
|
ips,
|
|
|
|
|
ports,
|
|
|
|
|
};
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add the container to the networked list, using it's name as address
|
|
|
|
|
container.Ports.forEach((portObject) => {
|
|
|
|
|
const port = portObject.PublicPort || portObject.PrivatePort;
|
|
|
|
|
const key = `${containerName}:${port}`;
|
|
|
|
|
|
|
|
|
|
// always include existing proxy-rule targets
|
|
|
|
|
if (existingTargets.has(key)) {
|
|
|
|
|
if (!existingEntries[key]) {
|
|
|
|
|
existingEntries[key] = {
|
|
|
|
|
name: containerName,
|
|
|
|
|
ip: containerName,
|
|
|
|
|
port,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// otherwise, include only if exposed or checkbox is checked
|
|
|
|
|
else if (portObject.PublicPort || $showUnexposed.is(":checked")) {
|
|
|
|
|
if (!networkedEntries[key]) {
|
|
|
|
|
networkedEntries[key] = {
|
|
|
|
|
name: containerName,
|
|
|
|
|
ip: containerName,
|
|
|
|
|
port,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// finally update the UI
|
|
|
|
|
updateNetworkedList();
|
|
|
|
|
updateHostmodeList();
|
|
|
|
|
updateOthersList();
|
|
|
|
|
updateExistingList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update networked list
|
|
|
|
|
function updateNetworkedList() {
|
|
|
|
|
$networkedList.empty();
|
|
|
|
|
let html = "";
|
|
|
|
|
Object.entries(networkedEntries)
|
|
|
|
|
.sort()
|
|
|
|
|
.forEach(([key, entry]) => {
|
|
|
|
|
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>
|
|
|
|
|
`;
|
|
|
|
|
});
|
|
|
|
|
$networkedList.append(html);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update hostmode list
|
|
|
|
|
function updateHostmodeList() {
|
|
|
|
|
$hostmodeList.empty();
|
|
|
|
|
let html = "";
|
|
|
|
|
Object.entries(hostmodeEntries)
|
|
|
|
|
.sort()
|
|
|
|
|
.forEach(([key, entry]) => {
|
|
|
|
|
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>
|
|
|
|
|
`;
|
|
|
|
|
});
|
|
|
|
|
$hostmodeList.append(html);
|
|
|
|
|
if (Object.keys(hostmodeEntries).length) {
|
|
|
|
|
$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 rules 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
|
|
|
|
|
function showError(error) {
|
|
|
|
|
$networkedList.html(
|
|
|
|
|
`<div class="ui basic segment"><i class="ui red times icon"></i> ${error}</div>`
|
|
|
|
|
);
|
|
|
|
|
parent.msgbox(`Error loading data: ${error}`, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// utils
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
// local storage handling
|
|
|
|
|
function loadCheckboxState(id, $elem) {
|
|
|
|
|
const state = localStorage.getItem(id);
|
|
|
|
|
if (state !== null) {
|
|
|
|
|
$elem.prop("checked", state === "true");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function saveCheckboxState(id, $elem) {
|
|
|
|
|
localStorage.setItem(id, $elem.prop("checked"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// debounce function
|
|
|
|
|
function debounce(func, delay) {
|
|
|
|
|
let timeout;
|
|
|
|
|
return (...args) => {
|
|
|
|
@@ -66,177 +446,6 @@
|
|
|
|
|
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 linesAdded = {};
|
|
|
|
|
|
|
|
|
|
// load showUnexposed checkbox state from local storage
|
|
|
|
|
function loadShowUnexposedState() {
|
|
|
|
|
const storedState = localStorage.getItem("showUnexposed");
|
|
|
|
|
if (storedState !== null) {
|
|
|
|
|
$showUnexposed.prop("checked", storedState === "true");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// save showUnexposed checkbox state to local storage
|
|
|
|
|
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 = {};
|
|
|
|
|
linesAdded = {};
|
|
|
|
|
|
|
|
|
|
const hostRequest = $.get("/api/proxy/list?type=host");
|
|
|
|
|
const dockerRequest = $.get("/api/docker/containers");
|
|
|
|
|
|
|
|
|
|
Promise.all([hostRequest, dockerRequest])
|
|
|
|
|
.then(([hostData, dockerData]) => {
|
|
|
|
|
if (!hostData.error && !dockerData.error) {
|
|
|
|
|
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(
|
|
|
|
|
hostData.flatMap(({ ActiveOrigins }) =>
|
|
|
|
|
ActiveOrigins.map(({ OriginIpOrDomain }) => OriginIpOrDomain)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
containers.forEach((container) => {
|
|
|
|
|
const name = container.Names[0].replace(/^\//, "");
|
|
|
|
|
container.Ports.forEach((portObject) => {
|
|
|
|
|
let port = portObject.PublicPort || portObject.PrivatePort;
|
|
|
|
|
if (!portObject.PublicPort && !$showUnexposed.is(":checked"))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
const targetAddress = portObject.IP || name;
|
|
|
|
|
const key = `${name}-${port}`;
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
existingTargets.has(`${targetAddress}:${port}`) &&
|
|
|
|
|
!linesAdded[key]
|
|
|
|
|
) {
|
|
|
|
|
linesAdded[key] = { name, ip: targetAddress, port };
|
|
|
|
|
} else if (!lines[key]) {
|
|
|
|
|
lines[key] = { name, ip: targetAddress, port };
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$searchbar.on(
|
|
|
|
|
"input",
|
|
|
|
|
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>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|