Initial commit: Tailscale Funnel plugin for OctoPrint with build documentation
This commit is contained in:
189
octoprint_tailscale_funnel/static/js/tailscale_funnel.js
Normal file
189
octoprint_tailscale_funnel/static/js/tailscale_funnel.js
Normal file
@@ -0,0 +1,189 @@
|
||||
$(function() {
|
||||
function TailscaleFunnelViewModel(parameters) {
|
||||
var self = this;
|
||||
|
||||
self.settings = parameters[0];
|
||||
|
||||
// Status observables
|
||||
self.funnelStatus = ko.observable("Checking...");
|
||||
self.funnelEnabled = ko.observable(false);
|
||||
self.publicUrl = ko.observable("Not available");
|
||||
self.tailscaleInstalled = ko.observable(true);
|
||||
self.tailscaleRunning = ko.observable(true);
|
||||
|
||||
// Button states
|
||||
self.refreshInProgress = ko.observable(false);
|
||||
self.toggleInProgress = ko.observable(false);
|
||||
|
||||
// Initialize
|
||||
self.onStartup = function() {
|
||||
self.refreshStatus();
|
||||
};
|
||||
|
||||
// Refresh the status
|
||||
self.refreshStatus = function() {
|
||||
if (self.refreshInProgress()) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.refreshInProgress(true);
|
||||
self.funnelStatus("Checking...");
|
||||
|
||||
$.ajax({
|
||||
url: PLUGIN_BASEURL + "tailscale_funnel/status",
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
if (response.status === "success") {
|
||||
self.tailscaleInstalled(response.data.tailscale_installed);
|
||||
self.tailscaleRunning(response.data.tailscale_running);
|
||||
|
||||
if (response.data.tailscale_installed && response.data.tailscale_running) {
|
||||
self.funnelEnabled(response.data.funnel_enabled);
|
||||
self.funnelStatus(response.data.funnel_enabled ? "Enabled" : "Disabled");
|
||||
self.publicUrl(response.data.public_url || "Not available");
|
||||
} else if (!response.data.tailscale_installed) {
|
||||
self.funnelStatus("Tailscale not installed");
|
||||
} else if (!response.data.tailscale_running) {
|
||||
self.funnelStatus("Tailscale not running");
|
||||
}
|
||||
} else {
|
||||
self.funnelStatus("Error: " + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
self.funnelStatus("Error: " + error);
|
||||
},
|
||||
complete: function() {
|
||||
self.refreshInProgress(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Toggle funnel on/off
|
||||
self.toggleFunnel = function() {
|
||||
if (self.toggleInProgress()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var enable = !self.funnelEnabled();
|
||||
|
||||
// Show confirmation if required
|
||||
if (self.settings.settings.plugins.tailscale_funnel.confirm_enable() && enable) {
|
||||
if (!confirm("Enabling Funnel will make your OctoPrint instance accessible from the public internet. Do you want to continue?")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.toggleInProgress(true);
|
||||
self.funnelStatus(enable ? "Enabling..." : "Disabling...");
|
||||
|
||||
var action = enable ? "enable" : "disable";
|
||||
|
||||
$.ajax({
|
||||
url: PLUGIN_BASEURL + "tailscale_funnel/" + action,
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
if (response.status === "success") {
|
||||
self.funnelEnabled(enable);
|
||||
self.funnelStatus(enable ? "Enabled" : "Disabled");
|
||||
|
||||
if (enable && response.data && response.data.public_url) {
|
||||
self.publicUrl(response.data.public_url);
|
||||
} else if (!enable) {
|
||||
self.publicUrl("Not available");
|
||||
}
|
||||
|
||||
// Show success message
|
||||
new PNotify({
|
||||
title: "Tailscale Funnel",
|
||||
text: response.message,
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
self.funnelStatus("Error");
|
||||
// Show error message
|
||||
new PNotify({
|
||||
title: "Tailscale Funnel Error",
|
||||
text: response.message,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
self.funnelStatus("Error");
|
||||
// Show error message
|
||||
new PNotify({
|
||||
title: "Tailscale Funnel Error",
|
||||
text: "Failed to " + action + " Funnel: " + error,
|
||||
type: "error"
|
||||
});
|
||||
},
|
||||
complete: function() {
|
||||
self.toggleInProgress(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Copy URL to clipboard
|
||||
self.copyUrlToClipboard = function() {
|
||||
if (self.publicUrl() && self.publicUrl() !== "Not available") {
|
||||
navigator.clipboard.writeText(self.publicUrl()).then(function() {
|
||||
new PNotify({
|
||||
title: "Copied to Clipboard",
|
||||
text: "Public URL copied to clipboard",
|
||||
type: "success"
|
||||
});
|
||||
}, function() {
|
||||
// Fallback for older browsers
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.value = self.publicUrl();
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
new PNotify({
|
||||
title: "Copied to Clipboard",
|
||||
text: "Public URL copied to clipboard",
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
new PNotify({
|
||||
title: "Copy Failed",
|
||||
text: "Failed to copy URL to clipboard",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handle messages from the backend
|
||||
self.onDataUpdaterPluginMessage = function(plugin, data) {
|
||||
if (plugin !== "tailscale_funnel") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === "funnel_status_change") {
|
||||
self.funnelEnabled(data.enabled);
|
||||
self.funnelStatus(data.enabled ? "Enabled" : "Disabled");
|
||||
|
||||
if (data.enabled) {
|
||||
// Refresh to get the public URL
|
||||
self.refreshStatus();
|
||||
} else {
|
||||
self.publicUrl("Not available");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Register the ViewModel
|
||||
OCTOPRINT_VIEWMODELS.push({
|
||||
construct: TailscaleFunnelViewModel,
|
||||
dependencies: ["settingsViewModel"],
|
||||
elements: []
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user