Merge pull request #1313 from JeLuF/cloudflared

Share ED via Cloudflare's ArgoTunnel
This commit is contained in:
cmdr2 2023-06-05 16:20:40 +05:30 committed by GitHub
commit 4b36ca75cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 171 additions and 8 deletions

View File

@ -23,6 +23,7 @@ modules_to_check = {
"rich": "12.6.0", "rich": "12.6.0",
"uvicorn": "0.19.0", "uvicorn": "0.19.0",
"fastapi": "0.85.1", "fastapi": "0.85.1",
"pycloudflared": "0.2.0",
# "xformers": "0.0.16", # "xformers": "0.0.16",
} }

View File

@ -15,6 +15,7 @@ from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Extra from pydantic import BaseModel, Extra
from starlette.responses import FileResponse, JSONResponse, StreamingResponse from starlette.responses import FileResponse, JSONResponse, StreamingResponse
from pycloudflared import try_cloudflare
log.info(f"started in {app.SD_DIR}") log.info(f"started in {app.SD_DIR}")
log.info(f"started at {datetime.datetime.now():%x %X}") log.info(f"started at {datetime.datetime.now():%x %X}")
@ -113,6 +114,14 @@ def init():
def get_image(task_id: int, img_id: int): def get_image(task_id: int, img_id: int):
return get_image_internal(task_id, img_id) return get_image_internal(task_id, img_id)
@server_api.post("/tunnel/cloudflare/start")
def start_cloudflare_tunnel(req: dict):
return start_cloudflare_tunnel_internal(req)
@server_api.post("/tunnel/cloudflare/stop")
def stop_cloudflare_tunnel(req: dict):
return stop_cloudflare_tunnel_internal(req)
@server_api.get("/") @server_api.get("/")
def read_root(): def read_root():
return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS) return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS)
@ -211,6 +220,8 @@ def ping_internal(session_id: str = None):
session = task_manager.get_cached_session(session_id, update_ttl=True) session = task_manager.get_cached_session(session_id, update_ttl=True)
response["tasks"] = {id(t): t.status for t in session.tasks} response["tasks"] = {id(t): t.status for t in session.tasks}
response["devices"] = task_manager.get_devices() response["devices"] = task_manager.get_devices()
if cloudflare.address != None:
response["cloudflare"] = cloudflare.address
return JSONResponse(response, headers=NOCACHE_HEADERS) return JSONResponse(response, headers=NOCACHE_HEADERS)
@ -322,3 +333,46 @@ def get_image_internal(task_id: int, img_id: int):
return StreamingResponse(img_data, media_type="image/jpeg") return StreamingResponse(img_data, media_type="image/jpeg")
except KeyError as e: except KeyError as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
#---- Cloudflare Tunnel ----
class CloudflareTunnel:
def __init__(self):
config = app.getConfig()
self.Urls = None
self.port = config["net"]["listen_port"]
def start(self):
self.Urls = try_cloudflare(self.port)
def stop(self):
if self.Urls != None:
try_cloudflare.terminate(self.port)
self.Urls = None
@property
def address(self):
if self.Urls != None:
return self.Urls.tunnel
else:
return None
cloudflare = CloudflareTunnel()
def start_cloudflare_tunnel_internal(req: dict):
try:
cloudflare.start()
log.info(f"- Started cloudflare tunnel. Using address: {cloudflare.address}")
return JSONResponse({"address":cloudflare.address})
except Exception as e:
log.error(str(e))
log.error(traceback.format_exc())
return HTTPException(status_code=500, detail=str(e))
def stop_cloudflare_tunnel_internal(req: dict):
try:
cloudflare.stop()
except Exception as e:
log.error(str(e))
log.error(traceback.format_exc())
return HTTPException(status_code=500, detail=str(e))

View File

