WebHost: Better host room v2 (#3948)
* WebHost: add spinner to room command and show error message if fetch fails due to NetworkError * WebHost: don't update room log while tab is inactive * WebHost: don't include log for automated requests * WebHost: refresh room also for re-spinups and do that from javascript * Test, WebHost: send fake user-agent where required * WebHost: remove wrong comment in host room
This commit is contained in:
parent
6fac83b84c
commit
f73c0d9894
|
@ -132,26 +132,41 @@ def display_log(room: UUID) -> Union[str, Response, Tuple[str, int]]:
|
||||||
return "Access Denied", 403
|
return "Access Denied", 403
|
||||||
|
|
||||||
|
|
||||||
@app.route('/room/<suuid:room>', methods=['GET', 'POST'])
|
@app.post("/room/<suuid:room>")
|
||||||
|
def host_room_command(room: UUID):
|
||||||
|
room: Room = Room.get(id=room)
|
||||||
|
if room is None:
|
||||||
|
return abort(404)
|
||||||
|
|
||||||
|
if room.owner == session["_id"]:
|
||||||
|
cmd = request.form["cmd"]
|
||||||
|
if cmd:
|
||||||
|
Command(room=room, commandtext=cmd)
|
||||||
|
commit()
|
||||||
|
return redirect(url_for("host_room", room=room.id))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/room/<suuid:room>")
|
||||||
def host_room(room: UUID):
|
def host_room(room: UUID):
|
||||||
room: Room = Room.get(id=room)
|
room: Room = Room.get(id=room)
|
||||||
if room is None:
|
if room is None:
|
||||||
return abort(404)
|
return abort(404)
|
||||||
if request.method == "POST":
|
|
||||||
if room.owner == session["_id"]:
|
|
||||||
cmd = request.form["cmd"]
|
|
||||||
if cmd:
|
|
||||||
Command(room=room, commandtext=cmd)
|
|
||||||
commit()
|
|
||||||
return redirect(url_for("host_room", room=room.id))
|
|
||||||
|
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
# indicate that the page should reload to get the assigned port
|
# indicate that the page should reload to get the assigned port
|
||||||
should_refresh = not room.last_port and now - room.creation_time < datetime.timedelta(seconds=3)
|
should_refresh = ((not room.last_port and now - room.creation_time < datetime.timedelta(seconds=3))
|
||||||
|
or room.last_activity < now - datetime.timedelta(seconds=room.timeout))
|
||||||
with db_session:
|
with db_session:
|
||||||
room.last_activity = now # will trigger a spinup, if it's not already running
|
room.last_activity = now # will trigger a spinup, if it's not already running
|
||||||
|
|
||||||
def get_log(max_size: int = 1024000) -> str:
|
browser_tokens = "Mozilla", "Chrome", "Safari"
|
||||||
|
automated = ("update" in request.args
|
||||||
|
or "Discordbot" in request.user_agent.string
|
||||||
|
or not any(browser_token in request.user_agent.string for browser_token in browser_tokens))
|
||||||
|
|
||||||
|
def get_log(max_size: int = 0 if automated else 1024000) -> str:
|
||||||
|
if max_size == 0:
|
||||||
|
return "…"
|
||||||
try:
|
try:
|
||||||
with open(os.path.join("logs", str(room.id) + ".txt"), "rb") as log:
|
with open(os.path.join("logs", str(room.id) + ".txt"), "rb") as log:
|
||||||
raw_size = 0
|
raw_size = 0
|
||||||
|
|
|
@ -58,3 +58,28 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loader{
|
||||||
|
display: inline-block;
|
||||||
|
visibility: hidden;
|
||||||
|
margin-left: 5px;
|
||||||
|
width: 40px;
|
||||||
|
aspect-ratio: 4;
|
||||||
|
--_g: no-repeat radial-gradient(circle closest-side,#fff 90%,#fff0);
|
||||||
|
background:
|
||||||
|
var(--_g) 0 50%,
|
||||||
|
var(--_g) 50% 50%,
|
||||||
|
var(--_g) 100% 50%;
|
||||||
|
background-size: calc(100%/3) 100%;
|
||||||
|
animation: l7 1s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader.loading{
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes l7{
|
||||||
|
33%{background-size:calc(100%/3) 0% ,calc(100%/3) 100%,calc(100%/3) 100%}
|
||||||
|
50%{background-size:calc(100%/3) 100%,calc(100%/3) 0 ,calc(100%/3) 100%}
|
||||||
|
66%{background-size:calc(100%/3) 100%,calc(100%/3) 100%,calc(100%/3) 0 }
|
||||||
|
}
|
||||||
|
|
|
@ -19,28 +19,30 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% include 'header/grassHeader.html' %}
|
{% include 'header/grassHeader.html' %}
|
||||||
<div id="host-room">
|
<div id="host-room">
|
||||||
{% if room.owner == session["_id"] %}
|
<span id="host-room-info">
|
||||||
Room created from <a href="{{ url_for("view_seed", seed=room.seed.id) }}">Seed #{{ room.seed.id|suuid }}</a>
|
{% if room.owner == session["_id"] %}
|
||||||
<br />
|
Room created from <a href="{{ url_for("view_seed", seed=room.seed.id) }}">Seed #{{ room.seed.id|suuid }}</a>
|
||||||
{% endif %}
|
<br />
|
||||||
{% if room.tracker %}
|
{% endif %}
|
||||||
This room has a <a href="{{ url_for("get_multiworld_tracker", tracker=room.tracker) }}">Multiworld Tracker</a>
|
{% if room.tracker %}
|
||||||
and a <a href="{{ url_for("get_multiworld_sphere_tracker", tracker=room.tracker) }}">Sphere Tracker</a> enabled.
|
This room has a <a href="{{ url_for("get_multiworld_tracker", tracker=room.tracker) }}">Multiworld Tracker</a>
|
||||||
<br />
|
and a <a href="{{ url_for("get_multiworld_sphere_tracker", tracker=room.tracker) }}">Sphere Tracker</a> enabled.
|
||||||
{% endif %}
|
<br />
|
||||||
The server for this room will be paused after {{ room.timeout//60//60 }} hours of inactivity.
|
{% endif %}
|
||||||
Should you wish to continue later,
|
The server for this room will be paused after {{ room.timeout//60//60 }} hours of inactivity.
|
||||||
anyone can simply refresh this page and the server will resume.<br>
|
Should you wish to continue later,
|
||||||
{% if room.last_port == -1 %}
|
anyone can simply refresh this page and the server will resume.<br>
|
||||||
There was an error hosting this Room. Another attempt will be made on refreshing this page.
|
{% if room.last_port == -1 %}
|
||||||
The most likely failure reason is that the multiworld is too old to be loaded now.
|
There was an error hosting this Room. Another attempt will be made on refreshing this page.
|
||||||
{% elif room.last_port %}
|
The most likely failure reason is that the multiworld is too old to be loaded now.
|
||||||
You can connect to this room by using <span class="interactive"
|
{% elif room.last_port %}
|
||||||
data-tooltip="This means address/ip is {{ config['HOST_ADDRESS'] }} and port is {{ room.last_port }}.">
|
You can connect to this room by using <span class="interactive"
|
||||||
'/connect {{ config['HOST_ADDRESS'] }}:{{ room.last_port }}'
|
data-tooltip="This means address/ip is {{ config['HOST_ADDRESS'] }} and port is {{ room.last_port }}.">
|
||||||
</span>
|
'/connect {{ config['HOST_ADDRESS'] }}:{{ room.last_port }}'
|
||||||
in the <a href="{{ url_for("tutorial_landing")}}">client</a>.<br>
|
</span>
|
||||||
{% endif %}
|
in the <a href="{{ url_for("tutorial_landing")}}">client</a>.<br>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
{{ macros.list_patches_room(room) }}
|
{{ macros.list_patches_room(room) }}
|
||||||
{% if room.owner == session["_id"] %}
|
{% if room.owner == session["_id"] %}
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
|
@ -49,6 +51,7 @@
|
||||||
<label for="cmd"></label>
|
<label for="cmd"></label>
|
||||||
<input class="form-control" type="text" id="cmd" name="cmd"
|
<input class="form-control" type="text" id="cmd" name="cmd"
|
||||||
placeholder="Server Command. /help to list them, list gets appended to log.">
|
placeholder="Server Command. /help to list them, list gets appended to log.">
|
||||||
|
<span class="loader"></span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<a href="{{ url_for("display_log", room=room.id) }}">
|
<a href="{{ url_for("display_log", room=room.id) }}">
|
||||||
|
@ -62,6 +65,7 @@
|
||||||
let url = '{{ url_for('display_log', room = room.id) }}';
|
let url = '{{ url_for('display_log', room = room.id) }}';
|
||||||
let bytesReceived = {{ log_len }};
|
let bytesReceived = {{ log_len }};
|
||||||
let updateLogTimeout;
|
let updateLogTimeout;
|
||||||
|
let updateLogImmediately = false;
|
||||||
let awaitingCommandResponse = false;
|
let awaitingCommandResponse = false;
|
||||||
let logger = document.getElementById("logger");
|
let logger = document.getElementById("logger");
|
||||||
|
|
||||||
|
@ -78,29 +82,36 @@
|
||||||
|
|
||||||
async function updateLog() {
|
async function updateLog() {
|
||||||
try {
|
try {
|
||||||
let res = await fetch(url, {
|
if (!document.hidden) {
|
||||||
headers: {
|
updateLogImmediately = false;
|
||||||
'Range': `bytes=${bytesReceived}-`,
|
let res = await fetch(url, {
|
||||||
}
|
headers: {
|
||||||
});
|
'Range': `bytes=${bytesReceived}-`,
|
||||||
if (res.ok) {
|
}
|
||||||
let text = await res.text();
|
});
|
||||||
if (text.length > 0) {
|
if (res.ok) {
|
||||||
awaitingCommandResponse = false;
|
let text = await res.text();
|
||||||
if (bytesReceived === 0 || res.status !== 206) {
|
if (text.length > 0) {
|
||||||
logger.innerHTML = '';
|
awaitingCommandResponse = false;
|
||||||
}
|
if (bytesReceived === 0 || res.status !== 206) {
|
||||||
if (res.status !== 206) {
|
logger.innerHTML = '';
|
||||||
bytesReceived = 0;
|
}
|
||||||
} else {
|
if (res.status !== 206) {
|
||||||
bytesReceived += new Blob([text]).size;
|
bytesReceived = 0;
|
||||||
}
|
} else {
|
||||||
if (logger.innerHTML.endsWith('…')) {
|
bytesReceived += new Blob([text]).size;
|
||||||
logger.innerHTML = logger.innerHTML.substring(0, logger.innerHTML.length - 1);
|
}
|
||||||
}
|
if (logger.innerHTML.endsWith('…')) {
|
||||||
logger.appendChild(document.createTextNode(text));
|
logger.innerHTML = logger.innerHTML.substring(0, logger.innerHTML.length - 1);
|
||||||
scrollToBottom(logger);
|
}
|
||||||
|
logger.appendChild(document.createTextNode(text));
|
||||||
|
scrollToBottom(logger);
|
||||||
|
let loader = document.getElementById("command-form").getElementsByClassName("loader")[0];
|
||||||
|
loader.classList.remove("loading");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
updateLogImmediately = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@ -125,20 +136,62 @@
|
||||||
});
|
});
|
||||||
ev.preventDefault(); // has to happen before first await
|
ev.preventDefault(); // has to happen before first await
|
||||||
form.reset();
|
form.reset();
|
||||||
let res = await req;
|
let loader = form.getElementsByClassName("loader")[0];
|
||||||
if (res.ok || res.type === 'opaqueredirect') {
|
loader.classList.add("loading");
|
||||||
awaitingCommandResponse = true;
|
try {
|
||||||
window.clearTimeout(updateLogTimeout);
|
let res = await req;
|
||||||
updateLogTimeout = window.setTimeout(updateLog, 100);
|
if (res.ok || res.type === 'opaqueredirect') {
|
||||||
} else {
|
awaitingCommandResponse = true;
|
||||||
window.alert(res.statusText);
|
window.clearTimeout(updateLogTimeout);
|
||||||
|
updateLogTimeout = window.setTimeout(updateLog, 100);
|
||||||
|
} else {
|
||||||
|
loader.classList.remove("loading");
|
||||||
|
window.alert(res.statusText);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
loader.classList.remove("loading");
|
||||||
|
window.alert(e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("command-form").addEventListener("submit", postForm);
|
document.getElementById("command-form").addEventListener("submit", postForm);
|
||||||
updateLogTimeout = window.setTimeout(updateLog, 1000);
|
updateLogTimeout = window.setTimeout(updateLog, 1000);
|
||||||
logger.scrollTop = logger.scrollHeight;
|
logger.scrollTop = logger.scrollHeight;
|
||||||
|
document.addEventListener("visibilitychange", () => {
|
||||||
|
if (!document.hidden && updateLogImmediately) {
|
||||||
|
updateLog();
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<script>
|
||||||
|
function updateInfo() {
|
||||||
|
let url = new URL(window.location.href);
|
||||||
|
url.search = "?update";
|
||||||
|
fetch(url)
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP error ${res.status}`);
|
||||||
|
}
|
||||||
|
return res.text()
|
||||||
|
})
|
||||||
|
.then(text => new DOMParser().parseFromString(text, 'text/html'))
|
||||||
|
.then(newDocument => {
|
||||||
|
let el = newDocument.getElementById("host-room-info");
|
||||||
|
document.getElementById("host-room-info").innerHTML = el.innerHTML;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.querySelector("meta[http-equiv='refresh']")) {
|
||||||
|
console.log("Refresh!");
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
for (let i=0; i<3; i++) {
|
||||||
|
window.setTimeout(updateInfo, Math.pow(2, i) * 2000); // 2, 4, 8s
|
||||||
|
}
|
||||||
|
window.stop(); // cancel meta refresh
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -131,7 +131,8 @@ class TestHostFakeRoom(TestBase):
|
||||||
f.write(text)
|
f.write(text)
|
||||||
|
|
||||||
with self.app.app_context(), self.app.test_request_context():
|
with self.app.app_context(), self.app.test_request_context():
|
||||||
response = self.client.get(url_for("host_room", room=self.room_id))
|
response = self.client.get(url_for("host_room", room=self.room_id),
|
||||||
|
headers={"User-Agent": "Mozilla/5.0"})
|
||||||
response_text = response.get_data(True)
|
response_text = response.get_data(True)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn("href=\"/seed/", response_text)
|
self.assertIn("href=\"/seed/", response_text)
|
||||||
|
|
Loading…
Reference in New Issue