Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions plugins/check_port/action.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,21 @@ function get_public_ip($version, $timeout) {
if (filter_var($ip, FILTER_VALIDATE_IP, $flag)) {
return $ip; // Return the valid IP
}
error_log("check_port plugin: {$url} returned invalid IP: " . $ip);
// If looking for IPv6 and received a valid IPv4, it indicates no IPv6 is available (e.g. NAT64/DNS64).
// If looking for IPv4 and received a valid IPv6, it indicates no IPv4 is available.
// These are not error conditions, so we can return null without logging.
$isIPv6RequestWithIPv4Response = ($version == '6' && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4));
$isIPv4RequestWithIPv6Response = ($version == '4' && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));

if (!$isIPv6RequestWithIPv4Response && !$isIPv4RequestWithIPv6Response) {
error_log("check_port plugin: {$url} returned invalid IP: " . $ip);
}
} else {
error_log("check_port plugin: Failed to fetch from {$url}. Status: {$snoopy->status}, Error: {$snoopy->error}");
// cURL error 7 ("Failed to connect to host") indicates a lack of connectivity for the requested IP version.
// This is not a plugin error, so we suppress the log for both IPv4 and IPv6 checks.
if ($snoopy->status != 7) {
error_log("check_port plugin: Failed to fetch from {$url}. Status: {$snoopy->status}, Error: {$snoopy->error}");
}
}
return null; // Return null on failure
}
Expand Down Expand Up @@ -72,6 +84,7 @@ function check_port_yougetsignal($ip, $port, $timeout) {
if (stripos($client->results, "is open") !== false) return 2; // Port is open
error_log("check_port: yougetsignal response indicators not found for IP {$ip}. Response: " . substr($client->results, 0, 500));
} else {
// This log will be triggered if the service is offline or unreachable.
error_log("check_port: Failed fetch from yougetsignal for IP {$ip}. Status: {$client->status}, Error: {$client->error}");
}
return 0;
Expand Down Expand Up @@ -122,6 +135,7 @@ function check_port_portchecker($ip, $port, $timeout) {
if (stripos($client->results, 'is <span class="green">open</span>') !== false) return 2; // Port is open
error_log("check_port: portchecker response indicators not found for IP {$ip}. Response: " . substr($client->results, 0, 500));
} else {
// This log will be triggered if the service is offline or unreachable.
error_log("check_port: Failed fetch from portchecker endpoint for IP {$ip}. Status: {$client->status}, Error: {$client->error}");
}
return 0; // Status is unknown
Expand Down Expand Up @@ -154,6 +168,10 @@ function get_and_check_ip($ip_version, $use_website, $rtorrent_ip, $rtorrent_por
// Call the appropriate checking function based on the selected service
if ($use_website == "yougetsignal") {
$status = check_port_yougetsignal($ip_to_check, $rtorrent_port, $timeout);
// If yougetsignal fails, fall back to portchecker for IPv4
if ($status === 0 && $ip_version == '4') {
$status = check_port_portchecker($ip_to_check, $rtorrent_port, $timeout);
}
} elseif ($use_website == "portchecker") {
$status = check_port_portchecker($ip_to_check, $rtorrent_port, $timeout);
}
Expand All @@ -174,6 +192,10 @@ function get_and_check_ip($ip_version, $use_website, $rtorrent_ip, $rtorrent_por
"ipv6" => "-", "ipv6_port" => (int)$port, "ipv6_status" => 0,
];

// Add enabled flags to the response
$response['use_ipv4'] = ($currentUseWebsiteIPv4 !== false);
$response['use_ipv6'] = ($currentUseWebsiteIPv6 !== false);

// Perform the IPv4 check if it's enabled in conf.php
if ($currentUseWebsiteIPv4 !== false) {
$ipv4_result = get_and_check_ip('4', $currentUseWebsiteIPv4, $ip_glob, $port, $currentCheckPortTimeout);
Expand Down
68 changes: 50 additions & 18 deletions plugins/check_port/check_port.css
Original file line number Diff line number Diff line change
@@ -1,35 +1,67 @@
/*
/**
* check_port Plugin Stylesheet
*
* Defines the icons and layout for the port status indicator in the status bar.
*/

/* Define CSS variables for the different status icons. */
/* Using data URIs for the icons avoids extra HTTP requests. */
/* 1. Icon Definitions */
#StatusBar {
/* Icon for when the port status is unknown or is being checked. */
--pane-port-unknown-icon: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAA7ElEQVR4AYyRtUEAQRBFzw2HiDbogYzLKIROKIFKSJEYpwDc3Yd9wUfOgz9r88bWM7MmxetLniEzKypvs6xsEvTnIQP4+ri3j4cDwV5FGSZymnRiHzilAr/eTtrAAjP152IcR/t6sZfjZUNf75eNMKYgE5kpGSect1fmUFu5gTY+mZXt8XBRIPu/WQv5Y3KnGS54pC+B6G5nwd5vVwUyUTahMgqirxrIuVoyZpxIXBIZJ+lsswS0h/2SgBWwkg2nWsbdeaSsJrBQNiAJQNkEuoACc0+fzeSGCF+Y74E1koNxJXIuEEawEeKwdA0A1zpJYUqqdYUAAAAASUVORK5CYII=);
/* Icon for when the port is confirmed to be closed (error state). */
--pane-port-error-icon: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAVFBMVEX////////////////////////////////////////////////ORirORirdcWDlTzzpgW/qXEHtY0XtjnvxbEjyppbznIf0dk71f1j5tqH72dP///8Q8E8oAAAADXRSTlMAAgUGBwgKDRMUFRnTtFxmSQAAAHpJREFUCB0FgAEOwiAMRV8pq5LI/Q+6xDlG+43FAiZw7zIZMBlOXpzg8HkNGXrTH5z5irLetCOP1SAKIHkG+BwyspOl8t5xbYASOA0EJUQJOplC2wtUtPMKiaMBxzq7c40vPyBucNQyqsriVjYDJuHkop5mJmACe2f1P5ZxRkN1OzPZAAAAAElFTkSuQmCC);
/* Icon for when the port is confirmed to be open (success state). */
--pane-port-success-icon: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAA/ElEQVR4AWL4//8/DLMDMRuIrdMt9h8ZAwEXSBxKMwExC4ijDMSsIBqmcPGVKSgYJg7VzAGiGWAmgSR6T1f/X393GZguOZwIwjAxEBumWRSmkQ1ZU8rhEGwYXTMLTCOKpqTdPoC6ycIGgRgAgFgUl31YhCHZBB0Bd3e9Tw6nydWvXgElUsVcyD0x4z+J0Fu36QOBHIiO6JKao+qbdDofHZhJniIFRJjtp87wJpXKRQZ7F10CDcjLw/xKOF/OSvC+VI7W5SIqs1wl8XBwvEM6K791FpZImy8JMX2n8CK7bCD/JTkjb480SQPYGch/PLkYkBFIUPnjkfsJwn6EG7GmEErgVbTJAAAAAElFTkSuQmCC);
}
/* General styling for the icon element within the port status pane. */
#port-pane .icon {
background-position: center; /* Center the icon within its container. */
background-repeat: no-repeat; /* Prevent the icon from tiling. */
width: 14px; /* Explicitly set width to match the icon image. */
height: 14px; /* Explicitly set height to match the icon image. */

