Files
octo-funnel/octoprint_tailscale_funnel/static/js/tailscale_funnel.js

189 lines
7.4 KiB
JavaScript

$(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: []
});
});