Merge commit '510a6977bec3c122382ed45788b7f6690770b56b' into feature-clean

This commit is contained in:
2024-11-21 02:23:19 +03:00
10 changed files with 232 additions and 43 deletions

View File

@@ -12,17 +12,21 @@ function hCaptchaReady() {
}
onMounted(() => {
window.hCaptchaReady = hCaptchaReady;
if (window.hcaptcha) {
hCaptchaReady();
} else {
window.hCaptchaReady = hCaptchaReady;
useHead({
script: [
{
src: "https://js.hcaptcha.com/1/api.js?render=explicit&onload=hCaptchaReady",
async: true,
defer: true,
},
],
});
useHead({
script: [
{
src: "https://js.hcaptcha.com/1/api.js?render=explicit&onload=hCaptchaReady",
async: true,
defer: true,
},
],
});
}
});
defineExpose({

View File

@@ -17,6 +17,9 @@
<span class="font-semibold"> Backup #{{ newBackupAmount }}</span>
</span>
</div>
<div v-if="isRateLimited" class="mt-2 text-sm text-red">
You're creating backups too fast. Please wait a moment before trying again.
</div>
</div>
<div class="mb-1 mt-4 flex justify-start gap-4">
<ButtonStyled color="brand">
@@ -36,10 +39,9 @@
</template>
<script setup lang="ts">
import { ref, nextTick } from "vue";
import { ref, nextTick, computed } from "vue";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { PlusIcon, XIcon, InfoIcon } from "@modrinth/assets";
import type { Server } from "~/composables/pyroServers";
const props = defineProps<{
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
@@ -50,6 +52,7 @@ const emit = defineEmits(["backupCreated"]);
const modal = ref<InstanceType<typeof NewModal>>();
const input = ref<HTMLInputElement>();
const isCreating = ref(false);
const isRateLimited = ref(false);
const backupError = ref<string | null>(null);
const backupName = ref("");
const newBackupAmount = computed(() =>
@@ -75,14 +78,20 @@ const createBackup = async () => {
}
isCreating.value = true;
isRateLimited.value = false;
try {
await props.server.backups?.create(backupName.value);
await props.server.refresh();
hideModal();
emit("backupCreated", { success: true, message: "Backup created successfully" });
} catch (error) {
backupError.value = error instanceof Error ? error.message : String(error);
emit("backupCreated", { success: false, message: backupError.value });
if (error instanceof PyroFetchError && error.statusCode === 429) {
isRateLimited.value = true;
backupError.value = "You're creating backups too fast.";
} else {
backupError.value = error instanceof Error ? error.message : String(error);
emit("backupCreated", { success: false, message: backupError.value });
}
} finally {
isCreating.value = false;
}

View File

@@ -239,6 +239,7 @@
:is-server-running="isServerRunning"
:stats="stats"
:server-power-state="serverPowerState"
:power-state-details="powerStateDetails"
:console-output="throttledConsoleOutput"
:socket="socket"
:server="server"
@@ -312,6 +313,8 @@ const ramData = ref<number[]>([]);
const isActioning = ref(false);
const isServerRunning = computed(() => serverPowerState.value === "running");
const serverPowerState = ref<ServerState>("stopped");
const powerStateDetails = ref<{ oom_killed?: boolean; exit_code?: number }>();
const uptimeSeconds = ref(0);
const firstConnect = ref(true);
const copied = ref(false);
@@ -488,7 +491,14 @@ const handleWebSocketMessage = (data: WSEvent) => {
reauthenticate();
break;
case "power-state":
updatePowerState(data.state);
if (data.state === "crashed") {
updatePowerState(data.state, {
oom_killed: data.oom_killed,
exit_code: data.exit_code,
});
} else {
updatePowerState(data.state);
}
break;
case "installation-result":
handleInstallationResult(data);
@@ -606,9 +616,19 @@ const updateStats = (currentStats: Stats["current"]) => {
};
};
const updatePowerState = (state: ServerState) => {
console.log("Power state:", state);
const updatePowerState = (
state: ServerState,
details?: { oom_killed?: boolean; exit_code?: number },
) => {
console.log("Power state:", state, details);
serverPowerState.value = state;
if (state === "crashed") {
powerStateDetails.value = details;
} else {
powerStateDetails.value = undefined;
}
if (state === "stopped" || state === "crashed") {
stopUptimeUpdates();
uptimeSeconds.value = 0;

View File

@@ -66,7 +66,11 @@
: ''
"
class="w-full sm:w-fit"
:disabled="isServerRunning && !userPreferences.backupWhileRunning"
:disabled="
(isServerRunning && !userPreferences.backupWhileRunning) ||
data.used_backup_quota >= data.backup_quota ||
backups.some((backup) => backup.ongoing)
"
@click="showCreateModel"
>
<PlusIcon class="h-5 w-5" />
@@ -77,6 +81,15 @@
</div>
</div>
<div
v-if="backups.some((backup) => backup.ongoing)"
data-pyro-server-backup-ongoing
class="flex w-full flex-row items-center gap-4 rounded-2xl bg-bg-orange p-4 text-contrast"
>
A backup is currently being created. This may take a few minutes. This page will
automatically refresh when the backup is complete.
</div>
<li
v-for="(backup, index) in backups"
:key="backup.id"
@@ -246,6 +259,8 @@ const backupSettingsModal = ref<typeof NewModal>();
const renameBackupName = ref("");
const currentBackup = ref("");
const refreshInterval = ref<ReturnType<typeof setInterval>>();
const currentBackupDetails = computed(() => {
return backups.value.find((backup) => backup.id === currentBackup.value);
});
@@ -319,6 +334,29 @@ const initiateDownload = async (backupId: string) => {
console.error("Download failed:", error);
}
};
onMounted(() => {
watchEffect(() => {
const hasOngoingBackups = backups.value.some((backup) => backup.ongoing);
if (refreshInterval.value) {
clearInterval(refreshInterval.value);
refreshInterval.value = undefined;
}
if (hasOngoingBackups) {
refreshInterval.value = setInterval(() => {
props.server.refresh(["backups"]);
}, 10000);
}
});
});
onUnmounted(() => {
if (refreshInterval.value) {
clearInterval(refreshInterval.value);
}
});
</script>
<style scoped>

View File

@@ -35,6 +35,31 @@
</li>
</div>
</div>
<div v-else-if="props.serverPowerState === 'crashed'" class="flex flex-row gap-4">
<IssuesIcon class="hidden h-8 w-8 text-red sm:block" />
<div class="flex flex-col gap-2">
<div class="font-semibold">{{ serverData?.name }} shut down unexpectedly.</div>
<div class="font-normal">
<template v-if="props.powerStateDetails?.oom_killed">
The server stopped because it ran out of memory. There may be a memory leak caused
by a mod or plugin, or you may need to upgrade your Modrinth Server.
</template>
<template v-else-if="props.powerStateDetails?.exit_code !== undefined">
We could not automatically determine the specific cause of the crash, but your
server exited with code
{{ props.powerStateDetails.exit_code }}.
{{
props.powerStateDetails.exit_code === 1
? "There may be a mod or plugin causing the issue, or an issue with your server configuration."
: ""
}}
</template>
<template v-else> We could not determine the specific cause of the crash. </template>
<div class="mt-2">You can try restarting the server.</div>
</div>
</div>
</div>
<div v-else class="flex flex-row gap-4">
<IssuesIcon class="hidden h-8 w-8 text-red sm:block" />
@@ -169,6 +194,10 @@ type ServerProps = {
stats: Stats;
consoleOutput: string[];
serverPowerState: ServerState;
powerStateDetails?: {
oom_killed?: boolean;
exit_code?: number;
};
isServerRunning: boolean;
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
};

View File

@@ -0,0 +1,84 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Modrinth App Ad</title>
<script type="module" src="//js.rev.iq" data-domain="modrinth.com"></script>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
cursor: pointer;
}
.ads-container {
width: 100vw;
height: 100vh;
overflow: hidden;
position: relative;
}
#plus-link {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
z-index: 0;
}
#modrinth-rail-1 {
border-radius: 1rem;
position: absolute;
left: 0;
top: 0;
z-index: 2;
}
</style>
</head>
<body>
<div class="ads-container">
<div id="plus-link"></div>
<div id="modrinth-rail-1" data-ad="app-rail" />
</div>
<script>
// window.addEventListener(
// "message",
// (event) => {
// if (event.data.modrinthAdClick && window.__TAURI_INTERNALS__) {
// window.__TAURI_INTERNALS__.invoke("plugin:ads|record_ads_click", {});
// }
//
// if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__) {
// window.__TAURI_INTERNALS__.invoke("plugin:ads|open_link", {
// path: event.data.modrinthOpenUrl,
// origin: event.origin,
// });
// }
// },
// false,
// );
window.addEventListener("mousewheel", (event) => {
if (window.__TAURI_INTERNALS__) {
window.__TAURI_INTERNALS__.invoke("plugin:ads|scroll_ads_window", {
scroll: event.deltaY,
});
}
});
document.addEventListener("contextmenu", (event) => event.preventDefault());
const plusLink = document.getElementById("plus-link");
plusLink.addEventListener("click", function () {
window.__TAURI_INTERNALS__.invoke("plugin:ads|record_ads_click", {});
window.__TAURI_INTERNALS__.invoke("plugin:ads|open_link", {
path: "https://modrinth.com/plus",
origin: "https://modrinth.com",
});
});
</script>
</body>
</html>

View File

@@ -167,6 +167,9 @@ export interface WSAuthExpiringEvent {
export interface WSPowerStateEvent {
event: "power-state";
state: ServerState;
// if state "crashed"
oom_killed?: boolean;
exit_code?: number;
}
export interface WSAuthIncorrectEvent {