/* 2. Port Status Component Layout */
#port-pane.port-status-container {
display: flex;
align-items: center;
gap: 3px;
padding: 0 4px; /* Symmetrical padding */
box-sizing: border-box;
/* Crucial: Prevents content from expanding the container and breaking the layout */
overflow: hidden;
}

/* 3. Group for each IP version (icon + text) */
.port-group {
display: flex;
align-items: center;
gap: 3px; /* Space between icon and text inside the group */
overflow: hidden; /* Prevent internal content from breaking out */
min-width: 0; /* Allow the group to shrink */
}

/* 4. IP Text Behavior */
.port-status-container .port-ip-text-segment {
/* Prevents long text from wrapping and breaking the flexbox layout */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
/* This is the key fix: it allows the text element to shrink below its content size */
min-width: 0;
}
/* Assigns the 'unknown' status icon. */
#port-pane .pstatus0 {

/* 5. Icon Base and Status Styles */
.port-status-container .icon {
background-position: center;
background-repeat: no-repeat;
width: 14px;
height: 14px;
/* Required to prevent the icon from shrinking in the flex container */
flex-shrink: 0;
}

/* Port status is unknown */
.port-status-container .pstatus0 {
background-image: var(--pane-port-unknown-icon);
}
/* Assigns the 'error' (closed) status icon. */
#port-pane .pstatus1 {