@ -361,10 +361,16 @@
<div id="tab-content-settings" class="tab-content"> <div id="tab-content-settings" class="tab-content">
<div id="system-settings" class="tab-content-inner"> <div id="system-settings" class="tab-content-inner">
<h1>System Settings</h1> <h1>System Settings</h1>
<div class="parameters-table"></div> <div class="parameters-table" id="system-settings-table"></div>
<br/> <br/>
<button id="save-system-settings-btn" class="primaryButton">Save</button> <button id="save-system-settings-btn" class="primaryButton">Save</button>
<br/><br/> <br/><br/>
<div id="share-easy-diffusion">
<h3><i class="fa fa-user-group"></i> Share Easy Diffusion</h3>
<div class="parameters-table" id="system-settings-network-table">
</div>
</div>
<br/><br/>
<div> <div>
<h3><i class="fa fa-microchip icon"></i> System Info</h3> <h3><i class="fa fa-microchip icon"></i> System Info</h3>
<div id="system-info"> <div id="system-info">
@ -539,7 +545,8 @@ async function init() {
SD.init({ SD.init({
events: { events: {
statusChange: setServerStatus, statusChange: setServerStatus,
idle: onIdle idle: onIdle,
ping: tunnelUpdate
} }
}) })

View File

@ -69,11 +69,13 @@
} }
.parameters-table > div:first-child { .parameters-table > div:first-child {
border-radius: 12px 12px 0px 0px; border-top-left-radius: 12px;
border-top-right-radius: 12px;
} }
.parameters-table > div:last-child { .parameters-table > div:last-child {
border-radius: 0px 0px 12px 12px; border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
} }
.parameters-table .fa-fire { .parameters-table .fa-fire {

View File

@ -1309,6 +1309,21 @@ body.wait-pause {
padding-left: 5pt; padding-left: 5pt;
} }
#cloudflare-address {
background-color: var(--background-color3);
padding: 6px;
border-radius: var(--input-border-radius);
border: var(--input-border-size) solid var(--input-border-color);
margin-top: 0.2em;
margin-bottom: 0.2em;
display: inline-block;
}
#copy-cloudflare-address {
padding: 4px 8px;
margin-left: 0.5em;
}
/* TOAST NOTIFICATIONS */ /* TOAST NOTIFICATIONS */
.toast-notification { .toast-notification {
position: fixed; position: fixed;

View File

@ -186,6 +186,7 @@
const EVENT_TASK_START = "taskStart" const EVENT_TASK_START = "taskStart"
const EVENT_TASK_END = "taskEnd" const EVENT_TASK_END = "taskEnd"
const EVENT_TASK_ERROR = "task_error" const EVENT_TASK_ERROR = "task_error"
const EVENT_PING = "ping"
const EVENT_UNEXPECTED_RESPONSE = "unexpectedResponse" const EVENT_UNEXPECTED_RESPONSE = "unexpectedResponse"
const EVENTS_TYPES = [ const EVENTS_TYPES = [
EVENT_IDLE, EVENT_IDLE,
@ -196,6 +197,7 @@
EVENT_TASK_START, EVENT_TASK_START,
EVENT_TASK_END, EVENT_TASK_END,
EVENT_TASK_ERROR, EVENT_TASK_ERROR,
EVENT_PING,
EVENT_UNEXPECTED_RESPONSE, EVENT_UNEXPECTED_RESPONSE,
] ]
@ -240,6 +242,7 @@
setServerStatus("error", "offline") setServerStatus("error", "offline")
return false return false
} }
// Set status // Set status
switch (serverState.status) { switch (serverState.status) {
case ServerStates.init: case ServerStates.init:
@ -261,6 +264,7 @@
break break
} }
serverState.time = Date.now() serverState.time = Date.now()
await eventSource.fireEvent(EVENT_PING, serverState)
return true return true
} catch (e) { } catch (e) {
console.error(e) console.error(e)

View File

@ -1979,6 +1979,38 @@ resumeBtn.addEventListener("click", function() {
document.body.classList.remove("wait-pause") document.body.classList.remove("wait-pause")
}) })
function tunnelUpdate(event) {
if ("cloudflare" in event) {
document.getElementById('cloudflare-off').classList.add("displayNone")
document.getElementById('cloudflare-on').classList.remove("displayNone")
cloudflareAddressField.innerHTML = event.cloudflare
document.getElementById('toggle-cloudflare-tunnel').innerHTML = "Stop"
} else {
document.getElementById('cloudflare-on').classList.add("displayNone")
document.getElementById('cloudflare-off').classList.remove("displayNone")
document.getElementById('toggle-cloudflare-tunnel').innerHTML = "Start"
}
}
document.getElementById('toggle-cloudflare-tunnel').addEventListener("click", async function() {
let command = "stop"
if (document.getElementById('toggle-cloudflare-tunnel').innerHTML == "Start") {
command = "start"
}
showToast(`Cloudflare tunnel ${command} initiated. Please wait.`)
let res = await fetch("/tunnel/cloudflare/"+command, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({}),
})
res = await res.json()
console.log(`Cloudflare tunnel ${command} result:`, res)
})
/* Pause function */ /* Pause function */
document.querySelectorAll(".tab").forEach(linkTabContents) document.querySelectorAll(".tab").forEach(linkTabContents)

View File

