You've already forked AstralRinth
forked from didirus/AstralRinth
Revert "fix: error handling improvements (#3797)"
This reverts commit 706976439d.
This commit is contained in:
@@ -124,63 +124,58 @@ export class ModrinthServer {
|
|||||||
return dataURL;
|
return dataURL;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ModrinthServerError && error.statusCode === 404) {
|
if (error instanceof ModrinthServerError && error.statusCode === 404 && iconUrl) {
|
||||||
if (iconUrl) {
|
// Handle external icon processing
|
||||||
try {
|
try {
|
||||||
const response = await fetch(iconUrl);
|
const response = await fetch(iconUrl);
|
||||||
if (!response.ok) throw new Error("Failed to fetch icon");
|
if (!response.ok) throw new Error("Failed to fetch icon");
|
||||||
const file = await response.blob();
|
const file = await response.blob();
|
||||||
const originalFile = new File([file], "server-icon-original.png", {
|
const originalFile = new File([file], "server-icon-original.png", {
|
||||||
type: "image/png",
|
type: "image/png",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
const dataURL = await new Promise<string>((resolve) => {
|
const dataURL = await new Promise<string>((resolve) => {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
canvas.width = 64;
|
canvas.width = 64;
|
||||||
canvas.height = 64;
|
canvas.height = 64;
|
||||||
ctx?.drawImage(img, 0, 0, 64, 64);
|
ctx?.drawImage(img, 0, 0, 64, 64);
|
||||||
canvas.toBlob(async (blob) => {
|
canvas.toBlob(async (blob) => {
|
||||||
if (blob) {
|
if (blob) {
|
||||||
const scaledFile = new File([blob], "server-icon.png", {
|
const scaledFile = new File([blob], "server-icon.png", { type: "image/png" });
|
||||||
type: "image/png",
|
await useServersFetch(`/create?path=/server-icon.png&type=file`, {
|
||||||
});
|
method: "POST",
|
||||||
await useServersFetch(`/create?path=/server-icon.png&type=file`, {
|
contentType: "application/octet-stream",
|
||||||
method: "POST",
|
body: scaledFile,
|
||||||
contentType: "application/octet-stream",
|
override: auth,
|
||||||
body: scaledFile,
|
});
|
||||||
override: auth,
|
await useServersFetch(`/create?path=/server-icon-original.png&type=file`, {
|
||||||
});
|
method: "POST",
|
||||||
await useServersFetch(`/create?path=/server-icon-original.png&type=file`, {
|
contentType: "application/octet-stream",
|
||||||
method: "POST",
|
body: originalFile,
|
||||||
contentType: "application/octet-stream",
|
override: auth,
|
||||||
body: originalFile,
|
});
|
||||||
override: auth,
|
}
|
||||||
});
|
}, "image/png");
|
||||||
}
|
const dataURL = canvas.toDataURL("image/png");
|
||||||
}, "image/png");
|
sharedImage.value = dataURL;
|
||||||
const dataURL = canvas.toDataURL("image/png");
|
resolve(dataURL);
|
||||||
sharedImage.value = dataURL;
|
URL.revokeObjectURL(img.src);
|
||||||
resolve(dataURL);
|
};
|
||||||
URL.revokeObjectURL(img.src);
|
img.src = URL.createObjectURL(file);
|
||||||
};
|
});
|
||||||
img.src = URL.createObjectURL(file);
|
return dataURL;
|
||||||
});
|
|
||||||
return dataURL;
|
|
||||||
}
|
|
||||||
} catch (externalError: any) {
|
|
||||||
console.debug("Could not process external icon:", externalError.message);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to process external icon:", error);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
console.debug("Icon processing failed:", error.message);
|
console.error("Failed to process server icon:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedImage.value = undefined;
|
sharedImage.value = undefined;
|
||||||
@@ -244,18 +239,6 @@ export class ModrinthServer {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ModrinthServerError) {
|
|
||||||
if (error.statusCode === 404 && ["fs", "content"].includes(module)) {
|
|
||||||
console.debug(`Optional ${module} resource not found:`, error.message);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.statusCode === 503) {
|
|
||||||
console.debug(`Temporary ${module} unavailable:`, error.message);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.errors[module] = {
|
this.errors[module] = {
|
||||||
error:
|
error:
|
||||||
error instanceof ModrinthServerError
|
error instanceof ModrinthServerError
|
||||||
|
|||||||
@@ -194,25 +194,19 @@ export class GeneralModule extends ServerModule implements ServerGeneral {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setMotd(motd: string): Promise<void> {
|
async setMotd(motd: string): Promise<void> {
|
||||||
try {
|
const props = (await this.server.fetchConfigFile("ServerProperties")) as any;
|
||||||
const props = (await this.server.fetchConfigFile("ServerProperties")) as any;
|
if (props) {
|
||||||
if (props) {
|
props.motd = motd;
|
||||||
props.motd = motd;
|
const newProps = this.server.constructServerProperties(props);
|
||||||
const newProps = this.server.constructServerProperties(props);
|
const octetStream = new Blob([newProps], { type: "application/octet-stream" });
|
||||||
const octetStream = new Blob([newProps], { type: "application/octet-stream" });
|
const auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/fs`);
|
||||||
const auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/fs`);
|
|
||||||
|
|
||||||
await useServersFetch(`/update?path=/server.properties`, {
|
await useServersFetch(`/update?path=/server.properties`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
contentType: "application/octet-stream",
|
contentType: "application/octet-stream",
|
||||||
body: octetStream,
|
body: octetStream,
|
||||||
override: auth,
|
override: auth,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
console.error(
|
|
||||||
"[Modrinth Servers] [General] Failed to set MOTD due to lack of server properties file.",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,25 +18,48 @@
|
|||||||
v-if="serverData?.status === 'suspended' && serverData.suspension_reason === 'upgrading'"
|
v-if="serverData?.status === 'suspended' && serverData.suspension_reason === 'upgrading'"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<ErrorInformationCard
|
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||||
title="Server upgrading"
|
<div class="flex flex-col items-center text-center">
|
||||||
description="Your server's hardware is currently being upgraded and will be back online shortly!"
|
<div class="flex flex-col items-center gap-4">
|
||||||
:icon="TransferIcon"
|
<div class="grid place-content-center rounded-full bg-bg-blue p-4">
|
||||||
icon-color="blue"
|
<TransferIcon class="size-12 text-blue" />
|
||||||
:action="generalErrorAction"
|
</div>
|
||||||
/>
|
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">Server upgrading</h1>
|
||||||
|
</div>
|
||||||
|
<p class="text-lg text-secondary">
|
||||||
|
Your server's hardware is currently being upgraded and will be back online shortly!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="serverData?.status === 'suspended'"
|
v-else-if="serverData?.status === 'suspended'"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<ErrorInformationCard
|
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||||
title="Server suspended"
|
<div class="flex flex-col items-center text-center">
|
||||||
:description="suspendedDescription"
|
<div class="flex flex-col items-center gap-4">
|
||||||
:icon="LockIcon"
|
<div class="grid place-content-center rounded-full bg-bg-orange p-4">
|
||||||
icon-color="orange"
|
<LockIcon class="size-12 text-orange" />
|
||||||
:action="suspendedAction"
|
</div>
|
||||||
/>
|
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">Server suspended</h1>
|
||||||
|
</div>
|
||||||
|
<p class="text-lg text-secondary">
|
||||||
|
{{
|
||||||
|
serverData.suspension_reason === "cancelled"
|
||||||
|
? "Your subscription has been cancelled."
|
||||||
|
: serverData.suspension_reason
|
||||||
|
? `Your server has been suspended: ${serverData.suspension_reason}`
|
||||||
|
: "Your server has been suspended."
|
||||||
|
}}
|
||||||
|
<br />
|
||||||
|
Contact Modrinth Support if you believe this is an error.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ButtonStyled size="large" color="brand" @click="() => router.push('/settings/billing')">
|
||||||
|
<button class="mt-6 !w-full">Go to billing settings</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="
|
v-else-if="
|
||||||
@@ -45,69 +68,110 @@
|
|||||||
"
|
"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<ErrorInformationCard
|
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||||
title="An error occured."
|
<div class="flex flex-col items-center text-center">
|
||||||
description="Please contact Modrinth Support."
|
<div class="flex flex-col items-center gap-4">
|
||||||
:icon="TransferIcon"
|
<div class="grid place-content-center rounded-full bg-bg-orange p-4">
|
||||||
icon-color="orange"
|
<TransferIcon class="size-12 text-orange" />
|
||||||
:error-details="generalErrorDetails"
|
</div>
|
||||||
:action="generalErrorAction"
|
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">Server not found</h1>
|
||||||
/>
|
</div>
|
||||||
|
<p class="text-lg text-secondary">
|
||||||
|
You don't have permission to view this server or it no longer exists. If you believe this
|
||||||
|
is an error, please contact Modrinth Support.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<UiCopyCode :text="JSON.stringify(server.moduleErrors?.general?.error)" />
|
||||||
|
|
||||||
|
<ButtonStyled size="large" color="brand" @click="() => router.push('/servers/manage')">
|
||||||
|
<button class="mt-6 !w-full">Go back to all servers</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="server.moduleErrors?.general?.error.statusCode === 503"
|
v-else-if="server.moduleErrors?.general?.error.statusCode === 503"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<ErrorInformationCard
|
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||||
title="Server Node Unavailable"
|
<div class="flex flex-col items-center text-center">
|
||||||
:icon="PanelErrorIcon"
|
<div class="flex flex-col items-center gap-4">
|
||||||
icon-color="red"
|
<div class="grid place-content-center rounded-full bg-bg-red p-4">
|
||||||
:action="nodeUnavailableAction"
|
<UiServersIconsPanelErrorIcon class="size-12 text-red" />
|
||||||
:error-details="nodeUnavailableDetails"
|
</div>
|
||||||
>
|
<h1 class="m-0 mb-4 w-fit text-4xl font-bold">Server Node Unavailable</h1>
|
||||||
<template #description>
|
|
||||||
<div class="text-md space-y-4">
|
|
||||||
<p class="leading-[170%] text-secondary">
|
|
||||||
Your server's node, where your Modrinth Server is physically hosted, is experiencing
|
|
||||||
issues. We are working with our datacenter to resolve the issue as quickly as possible.
|
|
||||||
</p>
|
|
||||||
<p class="leading-[170%] text-secondary">
|
|
||||||
Your data is safe and will not be lost, and your server will be back online as soon as
|
|
||||||
the issue is resolved.
|
|
||||||
</p>
|
|
||||||
<p class="leading-[170%] text-secondary">
|
|
||||||
For updates, please join the Modrinth Discord or contact Modrinth Support via the chat
|
|
||||||
bubble in the bottom right corner and we'll be happy to help.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<p class="m-0 mb-4 leading-[170%] text-secondary">
|
||||||
</ErrorInformationCard>
|
Your server's node, where your Modrinth Server is physically hosted, is experiencing
|
||||||
|
issues. We are working with our datacenter to resolve the issue as quickly as possible.
|
||||||
|
</p>
|
||||||
|
<p class="m-0 mb-4 leading-[170%] text-secondary">
|
||||||
|
Your data is safe and will not be lost, and your server will be back online as soon as the
|
||||||
|
issue is resolved.
|
||||||
|
</p>
|
||||||
|
<p class="m-0 mb-4 leading-[170%] text-secondary">
|
||||||
|
For updates, please join the Modrinth Discord or contact Modrinth Support via the chat
|
||||||
|
bubble in the bottom right corner and we'll be happy to help.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<UiCopyCode :text="'Server ID: ' + server.serverId" />
|
||||||
|
<UiCopyCode :text="'Node: ' + server.general?.datacenter" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ButtonStyled
|
||||||
|
size="large"
|
||||||
|
color="standard"
|
||||||
|
@click="
|
||||||
|
() =>
|
||||||
|
navigateTo('https://discord.modrinth.com', {
|
||||||
|
external: true,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<button class="mt-6 !w-full">Join Modrinth Discord</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled
|
||||||
|
:disabled="formattedTime !== '00'"
|
||||||
|
size="large"
|
||||||
|
color="standard"
|
||||||
|
@click="() => reloadNuxtApp()"
|
||||||
|
>
|
||||||
|
<button class="mt-3 !w-full">Reload</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="server.moduleErrors?.general?.error"
|
v-else-if="server.moduleErrors?.general?.error"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<ErrorInformationCard
|
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||||
title="Connection lost"
|
<div class="flex flex-col items-center text-center">
|
||||||
description=""
|
<div class="flex flex-col items-center gap-4">
|
||||||
:icon="TransferIcon"
|
<div class="grid place-content-center rounded-full bg-bg-orange p-4">
|
||||||
icon-color="orange"
|
<TransferIcon class="size-12 text-orange" />
|
||||||
:action="connectionLostAction"
|
</div>
|
||||||
>
|
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">Connection lost</h1>
|
||||||
<template #description>
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="text-center text-secondary">
|
<div class="text-center text-secondary">
|
||||||
{{
|
{{
|
||||||
formattedTime == "00" ? "Reconnecting..." : `Retrying in ${formattedTime} seconds...`
|
formattedTime == "00" ? "Reconnecting..." : `Retrying in ${formattedTime} seconds...`
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<p class="text-lg text-secondary">
|
|
||||||
Something went wrong, and we couldn't connect to your server. This is likely due to a
|
|
||||||
temporary network issue. You'll be reconnected automatically.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<p class="text-lg text-secondary">
|
||||||
</ErrorInformationCard>
|
Something went wrong, and we couldn't connect to your server. This is likely due to a
|
||||||
|
temporary network issue. You'll be reconnected automatically.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<UiCopyCode :text="JSON.stringify(server.moduleErrors?.general?.error)" />
|
||||||
|
<ButtonStyled
|
||||||
|
:disabled="formattedTime !== '00'"
|
||||||
|
size="large"
|
||||||
|
color="brand"
|
||||||
|
@click="() => reloadNuxtApp()"
|
||||||
|
>
|
||||||
|
<button class="mt-6 !w-full">Reload</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- SERVER START -->
|
<!-- SERVER START -->
|
||||||
<div
|
<div
|
||||||
@@ -368,7 +432,7 @@ import {
|
|||||||
LockIcon,
|
LockIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { ButtonStyled, ErrorInformationCard, ServerNotice } from "@modrinth/ui";
|
import { ButtonStyled, ServerNotice } from "@modrinth/ui";
|
||||||
import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
|
import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
|
||||||
import type { MessageDescriptor } from "@vintl/vintl";
|
import type { MessageDescriptor } from "@vintl/vintl";
|
||||||
import type {
|
import type {
|
||||||
@@ -384,7 +448,6 @@ import { useModrinthServersConsole } from "~/store/console.ts";
|
|||||||
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
||||||
import { ModrinthServer, useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
|
import { ModrinthServer, useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
|
||||||
import ServerInstallation from "~/components/ui/servers/ServerInstallation.vue";
|
import ServerInstallation from "~/components/ui/servers/ServerInstallation.vue";
|
||||||
import PanelErrorIcon from "~/components/ui/servers/icons/PanelErrorIcon.vue";
|
|
||||||
|
|
||||||
const app = useNuxtApp() as unknown as { $notify: any };
|
const app = useNuxtApp() as unknown as { $notify: any };
|
||||||
|
|
||||||
@@ -697,7 +760,7 @@ const startUptimeUpdates = () => {
|
|||||||
const stopUptimeUpdates = () => {
|
const stopUptimeUpdates = () => {
|
||||||
if (uptimeIntervalId) {
|
if (uptimeIntervalId) {
|
||||||
clearInterval(uptimeIntervalId);
|
clearInterval(uptimeIntervalId);
|
||||||
pollingIntervalId = null;
|
intervalId = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -992,7 +1055,7 @@ const notifyError = (title: string, text: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let pollingIntervalId: ReturnType<typeof setInterval> | null = null;
|
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||||
const countdown = ref(15);
|
const countdown = ref(15);
|
||||||
|
|
||||||
const formattedTime = computed(() => {
|
const formattedTime = computed(() => {
|
||||||
@@ -1036,142 +1099,23 @@ const backupInProgress = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const stopPolling = () => {
|
const stopPolling = () => {
|
||||||
if (pollingIntervalId) {
|
if (intervalId) {
|
||||||
clearTimeout(pollingIntervalId);
|
clearInterval(intervalId);
|
||||||
pollingIntervalId = null;
|
intervalId = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const startPolling = () => {
|
const startPolling = () => {
|
||||||
stopPolling();
|
countdown.value = 15;
|
||||||
|
intervalId = setInterval(() => {
|
||||||
let retryCount = 0;
|
if (countdown.value <= 0) {
|
||||||
const maxRetries = 10;
|
reloadNuxtApp();
|
||||||
|
} else {
|
||||||
const poll = async () => {
|
countdown.value--;
|
||||||
try {
|
|
||||||
await server.refresh(["general", "ws"]);
|
|
||||||
|
|
||||||
if (!server.moduleErrors?.general?.error) {
|
|
||||||
stopPolling();
|
|
||||||
connectWebSocket();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
retryCount++;
|
|
||||||
if (retryCount >= maxRetries) {
|
|
||||||
console.error("Max retries reached, stopping polling");
|
|
||||||
stopPolling();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exponential backoff: 3s, 6s, 12s, 24s, etc.
|
|
||||||
const delay = Math.min(3000 * Math.pow(2, retryCount - 1), 60000);
|
|
||||||
|
|
||||||
pollingIntervalId = setTimeout(poll, delay);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Polling failed:", error);
|
|
||||||
retryCount++;
|
|
||||||
|
|
||||||
if (retryCount < maxRetries) {
|
|
||||||
const delay = Math.min(3000 * Math.pow(2, retryCount - 1), 60000);
|
|
||||||
pollingIntervalId = setTimeout(poll, delay);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}, 1000);
|
||||||
|
|
||||||
poll();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeUnavailableDetails = computed(() => [
|
|
||||||
{
|
|
||||||
label: "Server ID",
|
|
||||||
value: server.serverId,
|
|
||||||
type: "inline" as const,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Node",
|
|
||||||
value: server.general?.datacenter ?? "Unknown! Please contact support!",
|
|
||||||
type: "inline" as const,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const suspendedDescription = computed(() => {
|
|
||||||
if (serverData.value?.suspension_reason === "cancelled") {
|
|
||||||
return "Your subscription has been cancelled.\nContact Modrinth Support if you believe this is an error.";
|
|
||||||
}
|
|
||||||
if (serverData.value?.suspension_reason) {
|
|
||||||
return `Your server has been suspended: ${serverData.value.suspension_reason}\nContact Modrinth Support if you believe this is an error.`;
|
|
||||||
}
|
|
||||||
return "Your server has been suspended.\nContact Modrinth Support if you believe this is an error.";
|
|
||||||
});
|
|
||||||
|
|
||||||
const generalErrorDetails = computed(() => [
|
|
||||||
{
|
|
||||||
label: "Server ID",
|
|
||||||
value: server.serverId,
|
|
||||||
type: "inline" as const,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Timestamp",
|
|
||||||
value: String(server.moduleErrors?.general?.timestamp),
|
|
||||||
type: "inline" as const,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Error Name",
|
|
||||||
value: server.moduleErrors?.general?.error.name,
|
|
||||||
type: "inline" as const,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Error Message",
|
|
||||||
value: server.moduleErrors?.general?.error.message,
|
|
||||||
type: "block" as const,
|
|
||||||
},
|
|
||||||
...(server.moduleErrors?.general?.error.originalError
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: "Original Error",
|
|
||||||
value: String(server.moduleErrors.general.error.originalError),
|
|
||||||
type: "hidden" as const,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(server.moduleErrors?.general?.error.stack
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: "Stack Trace",
|
|
||||||
value: server.moduleErrors.general.error.stack,
|
|
||||||
type: "hidden" as const,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const suspendedAction = computed(() => ({
|
|
||||||
label: "Go to billing settings",
|
|
||||||
onClick: () => router.push("/settings/billing"),
|
|
||||||
color: "brand" as const,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const generalErrorAction = computed(() => ({
|
|
||||||
label: "Go back to all servers",
|
|
||||||
onClick: () => router.push("/servers/manage"),
|
|
||||||
color: "brand" as const,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const nodeUnavailableAction = computed(() => ({
|
|
||||||
label: "Join Modrinth Discord",
|
|
||||||
onClick: () => navigateTo("https://discord.modrinth.com", { external: true }),
|
|
||||||
color: "standard" as const,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const connectionLostAction = computed(() => ({
|
|
||||||
label: "Reload",
|
|
||||||
onClick: () => reloadNuxtApp(),
|
|
||||||
color: "brand" as const,
|
|
||||||
disabled: formattedTime.value !== "00",
|
|
||||||
}));
|
|
||||||
|
|
||||||
const copyServerDebugInfo = () => {
|
const copyServerDebugInfo = () => {
|
||||||
const debugInfo = `Server ID: ${serverData.value?.server_id}\nError: ${errorMessage.value}\nKind: ${serverData.value?.upstream?.kind}\nProject ID: ${serverData.value?.upstream?.project_id}\nVersion ID: ${serverData.value?.upstream?.version_id}\nLog: ${errorLog.value}`;
|
const debugInfo = `Server ID: ${serverData.value?.server_id}\nError: ${errorMessage.value}\nKind: ${serverData.value?.upstream?.kind}\nProject ID: ${serverData.value?.upstream?.project_id}\nVersion ID: ${serverData.value?.upstream?.version_id}\nLog: ${errorLog.value}`;
|
||||||
navigator.clipboard.writeText(debugInfo);
|
navigator.clipboard.writeText(debugInfo);
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-8 shadow-xl">
|
|
||||||
<div class="flex flex-col items-center text-center">
|
|
||||||
<div class="flex flex-col items-center gap-4">
|
|
||||||
<div class="grid place-content-center rounded-full bg-bg-orange p-4">
|
|
||||||
<component :is="icon" class="size-12 text-orange" />
|
|
||||||
</div>
|
|
||||||
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">{{ title }}</h1>
|
|
||||||
</div>
|
|
||||||
<div v-if="!description">
|
|
||||||
<slot name="description" />
|
|
||||||
</div>
|
|
||||||
<p v-else class="text-lg text-secondary">{{ description }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="errorDetails" class="my-4 w-full rounded-lg border border-divider bg-bg-raised">
|
|
||||||
<div class="divide-y divide-divider">
|
|
||||||
<div
|
|
||||||
v-for="detail in errorDetails.filter((detail) => detail.type !== 'hidden')"
|
|
||||||
:key="detail.label"
|
|
||||||
class="px-4 py-3"
|
|
||||||
>
|
|
||||||
<div v-if="detail.type === 'inline'" class="flex items-center justify-between">
|
|
||||||
<span class="font-medium text-secondary">{{ detail.label }}</span>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<code class="rounded-lg bg-code-bg px-2 py-1 text-sm text-code-text">
|
|
||||||
{{ detail.value }}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="detail.type === 'block'" class="flex flex-col gap-2">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="font-medium text-secondary">{{ detail.label }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-full overflow-hidden rounded-lg bg-code-bg p-3">
|
|
||||||
<code
|
|
||||||
class="block w-full overflow-x-auto break-words text-sm text-code-text whitespace-pre-wrap"
|
|
||||||
>
|
|
||||||
{{ detail.value }}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4 flex !w-full flex-row gap-4">
|
|
||||||
<ButtonStyled
|
|
||||||
v-if="action"
|
|
||||||
size="large"
|
|
||||||
:color="action.color || 'brand'"
|
|
||||||
:disabled="action.disabled"
|
|
||||||
@click="action.onClick"
|
|
||||||
>
|
|
||||||
<button class="!w-full">
|
|
||||||
<component :is="action.icon" v-if="action.icon && !action.showAltIcon" class="size-4" />
|
|
||||||
<component
|
|
||||||
:is="action.altIcon"
|
|
||||||
v-else-if="action.icon && action.showAltIcon"
|
|
||||||
class="size-4"
|
|
||||||
/>
|
|
||||||
{{ action.label }}
|
|
||||||
</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
|
|
||||||
<ButtonStyled v-if="errorDetails" size="large" color="standard" @click="copyErrorInformation">
|
|
||||||
<button class="!w-full">
|
|
||||||
<CopyIcon v-if="!infoCopied" class="size-4" />
|
|
||||||
<CheckIcon v-else class="size-4" />
|
|
||||||
Copy Information
|
|
||||||
</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import ButtonStyled from './ButtonStyled.vue'
|
|
||||||
import { CopyIcon, CheckIcon } from '@modrinth/assets'
|
|
||||||
import type { Component } from 'vue'
|
|
||||||
|
|
||||||
const infoCopied = ref(false)
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
icon: Component
|
|
||||||
errorDetails?: {
|
|
||||||
label?: string
|
|
||||||
value?: string
|
|
||||||
type?: 'inline' | 'block' | 'hidden'
|
|
||||||
}[]
|
|
||||||
action?: {
|
|
||||||
label: string
|
|
||||||
onClick: () => void
|
|
||||||
color?: 'brand' | 'standard' | 'red' | 'orange' | 'blue'
|
|
||||||
disabled?: boolean
|
|
||||||
icon?: Component
|
|
||||||
altIcon?: Component
|
|
||||||
showAltIcon?: boolean
|
|
||||||
}
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const copyErrorInformation = async () => {
|
|
||||||
if (!props.errorDetails || props.errorDetails.length === 0) return
|
|
||||||
|
|
||||||
const formattedErrorInfo = props.errorDetails
|
|
||||||
.filter((detail) => detail.label && detail.value)
|
|
||||||
.map((detail) => `${detail.label}: ${detail.value}`)
|
|
||||||
.join('\n\n')
|
|
||||||
|
|
||||||
await navigator.clipboard.writeText(formattedErrorInfo)
|
|
||||||
infoCopied.value = true
|
|
||||||
setTimeout(() => {
|
|
||||||
infoCopied.value = false
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -16,7 +16,6 @@ export { default as DoubleIcon } from './base/DoubleIcon.vue'
|
|||||||
export { default as DropArea } from './base/DropArea.vue'
|
export { default as DropArea } from './base/DropArea.vue'
|
||||||
export { default as DropdownSelect } from './base/DropdownSelect.vue'
|
export { default as DropdownSelect } from './base/DropdownSelect.vue'
|
||||||
export { default as EnvironmentIndicator } from './base/EnvironmentIndicator.vue'
|
export { default as EnvironmentIndicator } from './base/EnvironmentIndicator.vue'
|
||||||
export { default as ErrorInformationCard } from './base/ErrorInformationCard.vue'
|
|
||||||
export { default as FileInput } from './base/FileInput.vue'
|
export { default as FileInput } from './base/FileInput.vue'
|
||||||
export { default as FilterBar } from './base/FilterBar.vue'
|
export { default as FilterBar } from './base/FilterBar.vue'
|
||||||
export type { FilterBarOption } from './base/FilterBar.vue'
|
export type { FilterBarOption } from './base/FilterBar.vue'
|
||||||
|
|||||||
@@ -54,6 +54,6 @@ export class ModrinthServerError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
super(errorMessage)
|
super(errorMessage)
|
||||||
this.name = 'ModrinthServersFetchError'
|
this.name = 'PyroServersFetchError'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ export class ModrinthServersFetchError extends Error {
|
|||||||
public originalError?: Error,
|
public originalError?: Error,
|
||||||
) {
|
) {
|
||||||
super(message)
|
super(message)
|
||||||
this.name = 'ModrinthFetchError'
|
this.name = 'PyroFetchError'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user