/* Port is closed (error state) */
.port-status-container .pstatus1 {
background-image: var(--pane-port-error-icon);
}
/* Assigns the 'success' (open) status icon. */
#port-pane .pstatus2 {

/* Port is open (success state) */
.port-status-container .pstatus2 {
background-image: var(--pane-port-success-icon);
}
145 changes: 50 additions & 95 deletions plugins/check_port/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,14 @@ const a = {};
* @param {boolean} isUpdate - If true, indicates a manual refresh, adding "..." to the title
*/
plugin.resetStatus = function(isUpdate) {
// Reset icons to the "unknown" state (pstatus0)
a.iconIPv4.removeClass().addClass("icon port-icon-ipv4 pstatus0");
a.iconIPv6.removeClass().addClass("icon port-icon-ipv6 pstatus0");
// Hide IP address text and the separator
a.textIPv4.text("").hide();
a.separator.text("").hide();
a.textIPv6.text("").hide();

// Set a tooltip to indicate that a check is in progress
let title = theUILang.checkingPort || "Checking port status...";
if (isUpdate) {
title += "..."; // Append ellipsis for manual updates
}
a.pane.prop("title", title);
};

// Initial check when the plugin is first loaded
plugin.init = function() {
plugin.resetStatus(false);
// Request the initial port status from the backend
theWebUI.request("?action=initportcheck", [plugin.getPortStatus, plugin]);
if (a.pane) {
a.pane.prop("title", title);
}
};

// Function to manually trigger an update of the port status
Expand All @@ -40,69 +27,57 @@ plugin.update = function() {
theWebUI.request("?action=updateportcheck", [plugin.getPortStatus, plugin]);
};

/**
* Updates the UI for a specific IP protocol (IPv4 or IPv6) based on data from the backend
* @param {object} data - The response data containing status for both protocols
* @param {string} proto - The protocol to update, either "ipv4" or "ipv6"
* @param {function} getStatusText - A function to retrieve the localized status string
* @returns {string} The formatted title line for this protocol's status
*/
function updateProtocolStatus(data, proto, getStatusText) {
const status = parseInt(data[proto + '_status']);
const address = data[proto];
const port = data[proto + '_port'];
const isAvailable = address && address !== "-"; // Check if an IP address was returned

// Select the correct UI elements for the given protocol
const icon = (proto === 'ipv4') ? a.iconIPv4 : a.iconIPv6;
const textEl = (proto === 'ipv4') ? a.textIPv4 : a.textIPv6;

icon.removeClass("pstatus0 pstatus1 pstatus2").addClass("pstatus" + status);

let displayText = "";
let titleText = "";

if (isAvailable) {
// Format display text as IP:PORT, with brackets for IPv6
displayText = (proto === 'ipv6') ? `[${address}]:${port}` : `${address}:${port}`;
textEl.text(displayText).show();
// Create a detailed title for the tooltip
titleText = `${proto.toUpperCase()}: ${displayText} (${getStatusText(status)})`;
} else {
// If IP is not available, hide the text element
textEl.text("").hide();
titleText = `${proto.toUpperCase()}: ${(theUILang.notAvailable || "N/A")} (${getStatusText(status)})`;
}
return titleText; // Return the generated title string
}