@ -11,6 +11,12 @@ var ParameterType = {
custom: "custom", custom: "custom",
} }
/**
* Element shortcuts
*/
let parametersTable = document.querySelector("#system-settings-table")
let networkParametersTable = document.querySelector("#system-settings-network-table")
/** /**
* JSDoc style * JSDoc style
* @typedef {object} Parameter * @typedef {object} Parameter
@ -186,6 +192,7 @@ var PARAMETERS = [
icon: "fa-network-wired", icon: "fa-network-wired",
default: true, default: true,
saveInAppConfig: true, saveInAppConfig: true,
table: networkParametersTable,
}, },
{ {
id: "listen_port", id: "listen_port",
@ -198,6 +205,7 @@ var PARAMETERS = [
return `<input id="${parameter.id}" name="${parameter.id}" size="6" value="9000" onkeypress="preventNonNumericalInput(event)">` return `<input id="${parameter.id}" name="${parameter.id}" size="6" value="9000" onkeypress="preventNonNumericalInput(event)">`
}, },
saveInAppConfig: true, saveInAppConfig: true,
table: networkParametersTable,
}, },
{ {
id: "use_beta_channel", id: "use_beta_channel",
@ -218,6 +226,21 @@ var PARAMETERS = [
default: false, default: false,
saveInAppConfig: true, saveInAppConfig: true,
}, },
{
id: "cloudflare",
type: ParameterType.custom,
label: "Cloudflare tunnel",
note: `<span id="cloudflare-off">Create a VPN tunnel to share your Easy Diffusion instance with your friends. This will
generate a web server address on the public Internet for your Easy Diffusion instance. </span>
<div id="cloudflare-on" class="displayNone"><div>This Easy Diffusion server is available on the Internet using the
address:</div><div><div id="cloudflare-address"></div><button id="copy-cloudflare-address">Copy</button></div></div>
<b>Anyone knowing this address can access your server.</b> The address of your server will change each time
you share a session.<br>
Uses <a href="https://try.cloudflare.com/" target="_blank">Cloudflare services</a>.`,
icon: ["fa-brands", "fa-cloudflare"],
render: () => '<button id="toggle-cloudflare-tunnel" class="primaryButton">Start</button>',
table: networkParametersTable,
}
] ]
function getParameterSettingsEntry(id) { function getParameterSettingsEntry(id) {
@ -266,7 +289,6 @@ function getParameterElement(parameter) {
} }
} }
let parametersTable = document.querySelector("#system-settings .parameters-table")
/** /**
* fill in the system settings popup table * fill in the system settings popup table
* @param {Array<Parameter> | undefined} parameters * @param {Array<Parameter> | undefined} parameters
@ -293,7 +315,10 @@ function initParameters(parameters) {
noteElements.push(noteElement) noteElements.push(noteElement)
} }
const icon = parameter.icon ? [createElement("i", undefined, ["fa", parameter.icon])] : [] if (typeof(parameter.icon) == "string") {
parameter.icon = [parameter.icon]
}
const icon = parameter.icon ? [createElement("i", undefined, ["fa", ...parameter.icon])] : []
const label = typeof parameter.label === "function" ? parameter.label(parameter) : parameter.label const label = typeof parameter.label === "function" ? parameter.label(parameter) : parameter.label
const labelElement = createElement("label", { for: parameter.id }) const labelElement = createElement("label", { for: parameter.id })
@ -313,7 +338,13 @@ function initParameters(parameters) {
elementWrapper, elementWrapper,
] ]
) )
parametersTable.appendChild(newrow)
let p = parametersTable
if (parameter.table) {
p = parameter.table
}
p.appendChild(newrow)
parameter.settingsEntry = newrow parameter.settingsEntry = newrow
}) })
} }
@ -667,8 +698,25 @@ saveSettingsBtn.addEventListener("click", function() {
}) })
const savePromise = changeAppConfig(updateAppConfigRequest) const savePromise = changeAppConfig(updateAppConfigRequest)
showToast("Settings saved")
saveSettingsBtn.classList.add("active") saveSettingsBtn.classList.add("active")
Promise.all([savePromise, asyncDelay(300)]).then(() => saveSettingsBtn.classList.remove("active")) Promise.all([savePromise, asyncDelay(300)]).then(() => saveSettingsBtn.classList.remove("active"))
}) })
listenToNetworkField.addEventListener("change", debounce( ()=>{
saveSettingsBtn.click()
}, 1000))
listenPortField.addEventListener("change", debounce( ()=>{
saveSettingsBtn.click()
}, 1000))
let copyCloudflareAddressBtn = document.querySelector("#copy-cloudflare-address")
let cloudflareAddressField = document.getElementById("cloudflare-address")
copyCloudflareAddressBtn.addEventListener("click", (e) => {
navigator.clipboard.writeText(cloudflareAddressField.innerHTML)
showToast("Copied server address to clipboard")
})
document.addEventListener("system_info_update", (e) => setDeviceInfo(e.detail)) document.addEventListener("system_info_update", (e) => setDeviceInfo(e.detail))