230 lines
9.2 KiB
Python
230 lines
9.2 KiB
Python
# coding=utf-8
|
|
from __future__ import absolute_import
|
|
|
|
import octoprint.plugin
|
|
from .tailscale_interface import TailscaleInterface, TailscaleError, TailscaleNotInstalledError, TailscaleNotRunningError
|
|
from .status_monitor import StatusMonitor
|
|
|
|
|
|
class TailscaleFunnelPlugin(octoprint.plugin.StartupPlugin,
|
|
octoprint.plugin.SettingsPlugin,
|
|
octoprint.plugin.AssetPlugin,
|
|
octoprint.plugin.TemplatePlugin,
|
|
octoprint.plugin.BlueprintPlugin):
|
|
|
|
def __init__(self):
|
|
self._logger = None
|
|
self.tailscale_interface = None
|
|
self.status_monitor = None
|
|
|
|
##~~ StartupPlugin mixin
|
|
|
|
def on_after_startup(self):
|
|
self._logger = self._plugin_manager.get_logger("octoprint_tailscale_funnel")
|
|
self._logger.info("Tailscale Funnel Plugin started")
|
|
self.tailscale_interface = TailscaleInterface(self._logger)
|
|
self.status_monitor = StatusMonitor(self)
|
|
self.status_monitor.start()
|
|
|
|
def on_shutdown(self):
|
|
if self.status_monitor:
|
|
self.status_monitor.stop()
|
|
|
|
##~~ SettingsPlugin mixin
|
|
|
|
def get_settings_defaults(self):
|
|
return dict(
|
|
enabled=False,
|
|
port=80,
|
|
confirm_enable=True
|
|
)
|
|
|
|
def get_settings_version(self):
|
|
return 1
|
|
|
|
def on_settings_migrate(self, target, current):
|
|
if current is None:
|
|
# Migrate from settings version 0 to 1
|
|
if self._settings.has(["funnel_enabled"]):
|
|
self._settings.set_boolean(["enabled"], self._settings.get_boolean(["funnel_enabled"]))
|
|
self._settings.remove(["funnel_enabled"])
|
|
|
|
def on_settings_save(self, data):
|
|
# Save the settings
|
|
octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
|
|
|
|
# If enabled setting changed, update funnel status
|
|
if "enabled" in data:
|
|
enabled = self._settings.get_boolean(["enabled"])
|
|
port = self._settings.get_int(["port"])
|
|
|
|
try:
|
|
if enabled:
|
|
# Enable funnel
|
|
if self.tailscale_interface:
|
|
success = self.tailscale_interface.enable_funnel(port)
|
|
if success:
|
|
self._logger.info("Tailscale Funnel enabled on port {}".format(port))
|
|
else:
|
|
self._logger.error("Failed to enable Tailscale Funnel on port {}".format(port))
|
|
else:
|
|
# Disable funnel
|
|
if self.tailscale_interface:
|
|
success = self.tailscale_interface.disable_funnel(port)
|
|
if success:
|
|
self._logger.info("Tailscale Funnel disabled on port {}".format(port))
|
|
else:
|
|
self._logger.error("Failed to disable Tailscale Funnel on port {}".format(port))
|
|
except TailscaleError as e:
|
|
self._logger.error("Tailscale error: {}".format(str(e)))
|
|
except Exception as e:
|
|
self._logger.error("Unexpected error: {}".format(str(e)))
|
|
|
|
##~~ AssetPlugin mixin
|
|
|
|
def get_assets(self):
|
|
return dict(
|
|
js=["js/tailscale_funnel.js"],
|
|
css=["css/tailscale_funnel.css"],
|
|
less=["less/tailscale_funnel.less"]
|
|
)
|
|
|
|
##~~ TemplatePlugin mixin
|
|
|
|
def get_template_configs(self):
|
|
return [
|
|
dict(type="settings", custom_bindings=False)
|
|
]
|
|
|
|
##~~ BlueprintPlugin mixin
|
|
|
|
@octoprint.plugin.BlueprintPlugin.route("/status", methods=["GET"])
|
|
def get_status(self):
|
|
if not self.tailscale_interface:
|
|
return {"status": "error", "message": "Tailscale interface not initialized"}, 500
|
|
|
|
try:
|
|
# Check if tailscale is installed
|
|
if not self.tailscale_interface.is_tailscale_installed():
|
|
return {"status": "error", "message": "Tailscale is not installed"}, 500
|
|
|
|
# Check if tailscale is running
|
|
if not self.tailscale_interface.is_tailscale_running():
|
|
return {"status": "error", "message": "Tailscale is not running"}, 500
|
|
|
|
# Get funnel status
|
|
funnel_enabled = self.tailscale_interface.is_funnel_enabled()
|
|
public_url = self.tailscale_interface.get_public_url() if funnel_enabled else None
|
|
|
|
return {
|
|
"status": "success",
|
|
"data": {
|
|
"funnel_enabled": funnel_enabled,
|
|
"public_url": public_url,
|
|
"tailscale_installed": True,
|
|
"tailscale_running": True
|
|
}
|
|
}
|
|
except TailscaleNotInstalledError as e:
|
|
return {"status": "error", "message": "Tailscale is not installed: {}".format(str(e))}, 500
|
|
except TailscaleNotRunningError as e:
|
|
return {"status": "error", "message": "Tailscale is not running: {}".format(str(e))}, 500
|
|
except TailscaleError as e:
|
|
return {"status": "error", "message": "Tailscale error: {}".format(str(e))}, 500
|
|
except Exception as e:
|
|
self._logger.error("Unexpected error in get_status: {}".format(str(e)))
|
|
return {"status": "error", "message": "Unexpected error: {}".format(str(e))}, 500
|
|
|
|
@octoprint.plugin.BlueprintPlugin.route("/enable", methods=["POST"])
|
|
def enable_funnel(self):
|
|
if not self.tailscale_interface:
|
|
return {"status": "error", "message": "Tailscale interface not initialized"}, 500
|
|
|
|
try:
|
|
port = self._settings.get_int(["port"])
|
|
success = self.tailscale_interface.enable_funnel(port)
|
|
|
|
if success:
|
|
# Update settings
|
|
self._settings.set_boolean(["enabled"], True)
|
|
self._settings.save()
|
|
|
|
public_url = self.tailscale_interface.get_public_url()
|
|
return {
|
|
"status": "success",
|
|
"message": "Tailscale Funnel enabled successfully",
|
|
"data": {
|
|
"public_url": public_url
|
|
}
|
|
}
|
|
else:
|
|
return {"status": "error", "message": "Failed to enable Tailscale Funnel"}, 500
|
|
except TailscaleNotInstalledError as e:
|
|
return {"status": "error", "message": "Tailscale is not installed: {}".format(str(e))}, 500
|
|
except TailscaleError as e:
|
|
return {"status": "error", "message": "Tailscale error: {}".format(str(e))}, 500
|
|
except Exception as e:
|
|
self._logger.error("Unexpected error in enable_funnel: {}".format(str(e)))
|
|
return {"status": "error", "message": "Unexpected error: {}".format(str(e))}, 500
|
|
|
|
@octoprint.plugin.BlueprintPlugin.route("/disable", methods=["POST"])
|
|
def disable_funnel(self):
|
|
if not self.tailscale_interface:
|
|
return {"status": "error", "message": "Tailscale interface not initialized"}, 500
|
|
|
|
try:
|
|
port = self._settings.get_int(["port"])
|
|
success = self.tailscale_interface.disable_funnel(port)
|
|
|
|
if success:
|
|
# Update settings
|
|
self._settings.set_boolean(["enabled"], False)
|
|
self._settings.save()
|
|
|
|
return {
|
|
"status": "success",
|
|
"message": "Tailscale Funnel disabled successfully"
|
|
}
|
|
else:
|
|
return {"status": "error", "message": "Failed to disable Tailscale Funnel"}, 500
|
|
except TailscaleNotInstalledError as e:
|
|
return {"status": "error", "message": "Tailscale is not installed: {}".format(str(e))}, 500
|
|
except TailscaleError as e:
|
|
return {"status": "error", "message": "Tailscale error: {}".format(str(e))}, 500
|
|
except Exception as e:
|
|
self._logger.error("Unexpected error in disable_funnel: {}".format(str(e)))
|
|
return {"status": "error", "message": "Unexpected error: {}".format(str(e))}, 500
|
|
|
|
##~~ Softwareupdate hook
|
|
|
|
def get_update_information(self):
|
|
return dict(
|
|
tailscale_funnel=dict(
|
|
displayName="Tailscale Funnel Plugin",
|
|
displayVersion=self._plugin_version,
|
|
|
|
# version check: github repository
|
|
type="github_release",
|
|
user="markus",
|
|
repo="OctoPrint-Tailscale-Funnel",
|
|
current=self._plugin_version,
|
|
|
|
# update method: pip
|
|
pip="https://github.com/markus/OctoPrint-Tailscale-Funnel/archive/{target_version}.zip"
|
|
)
|
|
)
|
|
|
|
|
|
# If you want your plugin to be registered within OctoPrint under its own name,
|
|
# define it here.
|
|
__plugin_name__ = "Tailscale Funnel Plugin"
|
|
__plugin_pythoncompat__ = ">=3.7,<4" # python 3.7+
|
|
|
|
def __plugin_load__():
|
|
global __plugin_implementation__
|
|
__plugin_implementation__ = TailscaleFunnelPlugin()
|
|
|
|
global __plugin_hooks__
|
|
__plugin_hooks__ = {
|
|
"octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information
|
|
} |