/**
* Main callback to process the port status response from the backend and update the UI
* @param {object} d - The JSON object received from the backend response
*/
plugin.getPortStatus = function(d) {
// Helper function to get the localized text for a status code
// Always clear the pane first to rebuild the UI dynamically
a.pane.empty();

const getStatusText = (statusCode) => theUILang.portStatus[statusCode] || theUILang.portStatus[0] || "Unknown";
const isIPv4Available = d.ipv4 && d.ipv4 !== "-";
const isIPv6Available = d.ipv6 && d.ipv6 !== "-";
const titleLines = [];

// Conditionally create and append the IPv4 group only if the IP is available
if (isIPv4Available) {
const status = parseInt(d.ipv4_status);
const displayText = `${d.ipv4}:${d.ipv4_port}`;
const ipv4Group = $("<div>").attr("id", "port-group-ipv4").addClass("port-group");
ipv4Group.append($("<div>").attr("id", "port-icon-ipv4").addClass("icon pstatus" + status));
ipv4Group.append($("<span>").attr("id", "port-ip-text-ipv4").addClass("d-none d-lg-block port-ip-text-segment").text(displayText));
a.pane.append(ipv4Group);
titleLines.push(`IPV4: ${displayText} (${getStatusText(status)})`);
} else if (d.use_ipv4) {
titleLines.push(`IPV4: ${(theUILang.notAvailable || "N/A")}`);
}

// Update the status for both IPv4 and IPv6 and collect their title lines
const titleLines = [
updateProtocolStatus(d, 'ipv4', getStatusText),
updateProtocolStatus(d, 'ipv6', getStatusText)
];

// Check if both IPv4 and IPv6 addresses are available
const ipv4Available = d.ipv4 && d.ipv4 !== "-";
const ipv6Available = d.ipv6 && d.ipv6 !== "-";

// Show a separator only if both protocols have an IP address to display
if (ipv4Available && ipv6Available) {
a.separator.text("|").show();
} else {
a.separator.text("").hide();
// Conditionally create and append the separator
if (isIPv4Available && isIPv6Available) {
a.pane.append($("<span>").attr("id", "port-ip-separator").addClass("d-none d-lg-block").text("|"));
}

// Set the combined tooltip for the entire status pane
// Conditionally create and append the IPv6 group only if the IP is available
if (isIPv6Available) {
const status = parseInt(d.ipv6_status);
const displayText = `[${d.ipv6}]:${d.ipv6_port}`;
const ipv6Group = $("<div>").attr("id", "port-group-ipv6").addClass("port-group");
ipv6Group.append($("<div>").attr("id", "port-icon-ipv6").addClass("icon pstatus" + status));
ipv6Group.append($("<span>").attr("id", "port-ip-text-ipv6").addClass("d-none d-lg-block port-ip-text-segment").text(displayText));
a.pane.append(ipv6Group);
titleLines.push(`IPV6: ${displayText} (${getStatusText(status)})`);
} else if (d.use_ipv6) {
titleLines.push(`IPV6: ${(theUILang.notAvailable || "N/A")}`);
}

// Set the final combined tooltip for the entire status pane
a.pane.prop("title", titleLines.join(" | "));

// Re-attach the context menu handler since we cleared the pane
if (plugin.canChangeMenu()) {
a.pane.off("mousedown", plugin.createPortMenu).on("mousedown", plugin.createPortMenu);
}
};

// Defines the AJAX request for the initial port check
Expand Down Expand Up @@ -131,36 +106,16 @@ plugin.createPortMenu = function(e) {
};

plugin.onLangLoaded = function() {
// Create status bar elements in a more readable way
const container = $("<div>").addClass("port-status-container");

const ipv4Icon = $("<div>").attr("id", "port-icon-ipv4").addClass("icon");
const ipv4Text = $("<span>").attr("id", "port-ip-text-ipv4").addClass("d-none d-lg-block port-ip-text-segment");
const separator = $("<span>").attr("id", "port-ip-separator").addClass("d-none d-lg-block").css({"margin-left": "3px", "margin-right": "3px"});
const ipv6Icon = $("<div>").attr("id", "port-icon-ipv6").addClass("icon");
const ipv6Text = $("<span>").attr("id", "port-ip-text-ipv6").addClass("d-none d-lg-block port-ip-text-segment");

// Assemble the elements into the container
container.append(ipv4Icon, ipv4Text, separator, ipv6Icon, ipv6Text);
// Create a temporary loading state immediately
const container = $("<div>").addClass("port-status-container")
.append($("<div>").addClass("icon pstatus0")); // Add a single "unknown" icon as a placeholder

// Add the newly created pane to the ruTorrent status bar
plugin.addPaneToStatusbar("port-pane", container, -1, true);

// Now that the pane is in the DOM, cache all the jQuery elements for future use
a.pane = $("#port-pane");
a.iconIPv4 = $("#port-icon-ipv4");
a.textIPv4 = $("#port-ip-text-ipv4");
a.separator = $("#port-ip-separator");
a.iconIPv6 = $("#port-icon-ipv6");
a.textIPv6 = $("#port-ip-text-ipv6");
a.pane.prop("title", theUILang.checkingPort || "Checking port status...");

// If the user has permissions, attach the right-click context menu
if (plugin.canChangeMenu()) {
a.pane.on("mousedown", plugin.createPortMenu);
}

// Trigger the initial port check
plugin.init();
// Trigger the initial port check to get the configuration and build the final UI
theWebUI.request("?action=initportcheck", [plugin.getPortStatus, plugin]);
};

// This function is called when the plugin is removed/unloaded
Expand Down