refactor: Huge pyro servers composable cleanup (#3745)
* refactor: start refactor of pyro servers module-based class * refactor: finish modules * refactor: start on type checking + matching api * refactor: finish pyro servers composable refactor * refactor: pyro -> modrinth * fix: import not refactored * fix: broken power action enums * fix: remove pyro mentions * fix: lint * refactor: fix option pages * fix: error renames * remove empty pyro-servers.ts file --------- Signed-off-by: IMB11 <hendersoncal117@gmail.com> Co-authored-by: Prospector <prospectordev@gmail.com>
This commit is contained in:
parent
6955731def
commit
1b1d41605b
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,5 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CommitMessageInspectionProfile">
|
||||
<profile version="1.0">
|
||||
<inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
|
@ -45,9 +45,11 @@
|
||||
import { ref, nextTick, computed } from "vue";
|
||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||
import { IssuesIcon, PlusIcon, XIcon } from "@modrinth/assets";
|
||||
import { ModrinthServersFetchError, type ServerBackup } from "@modrinth/utils";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>();
|
||||
@ -64,7 +66,7 @@ const trimmedName = computed(() => backupName.value.trim());
|
||||
const nameExists = computed(() => {
|
||||
if (!props.server.backups?.data) return false;
|
||||
return props.server.backups.data.some(
|
||||
(backup) => backup.name.trim().toLowerCase() === trimmedName.value.toLowerCase(),
|
||||
(backup: ServerBackup) => backup.name.trim().toLowerCase() === trimmedName.value.toLowerCase(),
|
||||
);
|
||||
});
|
||||
|
||||
@ -98,7 +100,7 @@ const createBackup = async () => {
|
||||
hideModal();
|
||||
await props.server.refresh();
|
||||
} catch (error) {
|
||||
if (error instanceof PyroFetchError && error.statusCode === 429) {
|
||||
if (error instanceof ModrinthServersFetchError && error?.statusCode === 429) {
|
||||
isRateLimited.value = true;
|
||||
addNotification({
|
||||
type: "error",
|
||||
|
@ -20,13 +20,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ConfirmModal } from "@modrinth/ui";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import type { Backup } from "@modrinth/utils";
|
||||
import BackupItem from "~/components/ui/servers/BackupItem.vue";
|
||||
|
||||
defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "delete", backup: Backup | undefined): void;
|
||||
}>();
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
import { ButtonStyled, commonMessages, OverflowMenu, ProgressBar } from "@modrinth/ui";
|
||||
import { defineMessages, useVIntl } from "@vintl/vintl";
|
||||
import { ref } from "vue";
|
||||
import type { Backup } from "~/composables/pyroServers.ts";
|
||||
import type { Backup } from "@modrinth/utils";
|
||||
|
||||
const flags = useFeatureFlags();
|
||||
const { formatMessage } = useVIntl();
|
||||
|
@ -48,10 +48,11 @@
|
||||
import { ref, nextTick, computed } from "vue";
|
||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||
import { SpinnerIcon, SaveIcon, XIcon, IssuesIcon } from "@modrinth/assets";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import type { Backup } from "@modrinth/utils";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>();
|
||||
@ -70,7 +71,7 @@ const nameExists = computed(() => {
|
||||
}
|
||||
|
||||
return props.server.backups.data.some(
|
||||
(backup) => backup.name.trim().toLowerCase() === trimmedName.value.toLowerCase(),
|
||||
(backup: Backup) => backup.name.trim().toLowerCase() === trimmedName.value.toLowerCase(),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -19,11 +19,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ConfirmModal, NewModal } from "@modrinth/ui";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import type { Backup } from "@modrinth/utils";
|
||||
import BackupItem from "~/components/ui/servers/BackupItem.vue";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>();
|
||||
|
@ -59,10 +59,10 @@
|
||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||
import { XIcon, SaveIcon } from "@modrinth/assets";
|
||||
import { ref, computed } from "vue";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["backups"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>();
|
||||
|
@ -239,7 +239,7 @@ import {
|
||||
import { Admonition, ButtonStyled, NewModal } from "@modrinth/ui";
|
||||
import TagItem from "@modrinth/ui/src/components/base/TagItem.vue";
|
||||
import { ref, computed } from "vue";
|
||||
import { formatCategory, formatVersionsForDisplay, type Version } from "@modrinth/utils";
|
||||
import { formatCategory, formatVersionsForDisplay, type Mod, type Version } from "@modrinth/utils";
|
||||
import Accordion from "~/components/ui/Accordion.vue";
|
||||
import Checkbox from "~/components/ui/Checkbox.vue";
|
||||
import ContentVersionFilter, {
|
||||
|
@ -99,7 +99,7 @@
|
||||
import { FolderOpenIcon, CheckCircleIcon, XCircleIcon } from "@modrinth/assets";
|
||||
import { ButtonStyled } from "@modrinth/ui";
|
||||
import { ref, computed, watch, nextTick } from "vue";
|
||||
import type { FSModule } from "~/composables/pyroServers.ts";
|
||||
import { FSModule } from "~/composables/servers/modules/fs.ts";
|
||||
|
||||
interface UploadItem {
|
||||
file: File;
|
||||
|
@ -75,13 +75,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ExternalIcon, SpinnerIcon, DownloadIcon, XIcon } from "@modrinth/assets";
|
||||
import { BackupWarning, ButtonStyled, NewModal } from "@modrinth/ui";
|
||||
import { ModrinthServersFetchError } from "@modrinth/utils";
|
||||
import { ref, computed, nextTick } from "vue";
|
||||
import { handleError, type Server } from "~/composables/pyroServers.ts";
|
||||
import { handleError, ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const cf = ref(false);
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const modal = ref<typeof NewModal>();
|
||||
@ -110,24 +111,18 @@ const handleSubmit = async () => {
|
||||
if (!error.value) {
|
||||
// hide();
|
||||
try {
|
||||
const dry = await props.server.fs?.extractFile(trimmedUrl.value, true, true);
|
||||
const dry = await props.server.fs.extractFile(trimmedUrl.value, true, true);
|
||||
|
||||
if (!cf.value || dry.modpack_name) {
|
||||
await props.server.fs?.extractFile(trimmedUrl.value, true, false, true);
|
||||
await props.server.fs.extractFile(trimmedUrl.value, true, false, true);
|
||||
hide();
|
||||
} else {
|
||||
submitted.value = false;
|
||||
handleError(
|
||||
new ServersError(
|
||||
new ModrinthServersFetchError(
|
||||
"Could not find CurseForge modpack at that URL.",
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
context: "Error installing modpack",
|
||||
error: `url: ${url.value}`,
|
||||
description: "Could not find CurseForge modpack at that URL.",
|
||||
},
|
||||
404,
|
||||
new Error(`No modpack found at ${url.value}`),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -59,7 +59,7 @@
|
||||
|
||||
<template v-else>
|
||||
<ButtonStyled v-if="showStopButton" type="transparent">
|
||||
<button :disabled="!canTakeAction" @click="initiateAction('stop')">
|
||||
<button :disabled="!canTakeAction" @click="initiateAction('Stop')">
|
||||
<div class="flex gap-1">
|
||||
<StopCircleIcon class="h-5 w-5" />
|
||||
<span>{{ isStoppingState ? "Stopping..." : "Stop" }}</span>
|
||||
@ -120,14 +120,12 @@ import {
|
||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useStorage } from "@vueuse/core";
|
||||
|
||||
type ServerAction = "start" | "stop" | "restart" | "kill";
|
||||
type ServerState = "stopped" | "starting" | "running" | "stopping" | "restarting";
|
||||
import type { PowerAction as ServerPowerAction, ServerState } from "@modrinth/utils";
|
||||
|
||||
const flags = useFeatureFlags();
|
||||
|
||||
interface PowerAction {
|
||||
action: ServerAction;
|
||||
action: ServerPowerAction;
|
||||
nextState: ServerState;
|
||||
}
|
||||
|
||||
@ -142,7 +140,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "action", action: ServerAction): void;
|
||||
(e: "action", action: ServerPowerAction): void;
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
@ -170,7 +168,7 @@ const isStoppingState = computed(() => serverState.value === "stopping");
|
||||
const showStopButton = computed(() => isRunning.value || isStoppingState.value);
|
||||
|
||||
const primaryActionText = computed(() => {
|
||||
const states: Record<ServerState, string> = {
|
||||
const states: Partial<Record<ServerState, string>> = {
|
||||
starting: "Starting...",
|
||||
restarting: "Restarting...",
|
||||
running: "Restart",
|
||||
@ -193,7 +191,7 @@ const menuOptions = computed(() => [
|
||||
id: "kill",
|
||||
label: "Kill server",
|
||||
icon: SlashIcon,
|
||||
action: () => initiateAction("kill"),
|
||||
action: () => initiateAction("Kill"),
|
||||
},
|
||||
]),
|
||||
{
|
||||
@ -221,17 +219,17 @@ async function copyId() {
|
||||
await navigator.clipboard.writeText(serverId as string);
|
||||
}
|
||||
|
||||
function initiateAction(action: ServerAction) {
|
||||
function initiateAction(action: ServerPowerAction) {
|
||||
if (!canTakeAction.value) return;
|
||||
|
||||
const stateMap: Record<ServerAction, ServerState> = {
|
||||
start: "starting",
|
||||
stop: "stopping",
|
||||
restart: "restarting",
|
||||
kill: "stopping",
|
||||
const stateMap: Record<ServerPowerAction, ServerState> = {
|
||||
Start: "starting",
|
||||
Stop: "stopping",
|
||||
Restart: "restarting",
|
||||
Kill: "stopping",
|
||||
};
|
||||
|
||||
if (action === "start") {
|
||||
if (action === "Start") {
|
||||
emit("action", action);
|
||||
serverState.value = stateMap[action];
|
||||
startingDelay.value = true;
|
||||
@ -249,7 +247,7 @@ function initiateAction(action: ServerAction) {
|
||||
}
|
||||
|
||||
function handlePrimaryAction() {
|
||||
initiateAction(isRunning.value ? "restart" : "start");
|
||||
initiateAction(isRunning.value ? "Restart" : "Start");
|
||||
}
|
||||
|
||||
function executePowerAction() {
|
||||
@ -263,7 +261,7 @@ function executePowerAction() {
|
||||
userPreferences.value.powerDontAskAgain = true;
|
||||
}
|
||||
|
||||
if (action === "start") {
|
||||
if (action === "Start") {
|
||||
startingDelay.value = true;
|
||||
setTimeout(() => (startingDelay.value = false), 5000);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import type { ServerState } from "~/types/servers";
|
||||
import type { ServerState } from "@modrinth/utils";
|
||||
|
||||
const STATUS_CLASSES = {
|
||||
running: { main: "bg-brand", bg: "bg-bg-green" },
|
||||
@ -49,7 +49,7 @@ const STATUS_CLASSES = {
|
||||
unknown: { main: "", bg: "" },
|
||||
} as const;
|
||||
|
||||
const STATUS_TEXTS = {
|
||||
const STATUS_TEXTS: Partial<Record<ServerState, string>> = {
|
||||
running: "Running",
|
||||
stopped: "",
|
||||
crashed: "Crashed",
|
||||
@ -63,7 +63,10 @@ defineProps<{
|
||||
const isExpanded = ref(false);
|
||||
|
||||
function getStatusClass(state: ServerState) {
|
||||
return STATUS_CLASSES[state] ?? STATUS_CLASSES.unknown;
|
||||
if (state in STATUS_CLASSES) {
|
||||
return STATUS_CLASSES[state as keyof typeof STATUS_CLASSES];
|
||||
}
|
||||
return STATUS_CLASSES.unknown;
|
||||
}
|
||||
|
||||
function getStatusText(state: ServerState) {
|
||||
|
@ -301,7 +301,7 @@ import { useDebounceFn } from "@vueuse/core";
|
||||
import { NewModal } from "@modrinth/ui";
|
||||
import ButtonStyled from "@modrinth/ui/src/components/base/ButtonStyled.vue";
|
||||
import DOMPurify from "dompurify";
|
||||
import { usePyroConsole } from "~/store/console.ts";
|
||||
import { useModrinthServersConsole } from "~/store/console.ts";
|
||||
|
||||
const { $cosmetics } = useNuxtApp();
|
||||
const cosmetics = $cosmetics;
|
||||
@ -318,7 +318,7 @@ const SEPARATOR_HEIGHT = 32;
|
||||
const SCROLL_END_DELAY = 150;
|
||||
const progressiveBlurIterations = ref(8);
|
||||
|
||||
const pyroConsole = usePyroConsole();
|
||||
const pyroConsole = useModrinthServersConsole();
|
||||
const consoleOutput = computed(() => (props.loading ? [] : pyroConsole.output.value));
|
||||
|
||||
const scrollContainer = ref<HTMLElement | null>(null);
|
||||
|
@ -69,10 +69,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||
import { DownloadIcon, XIcon } from "@modrinth/assets";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServersFetchError } from "@modrinth/utils";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
project: any;
|
||||
versions: any[];
|
||||
currentVersion?: any;
|
||||
@ -98,8 +99,7 @@ const handleReinstall = async () => {
|
||||
try {
|
||||
const versionId = props.versions.find((v) => v.version_number === selectedVersion.value)?.id;
|
||||
|
||||
await props.server.general?.reinstall(
|
||||
props.server.serverId,
|
||||
await props.server.general.reinstall(
|
||||
false,
|
||||
props.project.id,
|
||||
versionId,
|
||||
@ -110,7 +110,7 @@ const handleReinstall = async () => {
|
||||
emit("reinstall");
|
||||
hide();
|
||||
} catch (error) {
|
||||
if (error instanceof PyroFetchError && error.statusCode === 429) {
|
||||
if (error instanceof ModrinthServersFetchError && error.statusCode === 429) {
|
||||
addNotification({
|
||||
group: "server",
|
||||
title: "Cannot reinstall server",
|
||||
|
@ -116,11 +116,12 @@
|
||||
<script setup lang="ts">
|
||||
import { BackupWarning, ButtonStyled, NewModal } from "@modrinth/ui";
|
||||
import { UploadIcon, RightArrowIcon, XIcon, ServerIcon } from "@modrinth/assets";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServersFetchError } from "@modrinth/utils";
|
||||
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
backupInProgress?: BackupInProgressReason;
|
||||
}>();
|
||||
|
||||
@ -175,7 +176,7 @@ const handleReinstall = async () => {
|
||||
window.scrollTo(0, 0);
|
||||
hide();
|
||||
} catch (error) {
|
||||
if (error instanceof PyroFetchError && error.statusCode === 429) {
|
||||
if (error instanceof ModrinthServersFetchError && error?.statusCode === 429) {
|
||||
addNotification({
|
||||
group: "server",
|
||||
title: "Cannot reinstall server",
|
||||
|
@ -200,9 +200,9 @@
|
||||
import { BackupWarning, ButtonStyled, NewModal, Toggle } from "@modrinth/ui";
|
||||
import { DropdownIcon, RightArrowIcon, ServerIcon, XIcon } from "@modrinth/assets";
|
||||
import { $fetch } from "ofetch";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import type { Loaders } from "~/types/servers";
|
||||
import { type Loaders, ModrinthServersFetchError } from "@modrinth/utils";
|
||||
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const { formatMessage } = useVIntl();
|
||||
|
||||
@ -220,7 +220,7 @@ type VersionMap = Record<string, LoaderVersion[]>;
|
||||
type VersionCache = Record<string, any>;
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
currentLoader: Loaders | undefined;
|
||||
backupInProgress?: BackupInProgressReason;
|
||||
initialSetup?: boolean;
|
||||
@ -458,7 +458,6 @@ const handleReinstall = async () => {
|
||||
|
||||
try {
|
||||
await props.server.general?.reinstall(
|
||||
props.server.serverId,
|
||||
true,
|
||||
selectedLoader.value,
|
||||
selectedMCVersion.value,
|
||||
@ -474,7 +473,7 @@ const handleReinstall = async () => {
|
||||
|
||||
hide();
|
||||
} catch (error) {
|
||||
if (error instanceof PyroFetchError && error.statusCode === 429) {
|
||||
if (error instanceof ModrinthServersFetchError && (error as any)?.statusCode === 429) {
|
||||
addNotification({
|
||||
group: "server",
|
||||
title: "Cannot reinstall server",
|
||||
|
@ -31,7 +31,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ButtonStyled } from "@modrinth/ui";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
isUpdating: boolean;
|
||||
@ -39,7 +39,7 @@ const props = defineProps<{
|
||||
save: () => void;
|
||||
reset: () => void;
|
||||
isVisible: boolean;
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const saveAndRestart = async () => {
|
||||
|
@ -160,14 +160,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ButtonStyled, NewProjectCard } from "@modrinth/ui";
|
||||
import { TransferIcon, UploadIcon, InfoIcon, CompassIcon, SettingsIcon } from "@modrinth/assets";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import type { Loaders } from "~/types/servers";
|
||||
import type { Loaders } from "@modrinth/utils";
|
||||
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const { formatMessage } = useVIntl();
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
ignoreCurrentInstallation?: boolean;
|
||||
backupInProgress?: BackupInProgressReason;
|
||||
}>();
|
||||
|
@ -81,12 +81,13 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ChevronRightIcon, LockIcon, SparklesIcon } from "@modrinth/assets";
|
||||
import type { Project, Server } from "~/types/servers";
|
||||
import type { Project, Server } from "@modrinth/utils";
|
||||
import { useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<Partial<Server>>();
|
||||
|
||||
if (props.server_id) {
|
||||
await usePyroServer(props.server_id, ["general"]);
|
||||
await useModrinthServers(props.server_id, ["general"]);
|
||||
}
|
||||
|
||||
const showGameLabel = computed(() => !!props.game);
|
||||
@ -109,7 +110,7 @@ if (props.upstream) {
|
||||
const image = useState<string | undefined>(`server-icon-${props.server_id}`, () => undefined);
|
||||
|
||||
if (import.meta.server && projectData.value?.icon_url) {
|
||||
await usePyroServer(props.server_id!, ["general"]);
|
||||
await useModrinthServers(props.server_id!, ["general"]);
|
||||
}
|
||||
|
||||
const iconUrl = computed(() => projectData.value?.icon_url || undefined);
|
||||
|
@ -34,15 +34,15 @@
|
||||
<script setup lang="ts">
|
||||
import { RightArrowIcon } from "@modrinth/assets";
|
||||
import type { RouteLocationNormalized } from "vue-router";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const emit = defineEmits(["reinstall"]);
|
||||
|
||||
defineProps<{
|
||||
navLinks: { label: string; href: string; icon: Component; external?: boolean }[];
|
||||
route: RouteLocationNormalized;
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
backupInProgress?: BackupInProgressReason;
|
||||
}>();
|
||||
|
||||
|
@ -72,7 +72,7 @@
|
||||
import { ref, computed, shallowRef } from "vue";
|
||||
import { FolderOpenIcon, CPUIcon, DatabaseIcon, IssuesIcon } from "@modrinth/assets";
|
||||
import { useStorage } from "@vueuse/core";
|
||||
import type { Stats } from "~/types/servers";
|
||||
import type { Stats } from "@modrinth/utils";
|
||||
|
||||
const route = useNativeRoute();
|
||||
const serverId = route.params.id;
|
||||
|
@ -224,7 +224,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LoaderIcon } from "@modrinth/assets";
|
||||
import type { Loaders } from "~/types/servers";
|
||||
import type { Loaders } from "@modrinth/utils";
|
||||
|
||||
defineProps<{
|
||||
loader: Loaders;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ButtonStyled, ServersSpecs } from "@modrinth/ui";
|
||||
import type { MessageDescriptor } from "@vintl/vintl";
|
||||
import { formatPrice } from "../../../../../../../packages/utils";
|
||||
import { formatPrice } from "@modrinth/utils";
|
||||
|
||||
const { formatMessage, locale } = useVIntl();
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { Accordion, ButtonStyled, NewModal, ServerNotice, TagItem } from "@modri
|
||||
import { PlusIcon, XIcon } from "@modrinth/assets";
|
||||
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
|
||||
import { ref } from "vue";
|
||||
import { usePyroFetch } from "~/composables/pyroFetch.ts";
|
||||
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
||||
|
||||
const app = useNuxtApp() as unknown as { $notify: any };
|
||||
|
||||
@ -23,7 +23,7 @@ const assignedNodes = computed(() => assigned.value.filter((n) => n.kind === "no
|
||||
const inputField = ref("");
|
||||
|
||||
async function refresh() {
|
||||
await usePyroFetch("notices").then((res) => {
|
||||
await useServersFetch("notices").then((res) => {
|
||||
const notices = res as ServerNoticeType[];
|
||||
assigned.value = notices.find((n) => n.id === notice.value?.id)?.assigned ?? [];
|
||||
});
|
||||
@ -33,9 +33,12 @@ async function assign(server: boolean = true) {
|
||||
const input = inputField.value.trim();
|
||||
|
||||
if (input !== "" && notice.value) {
|
||||
await usePyroFetch(`notices/${notice.value.id}/assign?${server ? "server" : "node"}=${input}`, {
|
||||
method: "PUT",
|
||||
}).catch((err) => {
|
||||
await useServersFetch(
|
||||
`notices/${notice.value.id}/assign?${server ? "server" : "node"}=${input}`,
|
||||
{
|
||||
method: "PUT",
|
||||
},
|
||||
).catch((err) => {
|
||||
app.$notify({
|
||||
group: "main",
|
||||
title: "Error assigning notice",
|
||||
@ -75,9 +78,12 @@ async function unassignDetect() {
|
||||
|
||||
async function unassign(id: string, server: boolean = true) {
|
||||
if (notice.value) {
|
||||
await usePyroFetch(`notices/${notice.value.id}/unassign?${server ? "server" : "node"}=${id}`, {
|
||||
method: "PUT",
|
||||
}).catch((err) => {
|
||||
await useServersFetch(
|
||||
`notices/${notice.value.id}/unassign?${server ? "server" : "node"}=${id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
},
|
||||
).catch((err) => {
|
||||
app.$notify({
|
||||
group: "main",
|
||||
title: "Error unassigning notice",
|
||||
|
@ -2,13 +2,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import { ButtonStyled, commonMessages, CopyCode, ServerNotice, TagItem } from "@modrinth/ui";
|
||||
import { EditIcon, SettingsIcon, TrashIcon } from "@modrinth/assets";
|
||||
import { ServerNotice as ServerNoticeType } from "@modrinth/utils";
|
||||
import { useRelativeTime } from "@modrinth/ui";
|
||||
import {
|
||||
DISMISSABLE,
|
||||
getDismissableMetadata,
|
||||
NOTICE_LEVELS,
|
||||
} from "@modrinth/ui/src/utils/notices.ts";
|
||||
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
|
||||
import { useRelativeTime, getDismissableMetadata, NOTICE_LEVELS } from "@modrinth/ui";
|
||||
import { useVIntl } from "@vintl/vintl";
|
||||
|
||||
const { formatMessage } = useVIntl();
|
||||
|
@ -1,112 +0,0 @@
|
||||
import { $fetch, FetchError } from "ofetch";
|
||||
|
||||
interface PyroFetchOptions {
|
||||
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
||||
contentType?: string;
|
||||
body?: Record<string, any>;
|
||||
version?: number;
|
||||
override?: {
|
||||
url?: string;
|
||||
token?: string;
|
||||
};
|
||||
retry?: boolean;
|
||||
bypassAuth?: boolean;
|
||||
}
|
||||
|
||||
export class PyroFetchError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public statusCode?: number,
|
||||
public originalError?: Error,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "PyroFetchError";
|
||||
}
|
||||
}
|
||||
|
||||
export async function usePyroFetch<T>(path: string, options: PyroFetchOptions = {}): Promise<T> {
|
||||
const config = useRuntimeConfig();
|
||||
const auth = await useAuth();
|
||||
const authToken = auth.value?.token;
|
||||
|
||||
if (!authToken && !options.bypassAuth) {
|
||||
throw new PyroFetchError("Cannot pyrofetch without auth", 10000);
|
||||
}
|
||||
|
||||
const { method = "GET", contentType = "application/json", body, version = 0, override } = options;
|
||||
|
||||
const base = (import.meta.server ? config.pyroBaseUrl : config.public.pyroBaseUrl)?.replace(
|
||||
/\/$/,
|
||||
"",
|
||||
);
|
||||
|
||||
if (!base) {
|
||||
throw new PyroFetchError(
|
||||
"Cannot pyrofetch without base url. Make sure to set a PYRO_BASE_URL in environment variables",
|
||||
10001,
|
||||
);
|
||||
}
|
||||
|
||||
const fullUrl = override?.url
|
||||
? `https://${override.url}/${path.replace(/^\//, "")}`
|
||||
: version === 0
|
||||
? `${base}/modrinth/v${version}/${path.replace(/^\//, "")}`
|
||||
: `${base}/v${version}/${path.replace(/^\//, "")}`;
|
||||
|
||||
type HeadersRecord = Record<string, string>;
|
||||
|
||||
const authHeader: HeadersRecord = options.bypassAuth
|
||||
? {}
|
||||
: {
|
||||
Authorization: `Bearer ${override?.token ?? authToken}`,
|
||||
"Access-Control-Allow-Headers": "Authorization",
|
||||
};
|
||||
|
||||
const headers: HeadersRecord = {
|
||||
...authHeader,
|
||||
"User-Agent": "Pyro/1.0 (https://pyro.host)",
|
||||
Vary: "Accept, Origin",
|
||||
"Content-Type": contentType,
|
||||
};
|
||||
|
||||
if (import.meta.client && typeof window !== "undefined") {
|
||||
headers.Origin = window.location.origin;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await $fetch<T>(fullUrl, {
|
||||
method,
|
||||
headers,
|
||||
body: body && contentType === "application/json" ? JSON.stringify(body) : body ?? undefined,
|
||||
timeout: 10000,
|
||||
retry: options.retry !== false ? (method === "GET" ? 3 : 0) : 0,
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Fetch error:", error);
|
||||
if (error instanceof FetchError) {
|
||||
const statusCode = error.response?.status;
|
||||
const statusText = error.response?.statusText || "Unknown error";
|
||||
const errorMessages: { [key: number]: string } = {
|
||||
400: "Bad Request",
|
||||
401: "Unauthorized",
|
||||
403: "Forbidden",
|
||||
404: "Not Found",
|
||||
405: "Method Not Allowed",
|
||||
429: "Too Many Requests",
|
||||
500: "Internal Server Error",
|
||||
502: "Bad Gateway",
|
||||
};
|
||||
const message =
|
||||
statusCode && statusCode in errorMessages
|
||||
? errorMessages[statusCode]
|
||||
: `HTTP Error: ${statusCode || "unknown"} ${statusText}`;
|
||||
throw new PyroFetchError(`[PYROFETCH][PYRO] ${message}`, statusCode, error);
|
||||
}
|
||||
throw new PyroFetchError(
|
||||
"[PYROFETCH][PYRO] An unexpected error occurred during the fetch operation.",
|
||||
undefined,
|
||||
error as Error,
|
||||
);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
265
apps/frontend/src/composables/servers/modrinth-servers.ts
Normal file
265
apps/frontend/src/composables/servers/modrinth-servers.ts
Normal file
@ -0,0 +1,265 @@
|
||||
import { ModrinthServerError } from "@modrinth/utils";
|
||||
import type { JWTAuth, ModuleError, ModuleName } from "@modrinth/utils";
|
||||
import { useServersFetch } from "./servers-fetch.ts";
|
||||
|
||||
import {
|
||||
GeneralModule,
|
||||
ContentModule,
|
||||
BackupsModule,
|
||||
NetworkModule,
|
||||
StartupModule,
|
||||
WSModule,
|
||||
FSModule,
|
||||
} from "./modules/index.ts";
|
||||
|
||||
export function handleError(err: any) {
|
||||
if (err instanceof ModrinthServerError && err.v1Error) {
|
||||
addNotification({
|
||||
title: err.v1Error?.context ?? `An error occurred`,
|
||||
type: "error",
|
||||
text: err.v1Error.description,
|
||||
errorCode: err.v1Error.error,
|
||||
});
|
||||
} else {
|
||||
addNotification({
|
||||
title: "An error occurred",
|
||||
type: "error",
|
||||
text: err.message ?? (err.data ? err.data.description : err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ModrinthServer {
|
||||
readonly serverId: string;
|
||||
private errors: Partial<Record<ModuleName, ModuleError>> = {};
|
||||
|
||||
readonly general: GeneralModule;
|
||||
readonly content: ContentModule;
|
||||
readonly backups: BackupsModule;
|
||||
readonly network: NetworkModule;
|
||||
readonly startup: StartupModule;
|
||||
readonly ws: WSModule;
|
||||
readonly fs: FSModule;
|
||||
|
||||
constructor(serverId: string) {
|
||||
this.serverId = serverId;
|
||||
|
||||
this.general = new GeneralModule(this);
|
||||
this.content = new ContentModule(this);
|
||||
this.backups = new BackupsModule(this);
|
||||
this.network = new NetworkModule(this);
|
||||
this.startup = new StartupModule(this);
|
||||
this.ws = new WSModule(this);
|
||||
this.fs = new FSModule(this);
|
||||
}
|
||||
|
||||
async createMissingFolders(path: string): Promise<void> {
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
const folders = path.split("/");
|
||||
let currentPath = "";
|
||||
|
||||
for (const folder of folders) {
|
||||
currentPath += "/" + folder;
|
||||
try {
|
||||
await this.fs.createFileOrFolder(currentPath, "directory");
|
||||
} catch {
|
||||
// Folder might already exist, ignore error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fetchConfigFile(fileName: string): Promise<any> {
|
||||
return await useServersFetch(`servers/${this.serverId}/config/${fileName}`);
|
||||
}
|
||||
|
||||
constructServerProperties(properties: any): string {
|
||||
let fileContent = `#Minecraft server properties\n#${new Date().toUTCString()}\n`;
|
||||
|
||||
for (const [key, value] of Object.entries(properties)) {
|
||||
if (typeof value === "object") {
|
||||
fileContent += `${key}=${JSON.stringify(value)}\n`;
|
||||
} else if (typeof value === "boolean") {
|
||||
fileContent += `${key}=${value ? "true" : "false"}\n`;
|
||||
} else {
|
||||
fileContent += `${key}=${value}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return fileContent;
|
||||
}
|
||||
|
||||
async processImage(iconUrl: string | undefined): Promise<string | undefined> {
|
||||
const sharedImage = useState<string | undefined>(`server-icon-${this.serverId}`);
|
||||
|
||||
if (sharedImage.value) {
|
||||
return sharedImage.value;
|
||||
}
|
||||
|
||||
try {
|
||||
const auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/fs`);
|
||||
try {
|
||||
const fileData = await useServersFetch(`/download?path=/server-icon-original.png`, {
|
||||
override: auth,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
if (fileData instanceof Blob && import.meta.client) {
|
||||
const dataURL = await new Promise<string>((resolve) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
canvas.width = 512;
|
||||
canvas.height = 512;
|
||||
ctx?.drawImage(img, 0, 0, 512, 512);
|
||||
const dataURL = canvas.toDataURL("image/png");
|
||||
sharedImage.value = dataURL;
|
||||
resolve(dataURL);
|
||||
URL.revokeObjectURL(img.src);
|
||||
};
|
||||
img.src = URL.createObjectURL(fileData);
|
||||
});
|
||||
return dataURL;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ModrinthServerError && error.statusCode === 404 && iconUrl) {
|
||||
// Handle external icon processing
|
||||
try {
|
||||
const response = await fetch(iconUrl);
|
||||
if (!response.ok) throw new Error("Failed to fetch icon");
|
||||
const file = await response.blob();
|
||||
const originalFile = new File([file], "server-icon-original.png", {
|
||||
type: "image/png",
|
||||
});
|
||||
|
||||
if (import.meta.client) {
|
||||
const dataURL = await new Promise<string>((resolve) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
canvas.width = 64;
|
||||
canvas.height = 64;
|
||||
ctx?.drawImage(img, 0, 0, 64, 64);
|
||||
canvas.toBlob(async (blob) => {
|
||||
if (blob) {
|
||||
const scaledFile = new File([blob], "server-icon.png", { type: "image/png" });
|
||||
await useServersFetch(`/create?path=/server-icon.png&type=file`, {
|
||||
method: "POST",
|
||||
contentType: "application/octet-stream",
|
||||
body: scaledFile,
|
||||
override: auth,
|
||||
});
|
||||
await useServersFetch(`/create?path=/server-icon-original.png&type=file`, {
|
||||
method: "POST",
|
||||
contentType: "application/octet-stream",
|
||||
body: originalFile,
|
||||
override: auth,
|
||||
});
|
||||
}
|
||||
}, "image/png");
|
||||
const dataURL = canvas.toDataURL("image/png");
|
||||
sharedImage.value = dataURL;
|
||||
resolve(dataURL);
|
||||
URL.revokeObjectURL(img.src);
|
||||
};
|
||||
img.src = URL.createObjectURL(file);
|
||||
});
|
||||
return dataURL;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to process external icon:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to process server icon:", error);
|
||||
}
|
||||
|
||||
sharedImage.value = undefined;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async refresh(
|
||||
modules: ModuleName[] = [],
|
||||
options?: {
|
||||
preserveConnection?: boolean;
|
||||
preserveInstallState?: boolean;
|
||||
},
|
||||
): Promise<void> {
|
||||
const modulesToRefresh =
|
||||
modules.length > 0
|
||||
? modules
|
||||
: (["general", "content", "backups", "network", "startup", "ws", "fs"] as ModuleName[]);
|
||||
|
||||
for (const module of modulesToRefresh) {
|
||||
try {
|
||||
switch (module) {
|
||||
case "general": {
|
||||
if (options?.preserveConnection) {
|
||||
const currentImage = this.general.image;
|
||||
const currentMotd = this.general.motd;
|
||||
const currentStatus = this.general.status;
|
||||
|
||||
await this.general.fetch();
|
||||
|
||||
if (currentImage) {
|
||||
this.general.image = currentImage;
|
||||
}
|
||||
if (currentMotd) {
|
||||
this.general.motd = currentMotd;
|
||||
}
|
||||
if (options.preserveInstallState && currentStatus === "installing") {
|
||||
this.general.status = "installing";
|
||||
}
|
||||
} else {
|
||||
await this.general.fetch();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "content":
|
||||
await this.content.fetch();
|
||||
break;
|
||||
case "backups":
|
||||
await this.backups.fetch();
|
||||
break;
|
||||
case "network":
|
||||
await this.network.fetch();
|
||||
break;
|
||||
case "startup":
|
||||
await this.startup.fetch();
|
||||
break;
|
||||
case "ws":
|
||||
await this.ws.fetch();
|
||||
break;
|
||||
case "fs":
|
||||
await this.fs.fetch();
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
this.errors[module] = {
|
||||
error:
|
||||
error instanceof ModrinthServerError
|
||||
? error
|
||||
: new ModrinthServerError("Unknown error", undefined, error as Error),
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get moduleErrors() {
|
||||
return this.errors;
|
||||
}
|
||||
}
|
||||
|
||||
export const useModrinthServers = async (
|
||||
serverId: string,
|
||||
includedModules: ModuleName[] = ["general"],
|
||||
) => {
|
||||
const server = new ModrinthServer(serverId);
|
||||
await server.refresh(includedModules);
|
||||
return reactive(server);
|
||||
};
|
79
apps/frontend/src/composables/servers/modules/backups.ts
Normal file
79
apps/frontend/src/composables/servers/modules/backups.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import type { Backup, AutoBackupSettings } from "@modrinth/utils";
|
||||
import { useServersFetch } from "../servers-fetch.ts";
|
||||
import { ServerModule } from "./base.ts";
|
||||
|
||||
export class BackupsModule extends ServerModule {
|
||||
data: Backup[] = [];
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
this.data = await useServersFetch<Backup[]>(`servers/${this.serverId}/backups`, {}, "backups");
|
||||
}
|
||||
|
||||
async create(backupName: string): Promise<string> {
|
||||
const response = await useServersFetch<{ id: string }>(`servers/${this.serverId}/backups`, {
|
||||
method: "POST",
|
||||
body: { name: backupName },
|
||||
});
|
||||
await this.fetch(); // Refresh this module
|
||||
return response.id;
|
||||
}
|
||||
|
||||
async rename(backupId: string, newName: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/rename`, {
|
||||
method: "POST",
|
||||
body: { name: newName },
|
||||
});
|
||||
await this.fetch(); // Refresh this module
|
||||
}
|
||||
|
||||
async delete(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
await this.fetch(); // Refresh this module
|
||||
}
|
||||
|
||||
async restore(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/restore`, {
|
||||
method: "POST",
|
||||
});
|
||||
await this.fetch(); // Refresh this module
|
||||
}
|
||||
|
||||
async prepare(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/prepare-download`, {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
async lock(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/lock`, {
|
||||
method: "POST",
|
||||
});
|
||||
await this.fetch(); // Refresh this module
|
||||
}
|
||||
|
||||
async unlock(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/unlock`, {
|
||||
method: "POST",
|
||||
});
|
||||
await this.fetch(); // Refresh this module
|
||||
}
|
||||
|
||||
async retry(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/retry`, {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
async updateAutoBackup(autoBackup: "enable" | "disable", interval: number): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/autobackup`, {
|
||||
method: "POST",
|
||||
body: { set: autoBackup, interval },
|
||||
});
|
||||
}
|
||||
|
||||
async getAutoBackup(): Promise<AutoBackupSettings> {
|
||||
return await useServersFetch(`servers/${this.serverId}/autobackup`);
|
||||
}
|
||||
}
|
15
apps/frontend/src/composables/servers/modules/base.ts
Normal file
15
apps/frontend/src/composables/servers/modules/base.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { ModrinthServer } from "../modrinth-servers.ts";
|
||||
|
||||
export abstract class ServerModule {
|
||||
protected server: ModrinthServer;
|
||||
|
||||
constructor(server: ModrinthServer) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
protected get serverId(): string {
|
||||
return this.server.serverId;
|
||||
}
|
||||
|
||||
abstract fetch(): Promise<void>;
|
||||
}
|
36
apps/frontend/src/composables/servers/modules/content.ts
Normal file
36
apps/frontend/src/composables/servers/modules/content.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import type { Mod, ContentType } from "@modrinth/utils";
|
||||
import { useServersFetch } from "../servers-fetch.ts";
|
||||
import { ServerModule } from "./base.ts";
|
||||
|
||||
export class ContentModule extends ServerModule {
|
||||
data: Mod[] = [];
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
const mods = await useServersFetch<Mod[]>(`servers/${this.serverId}/mods`, {}, "content");
|
||||
this.data = mods.sort((a, b) => (a?.name ?? "").localeCompare(b?.name ?? ""));
|
||||
}
|
||||
|
||||
async install(contentType: ContentType, projectId: string, versionId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/mods`, {
|
||||
method: "POST",
|
||||
body: {
|
||||
rinth_ids: { project_id: projectId, version_id: versionId },
|
||||
install_as: contentType,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async remove(path: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/deleteMod`, {
|
||||
method: "POST",
|
||||
body: { path },
|
||||
});
|
||||
}
|
||||
|
||||
async reinstall(replace: string, projectId: string, versionId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/mods/update`, {
|
||||
method: "POST",
|
||||
body: { replace, project_id: projectId, version_id: versionId },
|
||||
});
|
||||
}
|
||||
}
|
224
apps/frontend/src/composables/servers/modules/fs.ts
Normal file
224
apps/frontend/src/composables/servers/modules/fs.ts
Normal file
@ -0,0 +1,224 @@
|
||||
import type {
|
||||
FileUploadQuery,
|
||||
JWTAuth,
|
||||
DirectoryResponse,
|
||||
FilesystemOp,
|
||||
FSQueuedOp,
|
||||
} from "@modrinth/utils";
|
||||
import { ModrinthServerError } from "@modrinth/utils";
|
||||
import { useServersFetch } from "../servers-fetch.ts";
|
||||
import { ServerModule } from "./base.ts";
|
||||
|
||||
export class FSModule extends ServerModule {
|
||||
auth!: JWTAuth;
|
||||
ops: FilesystemOp[] = [];
|
||||
queuedOps: FSQueuedOp[] = [];
|
||||
opsQueuedForModification: string[] = [];
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
this.auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/fs`, {}, "fs");
|
||||
this.ops = [];
|
||||
this.queuedOps = [];
|
||||
this.opsQueuedForModification = [];
|
||||
}
|
||||
|
||||
private async retryWithAuth<T>(requestFn: () => Promise<T>): Promise<T> {
|
||||
try {
|
||||
return await requestFn();
|
||||
} catch (error) {
|
||||
if (error instanceof ModrinthServerError && error.statusCode === 401) {
|
||||
await this.fetch(); // Refresh auth
|
||||
return await requestFn();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
listDirContents(path: string, page: number, pageSize: number): Promise<DirectoryResponse> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
return await useServersFetch(`/list?path=${encodedPath}&page=${page}&page_size=${pageSize}`, {
|
||||
override: this.auth,
|
||||
retry: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createFileOrFolder(path: string, type: "file" | "directory"): Promise<void> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
await useServersFetch(`/create?path=${encodedPath}&type=${type}`, {
|
||||
method: "POST",
|
||||
contentType: "application/octet-stream",
|
||||
override: this.auth,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
uploadFile(path: string, file: File): FileUploadQuery {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
const progressSubject = new EventTarget();
|
||||
const abortController = new AbortController();
|
||||
|
||||
const uploadPromise = new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.upload.addEventListener("progress", (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const progress = (e.loaded / e.total) * 100;
|
||||
progressSubject.dispatchEvent(
|
||||
new CustomEvent("progress", {
|
||||
detail: { loaded: e.loaded, total: e.total, progress },
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
reject(new Error(`Upload failed with status ${xhr.status}`));
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => reject(new Error("Upload failed"));
|
||||
xhr.onabort = () => reject(new Error("Upload cancelled"));
|
||||
|
||||
xhr.open("POST", `https://${this.auth.url}/create?path=${encodedPath}&type=file`);
|
||||
xhr.setRequestHeader("Authorization", `Bearer ${this.auth.token}`);
|
||||
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
||||
xhr.send(file);
|
||||
|
||||
abortController.signal.addEventListener("abort", () => xhr.abort());
|
||||
});
|
||||
|
||||
return {
|
||||
promise: uploadPromise,
|
||||
onProgress: (
|
||||
callback: (progress: { loaded: number; total: number; progress: number }) => void,
|
||||
) => {
|
||||
progressSubject.addEventListener("progress", ((e: CustomEvent) => {
|
||||
callback(e.detail);
|
||||
}) as EventListener);
|
||||
},
|
||||
cancel: () => abortController.abort(),
|
||||
} as FileUploadQuery;
|
||||
}
|
||||
|
||||
renameFileOrFolder(path: string, name: string): Promise<void> {
|
||||
const pathName = path.split("/").slice(0, -1).join("/") + "/" + name;
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(`/move`, {
|
||||
method: "POST",
|
||||
override: this.auth,
|
||||
body: { source: path, destination: pathName },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateFile(path: string, content: string): Promise<void> {
|
||||
const octetStream = new Blob([content], { type: "application/octet-stream" });
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(`/update?path=${path}`, {
|
||||
method: "PUT",
|
||||
contentType: "application/octet-stream",
|
||||
body: octetStream,
|
||||
override: this.auth,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
moveFileOrFolder(path: string, newPath: string): Promise<void> {
|
||||
return this.retryWithAuth(async () => {
|
||||
await this.server.createMissingFolders(newPath.substring(0, newPath.lastIndexOf("/")));
|
||||
await useServersFetch(`/move`, {
|
||||
method: "POST",
|
||||
override: this.auth,
|
||||
body: { source: path, destination: newPath },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteFileOrFolder(path: string, recursive: boolean): Promise<void> {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(`/delete?path=${encodedPath}&recursive=${recursive}`, {
|
||||
method: "DELETE",
|
||||
override: this.auth,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
downloadFile(path: string, raw?: boolean): Promise<any> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
const fileData = await useServersFetch(`/download?path=${encodedPath}`, {
|
||||
override: this.auth,
|
||||
});
|
||||
|
||||
if (fileData instanceof Blob) {
|
||||
return raw ? fileData : await fileData.text();
|
||||
}
|
||||
return fileData;
|
||||
});
|
||||
}
|
||||
|
||||
extractFile(
|
||||
path: string,
|
||||
override = true,
|
||||
dry = false,
|
||||
silentQueue = false,
|
||||
): Promise<{ modpack_name: string | null; conflicting_files: string[] }> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
|
||||
if (!silentQueue) {
|
||||
this.queuedOps.push({ op: "unarchive", src: path });
|
||||
setTimeout(() => this.removeQueuedOp("unarchive", path), 4000);
|
||||
}
|
||||
|
||||
try {
|
||||
return await useServersFetch(
|
||||
`/unarchive?src=${encodedPath}&trg=/&override=${override}&dry=${dry}`,
|
||||
{
|
||||
method: "POST",
|
||||
override: this.auth,
|
||||
version: 1,
|
||||
},
|
||||
undefined,
|
||||
"Error extracting file",
|
||||
);
|
||||
} catch (err) {
|
||||
this.removeQueuedOp("unarchive", path);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
modifyOp(id: string, action: "dismiss" | "cancel"): Promise<void> {
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(
|
||||
`/ops/${action}?id=${id}`,
|
||||
{
|
||||
method: "POST",
|
||||
override: this.auth,
|
||||
version: 1,
|
||||
},
|
||||
undefined,
|
||||
`Error ${action === "dismiss" ? "dismissing" : "cancelling"} filesystem operation`,
|
||||
);
|
||||
|
||||
this.opsQueuedForModification = this.opsQueuedForModification.filter((x: string) => x !== id);
|
||||
this.ops = this.ops.filter((x: FilesystemOp) => x.id !== id);
|
||||
});
|
||||
}
|
||||
|
||||
removeQueuedOp(op: FSQueuedOp["op"], src: string): void {
|
||||
this.queuedOps = this.queuedOps.filter((x: FSQueuedOp) => x.op !== op || x.src !== src);
|
||||
}
|
||||
|
||||
clearQueuedOps(): void {
|
||||
this.queuedOps = [];
|
||||
}
|
||||
}
|
173
apps/frontend/src/composables/servers/modules/general.ts
Normal file
173
apps/frontend/src/composables/servers/modules/general.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import { $fetch } from "ofetch";
|
||||
import type { ServerGeneral, Project, PowerAction, JWTAuth } from "@modrinth/utils";
|
||||
import { useServersFetch } from "../servers-fetch.ts";
|
||||
import { ServerModule } from "./base.ts";
|
||||
|
||||
export class GeneralModule extends ServerModule implements ServerGeneral {
|
||||
server_id!: string;
|
||||
name!: string;
|
||||
net!: { ip: string; port: number; domain: string };
|
||||
game!: string;
|
||||
backup_quota!: number;
|
||||
used_backup_quota!: number;
|
||||
status!: string;
|
||||
suspension_reason!: string;
|
||||
loader!: string;
|
||||
loader_version!: string;
|
||||
mc_version!: string;
|
||||
upstream!: {
|
||||
kind: "modpack" | "mod" | "resourcepack";
|
||||
version_id: string;
|
||||
project_id: string;
|
||||
} | null;
|
||||
|
||||
motd?: string;
|
||||
image?: string;
|
||||
project?: Project;
|
||||
sftp_username!: string;
|
||||
sftp_password!: string;
|
||||
sftp_host!: string;
|
||||
datacenter?: string;
|
||||
notices?: any[];
|
||||
node!: { token: string; instance: string };
|
||||
flows?: { intro?: boolean };
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
const data = await useServersFetch<ServerGeneral>(`servers/${this.serverId}`, {}, "general");
|
||||
|
||||
if (data.upstream?.project_id) {
|
||||
const project = await $fetch(
|
||||
`https://api.modrinth.com/v2/project/${data.upstream.project_id}`,
|
||||
);
|
||||
data.project = project as Project;
|
||||
}
|
||||
|
||||
if (import.meta.client) {
|
||||
data.image = (await this.server.processImage(data.project?.icon_url)) ?? undefined;
|
||||
}
|
||||
|
||||
const motd = await this.getMotd();
|
||||
if (motd === "A Minecraft Server") {
|
||||
await this.setMotd(
|
||||
`§b${data.project?.title || data.loader + " " + data.mc_version} §f♦ §aModrinth Servers`,
|
||||
);
|
||||
}
|
||||
data.motd = motd;
|
||||
|
||||
// Copy data to this module
|
||||
Object.assign(this, data);
|
||||
}
|
||||
|
||||
async updateName(newName: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/name`, {
|
||||
method: "POST",
|
||||
body: { name: newName },
|
||||
});
|
||||
}
|
||||
|
||||
async power(action: PowerAction): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/power`, {
|
||||
method: "POST",
|
||||
body: { action },
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
await this.fetch(); // Refresh this module
|
||||
}
|
||||
|
||||
async reinstall(
|
||||
loader: boolean,
|
||||
projectId: string,
|
||||
versionId?: string,
|
||||
loaderVersionId?: string,
|
||||
hardReset: boolean = false,
|
||||
): Promise<void> {
|
||||
const hardResetParam = hardReset ? "true" : "false";
|
||||
if (loader) {
|
||||
if (projectId.toLowerCase() === "neoforge") {
|
||||
projectId = "NeoForge";
|
||||
}
|
||||
await useServersFetch(`servers/${this.serverId}/reinstall?hard=${hardResetParam}`, {
|
||||
method: "POST",
|
||||
body: { loader: projectId, loader_version: loaderVersionId, game_version: versionId },
|
||||
});
|
||||
} else {
|
||||
await useServersFetch(`servers/${this.serverId}/reinstall?hard=${hardResetParam}`, {
|
||||
method: "POST",
|
||||
body: { project_id: projectId, version_id: versionId },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async reinstallFromMrpack(mrpack: File, hardReset: boolean = false): Promise<void> {
|
||||
const hardResetParam = hardReset ? "true" : "false";
|
||||
const auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/reinstallFromMrpack`);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", mrpack);
|
||||
|
||||
const response = await fetch(
|
||||
`https://${auth.url}/reinstallMrpackMultiparted?hard=${hardResetParam}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${auth.token}`,
|
||||
},
|
||||
body: formData,
|
||||
signal: AbortSignal.timeout(30 * 60 * 1000),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`[pyroservers] native fetch err status: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async suspend(status: boolean): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/suspend`, {
|
||||
method: "POST",
|
||||
body: { suspended: status },
|
||||
});
|
||||
}
|
||||
|
||||
async endIntro(): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/flows/intro`, {
|
||||
method: "DELETE",
|
||||
version: 1,
|
||||
});
|
||||
await this.fetch(); // Refresh this module
|
||||
}
|
||||
|
||||
async getMotd(): Promise<string | undefined> {
|
||||
try {
|
||||
const props = await this.server.fs.downloadFile("/server.properties");
|
||||
if (props) {
|
||||
const lines = props.split("\n");
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("motd=")) {
|
||||
return line.slice(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async setMotd(motd: string): Promise<void> {
|
||||
const props = (await this.server.fetchConfigFile("ServerProperties")) as any;
|
||||
if (props) {
|
||||
props.motd = motd;
|
||||
const newProps = this.server.constructServerProperties(props);
|
||||
const octetStream = new Blob([newProps], { type: "application/octet-stream" });
|
||||
const auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/fs`);
|
||||
|
||||
await useServersFetch(`/update?path=/server.properties`, {
|
||||
method: "PUT",
|
||||
contentType: "application/octet-stream",
|
||||
body: octetStream,
|
||||
override: auth,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
8
apps/frontend/src/composables/servers/modules/index.ts
Normal file
8
apps/frontend/src/composables/servers/modules/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from "./base.ts";
|
||||
export * from "./backups.ts";
|
||||
export * from "./content.ts";
|
||||
export * from "./fs.ts";
|
||||
export * from "./general.ts";
|
||||
export * from "./network.ts";
|
||||
export * from "./startup.ts";
|
||||
export * from "./ws.ts";
|
47
apps/frontend/src/composables/servers/modules/network.ts
Normal file
47
apps/frontend/src/composables/servers/modules/network.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import type { Allocation } from "@modrinth/utils";
|
||||
import { useServersFetch } from "../servers-fetch.ts";
|
||||
import { ServerModule } from "./base.ts";
|
||||
|
||||
export class NetworkModule extends ServerModule {
|
||||
allocations: Allocation[] = [];
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
this.allocations = await useServersFetch<Allocation[]>(
|
||||
`servers/${this.serverId}/allocations`,
|
||||
{},
|
||||
"network",
|
||||
);
|
||||
}
|
||||
|
||||
async reserveAllocation(name: string): Promise<Allocation> {
|
||||
return await useServersFetch<Allocation>(`servers/${this.serverId}/allocations?name=${name}`, {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
async updateAllocation(port: number, name: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/allocations/${port}?name=${name}`, {
|
||||
method: "PUT",
|
||||
});
|
||||
}
|
||||
|
||||
async deleteAllocation(port: number): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/allocations/${port}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
async checkSubdomainAvailability(subdomain: string): Promise<boolean> {
|
||||
const result = (await useServersFetch(`subdomains/${subdomain}/isavailable`)) as {
|
||||
available: boolean;
|
||||
};
|
||||
return result.available;
|
||||
}
|
||||
|
||||
async changeSubdomain(subdomain: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/subdomain`, {
|
||||
method: "POST",
|
||||
body: { subdomain },
|
||||
});
|
||||
}
|
||||
}
|
26
apps/frontend/src/composables/servers/modules/startup.ts
Normal file
26
apps/frontend/src/composables/servers/modules/startup.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { Startup, JDKVersion, JDKBuild } from "@modrinth/utils";
|
||||
import { useServersFetch } from "../servers-fetch.ts";
|
||||
import { ServerModule } from "./base.ts";
|
||||
|
||||
export class StartupModule extends ServerModule implements Startup {
|
||||
invocation!: string;
|
||||
original_invocation!: string;
|
||||
jdk_version!: JDKVersion;
|
||||
jdk_build!: JDKBuild;
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
const data = await useServersFetch<Startup>(`servers/${this.serverId}/startup`, {}, "startup");
|
||||
Object.assign(this, data);
|
||||
}
|
||||
|
||||
async update(invocation: string, jdkVersion: JDKVersion, jdkBuild: JDKBuild): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/startup`, {
|
||||
method: "POST",
|
||||
body: {
|
||||
invocation: invocation || null,
|
||||
jdk_version: jdkVersion || null,
|
||||
jdk_build: jdkBuild || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
13
apps/frontend/src/composables/servers/modules/ws.ts
Normal file
13
apps/frontend/src/composables/servers/modules/ws.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import type { JWTAuth } from "@modrinth/utils";
|
||||
import { useServersFetch } from "../servers-fetch.ts";
|
||||
import { ServerModule } from "./base.ts";
|
||||
|
||||
export class WSModule extends ServerModule implements JWTAuth {
|
||||
url!: string;
|
||||
token!: string;
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
const data = await useServersFetch<JWTAuth>(`servers/${this.serverId}/ws`, {}, "ws");
|
||||
Object.assign(this, data);
|
||||
}
|
||||
}
|
188
apps/frontend/src/composables/servers/servers-fetch.ts
Normal file
188
apps/frontend/src/composables/servers/servers-fetch.ts
Normal file
@ -0,0 +1,188 @@
|
||||
import { $fetch, FetchError } from "ofetch";
|
||||
import { ModrinthServerError, ModrinthServersFetchError } from "@modrinth/utils";
|
||||
import type { V1ErrorInfo } from "@modrinth/utils";
|
||||
|
||||
export interface ServersFetchOptions {
|
||||
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
||||
contentType?: string;
|
||||
body?: Record<string, any>;
|
||||
version?: number;
|
||||
override?: {
|
||||
url?: string;
|
||||
token?: string;
|
||||
};
|
||||
retry?: number | boolean;
|
||||
bypassAuth?: boolean;
|
||||
}
|
||||
|
||||
export async function useServersFetch<T>(
|
||||
path: string,
|
||||
options: ServersFetchOptions = {},
|
||||
module?: string,
|
||||
errorContext?: string,
|
||||
): Promise<T> {
|
||||
const config = useRuntimeConfig();
|
||||
const auth = await useAuth();
|
||||
const authToken = auth.value?.token;
|
||||
|
||||
if (!authToken && !options.bypassAuth) {
|
||||
const error = new ModrinthServersFetchError(
|
||||
"[Modrinth Servers] Cannot fetch without auth",
|
||||
10000,
|
||||
);
|
||||
throw new ModrinthServerError("Missing auth token", 401, error, module);
|
||||
}
|
||||
|
||||
const {
|
||||
method = "GET",
|
||||
contentType = "application/json",
|
||||
body,
|
||||
version = 0,
|
||||
override,
|
||||
retry = method === "GET" ? 3 : 0,
|
||||
} = options;
|
||||
|
||||
const base = (import.meta.server ? config.pyroBaseUrl : config.public.pyroBaseUrl)?.replace(
|
||||
/\/$/,
|
||||
"",
|
||||
);
|
||||
|
||||
if (!base) {
|
||||
const error = new ModrinthServersFetchError(
|
||||
"[Modrinth Servers] Cannot fetch without base url. Make sure to set a PYRO_BASE_URL in environment variables",
|
||||
10001,
|
||||
);
|
||||
throw new ModrinthServerError("Configuration error: Missing PYRO_BASE_URL", 500, error, module);
|
||||
}
|
||||
|
||||
const versionString = `v${version}`;
|
||||
let newOverrideUrl = override?.url;
|
||||
if (newOverrideUrl && newOverrideUrl.includes("v0") && version !== 0) {
|
||||
newOverrideUrl = newOverrideUrl.replace("v0", versionString);
|
||||
}
|
||||
|
||||
const fullUrl = newOverrideUrl
|
||||
? `https://${newOverrideUrl}/${path.replace(/^\//, "")}`
|
||||
: version === 0
|
||||
? `${base}/modrinth/v${version}/${path.replace(/^\//, "")}`
|
||||
: `${base}/v${version}/${path.replace(/^\//, "")}`;
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"User-Agent": "Modrinth/1.0 (https://modrinth.com)",
|
||||
Vary: "Accept, Origin",
|
||||
};
|
||||
|
||||
if (!options.bypassAuth) {
|
||||
headers.Authorization = `Bearer ${override?.token ?? authToken}`;
|
||||
headers["Access-Control-Allow-Headers"] = "Authorization";
|
||||
}
|
||||
|
||||
if (contentType !== "none") {
|
||||
headers["Content-Type"] = contentType;
|
||||
}
|
||||
|
||||
if (import.meta.client && typeof window !== "undefined") {
|
||||
headers.Origin = window.location.origin;
|
||||
}
|
||||
|
||||
let attempts = 0;
|
||||
const maxAttempts = (typeof retry === "boolean" ? (retry ? 3 : 1) : retry) + 1;
|
||||
let lastError: Error | null = null;
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
try {
|
||||
const response = await $fetch<T>(fullUrl, {
|
||||
method,
|
||||
headers,
|
||||
body: body && contentType === "application/json" ? JSON.stringify(body) : body ?? undefined,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
attempts++;
|
||||
|
||||
if (error instanceof FetchError) {
|
||||
const statusCode = error.response?.status;
|
||||
const statusText = error.response?.statusText || "Unknown error";
|
||||
|
||||
let v1Error: V1ErrorInfo | undefined;
|
||||
if (error.data?.error && error.data?.description) {
|
||||
v1Error = {
|
||||
context: errorContext,
|
||||
...error.data,
|
||||
};
|
||||
}
|
||||
|
||||
const errorMessages: { [key: number]: string } = {
|
||||
400: "Bad Request",
|
||||
401: "Unauthorized",
|
||||
403: "Forbidden",
|
||||
404: "Not Found",
|
||||
405: "Method Not Allowed",
|
||||
408: "Request Timeout",
|
||||
429: "Too Many Requests",
|
||||
500: "Internal Server Error",
|
||||
502: "Bad Gateway",
|
||||
503: "Service Unavailable",
|
||||
504: "Gateway Timeout",
|
||||
};
|
||||
|
||||
const message =
|
||||
statusCode && statusCode in errorMessages
|
||||
? errorMessages[statusCode]
|
||||
: `HTTP Error: ${statusCode || "unknown"} ${statusText}`;
|
||||
|
||||
const isRetryable = statusCode ? [408, 429, 500, 502, 504].includes(statusCode) : true;
|
||||
|
||||
if (!isRetryable || attempts >= maxAttempts) {
|
||||
console.error("Fetch error:", error);
|
||||
|
||||
const fetchError = new ModrinthServersFetchError(
|
||||
`[Modrinth Servers] ${message}`,
|
||||
statusCode,
|
||||
error,
|
||||
);
|
||||
throw new ModrinthServerError(error.message, statusCode, fetchError, module, v1Error);
|
||||
}
|
||||
|
||||
const delay = Math.min(1000 * Math.pow(2, attempts - 1) + Math.random() * 1000, 10000);
|
||||
console.warn(`Retrying request in ${delay}ms (attempt ${attempts}/${maxAttempts - 1})`);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
continue;
|
||||
}
|
||||
|
||||
console.error("Unexpected fetch error:", error);
|
||||
const fetchError = new ModrinthServersFetchError(
|
||||
"[Modrinth Servers] An unexpected error occurred during the fetch operation.",
|
||||
undefined,
|
||||
error as Error,
|
||||
);
|
||||
throw new ModrinthServerError(
|
||||
"Unexpected error during fetch operation",
|
||||
undefined,
|
||||
fetchError,
|
||||
module,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.error("All retry attempts failed:", lastError);
|
||||
if (lastError instanceof FetchError) {
|
||||
const statusCode = lastError.response?.status;
|
||||
const pyroError = new ModrinthServersFetchError(
|
||||
"Maximum retry attempts reached",
|
||||
statusCode,
|
||||
lastError,
|
||||
);
|
||||
throw new ModrinthServerError("Maximum retry attempts reached", statusCode, pyroError, module);
|
||||
}
|
||||
|
||||
const fetchError = new ModrinthServersFetchError(
|
||||
"Maximum retry attempts reached",
|
||||
undefined,
|
||||
lastError || undefined,
|
||||
);
|
||||
throw new ModrinthServerError("Maximum retry attempts reached", undefined, fetchError, module);
|
||||
}
|
@ -275,7 +275,7 @@ import { useVIntl } from "@vintl/vintl";
|
||||
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
|
||||
import { computed } from "vue";
|
||||
import { NOTICE_LEVELS } from "@modrinth/ui/src/utils/notices.ts";
|
||||
import { usePyroFetch } from "~/composables/pyroFetch.ts";
|
||||
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
||||
import AssignNoticeModal from "~/components/ui/servers/notice/AssignNoticeModal.vue";
|
||||
|
||||
const { formatMessage } = useVIntl();
|
||||
@ -290,7 +290,7 @@ const assignNoticeModal = ref<InstanceType<typeof AssignNoticeModal>>();
|
||||
await refreshNotices();
|
||||
|
||||
async function refreshNotices() {
|
||||
await usePyroFetch("notices").then((res) => {
|
||||
await useServersFetch("notices").then((res) => {
|
||||
notices.value = res as ServerNoticeType[];
|
||||
notices.value.sort((a, b) => {
|
||||
const dateDiff = dayjs(b.announce_at).diff(dayjs(a.announce_at));
|
||||
@ -347,7 +347,7 @@ function startEditing(notice: ServerNoticeType, assignments: boolean = false) {
|
||||
}
|
||||
|
||||
async function deleteNotice(notice: ServerNoticeType) {
|
||||
await usePyroFetch(`notices/${notice.id}`, {
|
||||
await useServersFetch(`notices/${notice.id}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
.then(() => {
|
||||
@ -401,7 +401,7 @@ async function saveChanges() {
|
||||
return;
|
||||
}
|
||||
|
||||
await usePyroFetch(`notices/${editingNotice.value?.id}`, {
|
||||
await useServersFetch(`notices/${editingNotice.value?.id}`, {
|
||||
method: "PATCH",
|
||||
body: {
|
||||
message: newNoticeMessage.value,
|
||||
@ -432,7 +432,7 @@ async function createNotice() {
|
||||
return;
|
||||
}
|
||||
|
||||
await usePyroFetch("notices", {
|
||||
await useServersFetch("notices", {
|
||||
method: "POST",
|
||||
body: {
|
||||
message: newNoticeMessage.value,
|
||||
|
@ -623,6 +623,7 @@ import {
|
||||
ServerIcon,
|
||||
} from "@modrinth/assets";
|
||||
import { products } from "~/generated/state.json";
|
||||
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
||||
import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue";
|
||||
import ServerPlanSelector from "~/components/ui/servers/marketing/ServerPlanSelector.vue";
|
||||
import OptionGroup from "~/components/ui/OptionGroup.vue";
|
||||
@ -674,7 +675,7 @@ const outOfStockUrl = "https://discord.modrinth.com";
|
||||
const { data: hasServers } = await useAsyncData("ServerListCountCheck", async () => {
|
||||
try {
|
||||
if (!auth.value.user) return false;
|
||||
const response = await usePyroFetch("servers");
|
||||
const response = await useServersFetch("servers");
|
||||
return response.servers && response.servers.length > 0;
|
||||
} catch {
|
||||
return false;
|
||||
@ -682,7 +683,7 @@ const { data: hasServers } = await useAsyncData("ServerListCountCheck", async ()
|
||||
});
|
||||
|
||||
function fetchStock(region, request) {
|
||||
return usePyroFetch(`stock?region=${region.shortcode}`, {
|
||||
return useServersFetch(`stock?region=${region.shortcode}`, {
|
||||
method: "POST",
|
||||
body: {
|
||||
...request,
|
||||
@ -702,7 +703,7 @@ async function fetchCapacityStatuses(customProduct = null) {
|
||||
),
|
||||
];
|
||||
const capacityChecks = productsToCheck.map((product) =>
|
||||
usePyroFetch("stock", {
|
||||
useServersFetch("stock", {
|
||||
method: "POST",
|
||||
body: {
|
||||
cpu: product.metadata.cpu,
|
||||
@ -892,7 +893,7 @@ const regions = ref([]);
|
||||
const regionPings = ref([]);
|
||||
|
||||
function pingRegions() {
|
||||
usePyroFetch("regions", {
|
||||
useServersFetch("regions", {
|
||||
method: "GET",
|
||||
version: 1,
|
||||
bypassAuth: true,
|
||||
|
@ -63,8 +63,8 @@
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
server.general?.error?.error.statusCode === 403 ||
|
||||
server.general?.error?.error.statusCode === 404
|
||||
server.moduleErrors?.general?.error.statusCode === 403 ||
|
||||
server.moduleErrors?.general?.error.statusCode === 404
|
||||
"
|
||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||
>
|
||||
@ -81,7 +81,7 @@
|
||||
is an error, please contact Modrinth Support.
|
||||
</p>
|
||||
</div>
|
||||
<UiCopyCode :text="JSON.stringify(server.general?.error)" />
|
||||
<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>
|
||||
@ -89,7 +89,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="server.general?.error?.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"
|
||||
>
|
||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||
@ -141,7 +141,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="server.general?.error"
|
||||
v-else-if="server.moduleErrors?.general?.error"
|
||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||
>
|
||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||
@ -162,7 +162,7 @@
|
||||
temporary network issue. You'll be reconnected automatically.
|
||||
</p>
|
||||
</div>
|
||||
<UiCopyCode :text="JSON.stringify(server.general?.error)" />
|
||||
<UiCopyCode :text="JSON.stringify(server.moduleErrors?.general?.error)" />
|
||||
<ButtonStyled
|
||||
:disabled="formattedTime !== '00'"
|
||||
size="large"
|
||||
@ -252,7 +252,7 @@
|
||||
</h2>
|
||||
|
||||
<ServerInstallation
|
||||
:server="server"
|
||||
:server="server as ModrinthServer"
|
||||
:backup-in-progress="backupInProgress"
|
||||
ignore-current-installation
|
||||
@reinstall="onReinstall"
|
||||
@ -419,7 +419,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
|
||||
import { ref, computed, onMounted, onUnmounted, watch, type Reactive } from "vue";
|
||||
import {
|
||||
SettingsIcon,
|
||||
CopyIcon,
|
||||
@ -434,12 +434,19 @@ import {
|
||||
import DOMPurify from "dompurify";
|
||||
import { ButtonStyled, ServerNotice } from "@modrinth/ui";
|
||||
import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
|
||||
import { reloadNuxtApp, navigateTo } from "#app";
|
||||
import type { MessageDescriptor } from "@vintl/vintl";
|
||||
import type { ServerState, Stats, WSEvent, WSInstallationResultEvent } from "~/types/servers";
|
||||
import { usePyroConsole } from "~/store/console.ts";
|
||||
import { type Backup } from "~/composables/pyroServers.ts";
|
||||
import { usePyroFetch } from "~/composables/pyroFetch.ts";
|
||||
import type {
|
||||
ServerState,
|
||||
Stats,
|
||||
WSEvent,
|
||||
WSInstallationResultEvent,
|
||||
Backup,
|
||||
PowerAction,
|
||||
} from "@modrinth/utils";
|
||||
import { reloadNuxtApp, navigateTo } from "#app";
|
||||
import { useModrinthServersConsole } from "~/store/console.ts";
|
||||
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
||||
import { ModrinthServer, useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
|
||||
import ServerInstallation from "~/components/ui/servers/ServerInstallation.vue";
|
||||
|
||||
const app = useNuxtApp() as unknown as { $notify: any };
|
||||
@ -467,19 +474,19 @@ const route = useNativeRoute();
|
||||
const router = useRouter();
|
||||
const serverId = route.params.id as string;
|
||||
|
||||
const server = await usePyroServer(serverId, ["general", "ws"]);
|
||||
const server: Reactive<ModrinthServer> = await useModrinthServers(serverId, ["general", "ws"]);
|
||||
|
||||
const loadModulesPromise = Promise.resolve().then(() => {
|
||||
if (server.general?.status === "suspended") {
|
||||
return;
|
||||
}
|
||||
return server.loadModules(["content", "backups", "network", "startup", "fs"]);
|
||||
return server.refresh(["content", "backups", "network", "startup", "fs"]);
|
||||
});
|
||||
|
||||
provide("modulesLoaded", loadModulesPromise);
|
||||
|
||||
watch(
|
||||
() => [server.general?.error, server.ws?.error],
|
||||
() => [server.moduleErrors?.general, server.moduleErrors?.ws],
|
||||
([generalError, wsError]) => {
|
||||
if (server.general?.status === "suspended") return;
|
||||
|
||||
@ -497,7 +504,7 @@ const errorLogFile = ref("");
|
||||
const serverData = computed(() => server.general);
|
||||
const isConnected = ref(false);
|
||||
const isWSAuthIncorrect = ref(false);
|
||||
const pyroConsole = usePyroConsole();
|
||||
const modrinthServersConsole = useModrinthServersConsole();
|
||||
const cpuData = ref<number[]>([]);
|
||||
const ramData = ref<number[]>([]);
|
||||
const isActioning = ref(false);
|
||||
@ -671,7 +678,7 @@ const connectWebSocket = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
pyroConsole.clear();
|
||||
modrinthServersConsole.clear();
|
||||
socket.value?.send(JSON.stringify({ event: "auth", jwt: wsAuth.value?.token }));
|
||||
isConnected.value = true;
|
||||
isReconnecting.value = false;
|
||||
@ -679,7 +686,7 @@ const connectWebSocket = () => {
|
||||
|
||||
if (firstConnect.value) {
|
||||
for (let i = 0; i < initialConsoleMessage.length; i++) {
|
||||
pyroConsole.addLine(initialConsoleMessage[i]);
|
||||
modrinthServersConsole.addLine(initialConsoleMessage[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -702,7 +709,9 @@ const connectWebSocket = () => {
|
||||
|
||||
socket.value.onclose = () => {
|
||||
if (isMounted.value) {
|
||||
pyroConsole.addLine("\nSomething went wrong with the connection, we're reconnecting...");
|
||||
modrinthServersConsole.addLine(
|
||||
"\nSomething went wrong with the connection, we're reconnecting...",
|
||||
);
|
||||
isConnected.value = false;
|
||||
scheduleReconnect();
|
||||
}
|
||||
@ -760,7 +769,7 @@ const handleWebSocketMessage = (data: WSEvent) => {
|
||||
case "log":
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const log = data.message.split("\n").filter((l) => l.trim());
|
||||
pyroConsole.addLines(log);
|
||||
modrinthServersConsole.addLines(log);
|
||||
break;
|
||||
case "stats":
|
||||
updateStats(data);
|
||||
@ -1021,11 +1030,11 @@ const toAdverb = (word: string) => {
|
||||
return word + "ing";
|
||||
};
|
||||
|
||||
const sendPowerAction = async (action: "restart" | "start" | "stop" | "kill") => {
|
||||
const sendPowerAction = async (action: PowerAction) => {
|
||||
const actionName = action.charAt(0).toUpperCase() + action.slice(1);
|
||||
try {
|
||||
isActioning.value = true;
|
||||
await server.general?.power(actionName);
|
||||
await server.general?.power(action);
|
||||
} catch (error) {
|
||||
console.error(`Error ${toAdverb(actionName)} server:`, error);
|
||||
notifyError(
|
||||
@ -1158,7 +1167,7 @@ const cleanup = () => {
|
||||
};
|
||||
|
||||
async function dismissNotice(noticeId: number) {
|
||||
await usePyroFetch(`servers/${serverId}/notices/${noticeId}/dismiss`, {
|
||||
await useServersFetch(`servers/${serverId}/notices/${noticeId}/dismiss`, {
|
||||
method: "POST",
|
||||
}).catch((err) => {
|
||||
app.$notify({
|
||||
@ -1177,8 +1186,8 @@ onMounted(() => {
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
if (server.error) {
|
||||
if (!server.error.message.includes("Forbidden")) {
|
||||
if (server.moduleErrors.general?.error) {
|
||||
if (!server.moduleErrors.general?.error?.message?.includes("Forbidden")) {
|
||||
startPolling();
|
||||
}
|
||||
} else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="server.backups?.error"
|
||||
v-if="server.moduleErrors.backups"
|
||||
class="flex w-full flex-col items-center justify-center gap-4 p-4"
|
||||
>
|
||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||
@ -15,7 +15,9 @@
|
||||
We couldn't load your server's backups. Here's what went wrong:
|
||||
</p>
|
||||
<p>
|
||||
<span class="break-all font-mono">{{ JSON.stringify(server.backups.error) }}</span>
|
||||
<span class="break-all font-mono">{{
|
||||
JSON.stringify(server.moduleErrors.backups.error)
|
||||
}}</span>
|
||||
</p>
|
||||
<ButtonStyled size="large" color="brand" @click="() => server.refresh(['backups'])">
|
||||
<button class="mt-6 !w-full">Retry</button>
|
||||
@ -152,16 +154,17 @@ import { ButtonStyled, TagItem } from "@modrinth/ui";
|
||||
import { useStorage } from "@vueuse/core";
|
||||
import { SpinnerIcon, PlusIcon, DownloadIcon, SettingsIcon, IssuesIcon } from "@modrinth/assets";
|
||||
import { ref, computed } from "vue";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import type { Backup } from "@modrinth/utils";
|
||||
import BackupItem from "~/components/ui/servers/BackupItem.vue";
|
||||
import BackupRenameModal from "~/components/ui/servers/BackupRenameModal.vue";
|
||||
import BackupCreateModal from "~/components/ui/servers/BackupCreateModal.vue";
|
||||
import BackupRestoreModal from "~/components/ui/servers/BackupRestoreModal.vue";
|
||||
import BackupDeleteModal from "~/components/ui/servers/BackupDeleteModal.vue";
|
||||
import BackupSettingsModal from "~/components/ui/servers/BackupSettingsModal.vue";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
isServerRunning: boolean;
|
||||
}>();
|
||||
|
||||
|
@ -5,12 +5,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const route = useNativeRoute();
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const data = computed(() => props.server.general);
|
||||
|
@ -11,7 +11,7 @@
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="server.content?.error"
|
||||
v-if="server.moduleErrors.content"
|
||||
class="flex w-full flex-col items-center justify-center gap-4 p-4"
|
||||
>
|
||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||
@ -24,7 +24,9 @@
|
||||
</div>
|
||||
<p class="text-lg text-secondary">
|
||||
We couldn't load your server's {{ type.toLowerCase() }}s. Here's what we know:
|
||||
<span class="break-all font-mono">{{ JSON.stringify(server.content.error) }}</span>
|
||||
<span class="break-all font-mono">{{
|
||||
JSON.stringify(server.moduleErrors.content.error)
|
||||
}}</span>
|
||||
</p>
|
||||
<ButtonStyled size="large" color="brand" @click="() => server.refresh(['content'])">
|
||||
<button class="mt-6 !w-full">Retry</button>
|
||||
@ -349,13 +351,14 @@ import {
|
||||
} from "@modrinth/assets";
|
||||
import { ButtonStyled } from "@modrinth/ui";
|
||||
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
|
||||
import type { Mod } from "@modrinth/utils";
|
||||
import FilesUploadDragAndDrop from "~/components/ui/servers/FilesUploadDragAndDrop.vue";
|
||||
import FilesUploadDropdown from "~/components/ui/servers/FilesUploadDropdown.vue";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const type = computed(() => {
|
||||
|
@ -278,16 +278,11 @@ import {
|
||||
} from "@modrinth/assets";
|
||||
import { computed } from "vue";
|
||||
import { ButtonStyled, ProgressBar } from "@modrinth/ui";
|
||||
import { formatBytes } from "@modrinth/utils";
|
||||
import {
|
||||
type DirectoryResponse,
|
||||
type DirectoryItem,
|
||||
type Server,
|
||||
handleError,
|
||||
} from "~/composables/pyroServers.ts";
|
||||
import { formatBytes, ModrinthServersFetchError } from "@modrinth/utils";
|
||||
import type { FilesystemOp, FSQueuedOp, DirectoryItem, DirectoryResponse } from "@modrinth/utils";
|
||||
import { handleError, ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
import FilesUploadDragAndDrop from "~/components/ui/servers/FilesUploadDragAndDrop.vue";
|
||||
import FilesUploadDropdown from "~/components/ui/servers/FilesUploadDropdown.vue";
|
||||
import type { FilesystemOp, FSQueuedOp } from "~/types/servers.ts";
|
||||
import FilesUploadZipUrlModal from "~/components/ui/servers/FilesUploadZipUrlModal.vue";
|
||||
import FilesUploadConflictModal from "~/components/ui/servers/FilesUploadConflictModal.vue";
|
||||
|
||||
@ -316,7 +311,7 @@ interface RenameOperation extends BaseOperation {
|
||||
type Operation = MoveOperation | RenameOperation;
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const modulesLoaded = inject<Promise<void>>("modulesLoaded");
|
||||
@ -402,7 +397,7 @@ const fetchDirectoryContents = async (): Promise<DirectoryResponse> => {
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching directory contents:", error);
|
||||
if (error instanceof PyroFetchError && error.statusCode === 400) {
|
||||
if (error instanceof ModrinthServersFetchError && error.statusCode === 400) {
|
||||
return directoryData.value || { items: [], total: 0 };
|
||||
}
|
||||
throw error;
|
||||
@ -561,7 +556,7 @@ const handleRenameItem = async (newName: string) => {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error renaming item:", error);
|
||||
if (error instanceof PyroFetchError) {
|
||||
if (error instanceof ModrinthServersFetchError) {
|
||||
if (error.statusCode === 400) {
|
||||
addNotification({
|
||||
group: "files",
|
||||
@ -719,7 +714,7 @@ const showDeleteModal = (item: any) => {
|
||||
|
||||
const handleCreateError = (error: any) => {
|
||||
console.error("Error creating item:", error);
|
||||
if (error instanceof PyroFetchError) {
|
||||
if (error instanceof ModrinthServersFetchError) {
|
||||
if (error.statusCode === 400) {
|
||||
addNotification({
|
||||
group: "files",
|
||||
|
@ -189,8 +189,8 @@
|
||||
<script setup lang="ts">
|
||||
import { TerminalSquareIcon, XIcon, IssuesIcon } from "@modrinth/assets";
|
||||
import { ButtonStyled } from "@modrinth/ui";
|
||||
import type { ServerState, Stats } from "~/types/servers";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import type { ServerState, Stats } from "@modrinth/utils";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
type ServerProps = {
|
||||
socket: WebSocket | null;
|
||||
@ -203,7 +203,7 @@ type ServerProps = {
|
||||
exit_code?: number;
|
||||
};
|
||||
isServerRunning: boolean;
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
};
|
||||
|
||||
const props = defineProps<ServerProps>();
|
||||
|
@ -17,14 +17,14 @@ import {
|
||||
UserIcon,
|
||||
WrenchIcon,
|
||||
} from "@modrinth/assets";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
||||
|
||||
const route = useRoute();
|
||||
const serverId = route.params.id as string;
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
backupInProgress?: BackupInProgressReason;
|
||||
}>();
|
||||
|
||||
|
@ -114,11 +114,10 @@
|
||||
<script setup lang="ts">
|
||||
import { EditIcon, TransferIcon } from "@modrinth/assets";
|
||||
import ButtonStyled from "@modrinth/ui/src/components/base/ButtonStyled.vue";
|
||||
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const data = computed(() => props.server.general);
|
||||
|
@ -117,13 +117,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ButtonStyled } from "@modrinth/ui";
|
||||
import { CopyIcon, ExternalIcon, EyeIcon, EyeOffIcon } from "@modrinth/assets";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const route = useNativeRoute();
|
||||
const serverId = route.params.id as string;
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const data = computed(() => props.server.general);
|
||||
|
@ -7,12 +7,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
||||
import ServerInstallation from "~/components/ui/servers/ServerInstallation.vue";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
backupInProgress?: BackupInProgressReason;
|
||||
}>();
|
||||
|
||||
|
@ -60,7 +60,7 @@
|
||||
|
||||
<div class="relative h-full w-full overflow-y-auto">
|
||||
<div
|
||||
v-if="server.network?.error"
|
||||
v-if="server.moduleErrors.network"
|
||||
class="flex w-full flex-col items-center justify-center gap-4 p-4"
|
||||
>
|
||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||
@ -73,7 +73,9 @@
|
||||
</div>
|
||||
<p class="text-lg text-secondary">
|
||||
We couldn't load your server's network settings. Here's what we know:
|
||||
<span class="break-all font-mono">{{ JSON.stringify(server.network.error) }}</span>
|
||||
<span class="break-all font-mono">{{
|
||||
JSON.stringify(server.moduleErrors.network.error)
|
||||
}}</span>
|
||||
</p>
|
||||
<ButtonStyled size="large" color="brand" @click="() => server.refresh(['network'])">
|
||||
<button class="mt-6 !w-full">Retry</button>
|
||||
@ -273,10 +275,10 @@ import {
|
||||
} from "@modrinth/assets";
|
||||
import { ButtonStyled, NewModal, ConfirmModal } from "@modrinth/ui";
|
||||
import { ref, computed, nextTick } from "vue";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const isUpdating = ref(false);
|
||||
|
@ -43,13 +43,13 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useStorage } from "@vueuse/core";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const route = useNativeRoute();
|
||||
const serverId = route.params.id as string;
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const preferences = {
|
||||
|
@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div class="relative h-full w-full select-none overflow-y-auto">
|
||||
<div v-if="server.fs?.error" class="flex w-full flex-col items-center justify-center gap-4 p-4">
|
||||
<div
|
||||
v-if="server.moduleErrors.fs"
|
||||
class="flex w-full flex-col items-center justify-center gap-4 p-4"
|
||||
>
|
||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||
<div class="flex flex-col items-center text-center">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
@ -11,7 +14,9 @@
|
||||
</div>
|
||||
<p class="text-lg text-secondary">
|
||||
We couldn't access your server's properties. Here's what we know:
|
||||
<span class="break-all font-mono">{{ JSON.stringify(server.fs.error) }}</span>
|
||||
<span class="break-all font-mono">{{
|
||||
JSON.stringify(server.moduleErrors.fs.error)
|
||||
}}</span>
|
||||
</p>
|
||||
<ButtonStyled size="large" color="brand" @click="() => server.refresh(['fs'])">
|
||||
<button class="mt-6 !w-full">Retry</button>
|
||||
@ -141,10 +146,10 @@
|
||||
import { ref, watch, computed, inject } from "vue";
|
||||
import { EyeIcon, SearchIcon, IssuesIcon } from "@modrinth/assets";
|
||||
import Fuse from "fuse.js";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const tags = useTags();
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="relative h-full w-full">
|
||||
<div
|
||||
v-if="server.startup?.error"
|
||||
v-if="server.moduleErrors.startup"
|
||||
class="flex w-full flex-col items-center justify-center gap-4 p-4"
|
||||
>
|
||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||
@ -16,7 +16,9 @@
|
||||
We couldn't load your server's startup settings. Here's what we know:
|
||||
</p>
|
||||
<p>
|
||||
<span class="break-all font-mono">{{ JSON.stringify(server.startup.error) }}</span>
|
||||
<span class="break-all font-mono">{{
|
||||
JSON.stringify(server.moduleErrors.startup.error)
|
||||
}}</span>
|
||||
</p>
|
||||
<ButtonStyled size="large" color="brand" @click="() => server.refresh(['startup'])">
|
||||
<button class="mt-6 !w-full">Retry</button>
|
||||
@ -112,10 +114,10 @@
|
||||
<script setup lang="ts">
|
||||
import { UpdatedIcon, IssuesIcon } from "@modrinth/assets";
|
||||
import { ButtonStyled } from "@modrinth/ui";
|
||||
import type { Server } from "~/composables/pyroServers";
|
||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||
server: ModrinthServer;
|
||||
}>();
|
||||
|
||||
const data = computed(() => props.server.general);
|
||||
|
@ -36,7 +36,7 @@
|
||||
<li v-if="fetchError" class="text-red">
|
||||
<p>Error details:</p>
|
||||
<UiCopyCode
|
||||
:text="(fetchError as PyroFetchError).message || 'Unknown error'"
|
||||
:text="(fetchError as ModrinthServersFetchError).message || 'Unknown error'"
|
||||
:copyable="false"
|
||||
:selectable="false"
|
||||
:language="'json'"
|
||||
@ -121,9 +121,9 @@ import { ref, computed, onMounted, onUnmounted, watch } from "vue";
|
||||
import Fuse from "fuse.js";
|
||||
import { HammerIcon, PlusIcon, SearchIcon } from "@modrinth/assets";
|
||||
import { ButtonStyled } from "@modrinth/ui";
|
||||
import type { Server, ModrinthServersFetchError } from "@modrinth/utils";
|
||||
import { reloadNuxtApp } from "#app";
|
||||
import type { PyroFetchError } from "~/composables/pyroFetch";
|
||||
import type { Server } from "~/types/servers";
|
||||
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth",
|
||||
@ -146,7 +146,9 @@ const {
|
||||
data: serverResponse,
|
||||
error: fetchError,
|
||||
refresh,
|
||||
} = await useAsyncData<ServerResponse>("ServerList", () => usePyroFetch<ServerResponse>("servers"));
|
||||
} = await useAsyncData<ServerResponse>("ServerList", () =>
|
||||
useServersFetch<ServerResponse>("servers"),
|
||||
);
|
||||
|
||||
watch([fetchError, serverResponse], ([error, response]) => {
|
||||
hasError.value = !!error || !response;
|
||||
|
@ -592,6 +592,7 @@ import {
|
||||
} from "@modrinth/assets";
|
||||
import { calculateSavings, formatPrice, getCurrency } from "@modrinth/utils";
|
||||
import { ref, computed } from "vue";
|
||||
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
||||
import { products } from "~/generated/state.json";
|
||||
|
||||
definePageMeta({
|
||||
@ -744,7 +745,7 @@ const [
|
||||
useBaseFetch("billing/subscriptions", { internal: true }),
|
||||
),
|
||||
useAsyncData("billing/products", () => useBaseFetch("billing/products", { internal: true })),
|
||||
useAsyncData("servers", () => usePyroFetch("servers")),
|
||||
useAsyncData("servers", () => useServersFetch("servers")),
|
||||
]);
|
||||
|
||||
const midasProduct = ref(products.find((x) => x.metadata?.type === "midas"));
|
||||
@ -984,7 +985,7 @@ async function fetchCapacityStatuses(serverId, product) {
|
||||
if (product) {
|
||||
try {
|
||||
return {
|
||||
custom: await usePyroFetch(`servers/${serverId}/upgrade-stock`, {
|
||||
custom: await useServersFetch(`servers/${serverId}/upgrade-stock`, {
|
||||
method: "POST",
|
||||
body: {
|
||||
cpu: product.metadata.cpu,
|
||||
|
@ -18,7 +18,7 @@ const initialBatchSize = 256;
|
||||
* @property {function(string): void} addConsoleOutput - Method to add a new console output line
|
||||
* @property {function(): void} clear - Method to clear all console output
|
||||
*/
|
||||
export const usePyroConsole = createGlobalState(() => {
|
||||
export const useModrinthServersConsole = createGlobalState(() => {
|
||||
/**
|
||||
* Reactive array storing console output lines
|
||||
* @type {Ref<string[]>}
|
||||
|
@ -1,284 +0,0 @@
|
||||
// export interface Mod {
|
||||
// id: string;
|
||||
// filename: string;
|
||||
// modrinth_ids: {
|
||||
// project_id: string;
|
||||
// version_id: string;
|
||||
// };
|
||||
// }
|
||||
|
||||
interface License {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface DonationUrl {
|
||||
id: string;
|
||||
platform: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface GalleryItem {
|
||||
url: string;
|
||||
featured: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
created: string;
|
||||
ordering: number;
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
categories: string[];
|
||||
client_side: "required" | "optional";
|
||||
server_side: "required" | "optional";
|
||||
body: string;
|
||||
status: "approved" | "pending" | "rejected";
|
||||
requested_status: "approved" | "pending" | "rejected";
|
||||
additional_categories: string[];
|
||||
issues_url: string;
|
||||
source_url: string;
|
||||
wiki_url: string;
|
||||
discord_url: string;
|
||||
donation_urls: DonationUrl[];
|
||||
project_type: "mod" | "resourcepack" | "map" | "plugin";
|
||||
downloads: number;
|
||||
icon_url: string;
|
||||
color: number;
|
||||
thread_id: string;
|
||||
monetization_status: "monetized" | "non-monetized";
|
||||
id: string;
|
||||
team: string;
|
||||
body_url: string | null;
|
||||
moderator_message: string | null;
|
||||
published: string;
|
||||
updated: string;
|
||||
approved: string;
|
||||
queued: string;
|
||||
followers: number;
|
||||
license: License;
|
||||
versions: string[];
|
||||
game_versions: string[];
|
||||
loaders: string[];
|
||||
gallery: GalleryItem[];
|
||||
}
|
||||
|
||||
export interface ServerBackup {
|
||||
id: string;
|
||||
name: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface Allocation {
|
||||
name: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface Server {
|
||||
server_id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
net: {
|
||||
ip: string;
|
||||
port: number;
|
||||
domain: string;
|
||||
allocations: Allocation[];
|
||||
};
|
||||
game: string;
|
||||
loader: string | null;
|
||||
loader_version: string | null;
|
||||
mc_version: string | null;
|
||||
backup_quota: number;
|
||||
used_backup_quota: number;
|
||||
backups: ServerBackup[];
|
||||
mods: Mod[];
|
||||
project: Project | null;
|
||||
suspension_reason: string | null;
|
||||
image: string | null;
|
||||
upstream?: {
|
||||
kind: "modpack";
|
||||
project_id: string;
|
||||
version_id: string;
|
||||
};
|
||||
motd: string;
|
||||
flows: {
|
||||
intro?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
current: {
|
||||
cpu_percent: number;
|
||||
ram_usage_bytes: number;
|
||||
ram_total_bytes: number;
|
||||
storage_usage_bytes: number;
|
||||
storage_total_bytes: number;
|
||||
};
|
||||
past: {
|
||||
cpu_percent: number;
|
||||
ram_usage_bytes: number;
|
||||
ram_total_bytes: number;
|
||||
storage_usage_bytes: number;
|
||||
storage_total_bytes: number;
|
||||
};
|
||||
graph: {
|
||||
cpu: number[];
|
||||
ram: number[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface WSAuth {
|
||||
url: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export type ServerState = "running" | "stopped" | "crashed";
|
||||
// export type WebsocketEventType =
|
||||
// | "log"
|
||||
// | "auth"
|
||||
// | "stats"
|
||||
// | "power-state"
|
||||
// | "auth-expiring"
|
||||
// | "auth-incorrect"
|
||||
// | "installation-result"
|
||||
// | (string & {});
|
||||
|
||||
// export interface WSEvent {
|
||||
// event: WebsocketEventType;
|
||||
// message: string;
|
||||
// state: ServerState;
|
||||
// }
|
||||
|
||||
export type Loaders =
|
||||
| "Fabric"
|
||||
| "Quilt"
|
||||
| "Forge"
|
||||
| "NeoForge"
|
||||
| "Paper"
|
||||
| "Spigot"
|
||||
| "Bukkit"
|
||||
| "Vanilla"
|
||||
| "Purpur";
|
||||
|
||||
export interface WSLogEvent {
|
||||
event: "log";
|
||||
message: string;
|
||||
}
|
||||
|
||||
type CurrentStats = Stats["current"];
|
||||
|
||||
export interface WSStatsEvent extends CurrentStats {
|
||||
event: "stats";
|
||||
}
|
||||
|
||||
export interface WSAuthExpiringEvent {
|
||||
event: "auth-expiring";
|
||||
}
|
||||
|
||||
export interface WSPowerStateEvent {
|
||||
event: "power-state";
|
||||
state: ServerState;
|
||||
// if state "crashed"
|
||||
oom_killed?: boolean;
|
||||
exit_code?: number;
|
||||
}
|
||||
|
||||
export interface WSAuthIncorrectEvent {
|
||||
event: "auth-incorrect";
|
||||
}
|
||||
|
||||
export interface WSInstallationResultOkEvent {
|
||||
event: "installation-result";
|
||||
result: "ok";
|
||||
}
|
||||
|
||||
export interface WSInstallationResultErrEvent {
|
||||
event: "installation-result";
|
||||
result: "err";
|
||||
reason: string;
|
||||
}
|
||||
export type WSInstallationResultEvent = WSInstallationResultOkEvent | WSInstallationResultErrEvent;
|
||||
|
||||
export interface WSAuthOkEvent {
|
||||
event: "auth-ok";
|
||||
}
|
||||
|
||||
export interface WSUptimeEvent {
|
||||
event: "uptime";
|
||||
uptime: number; // seconds
|
||||
}
|
||||
|
||||
export interface WSNewModEvent {
|
||||
event: "new-mod";
|
||||
}
|
||||
|
||||
export type WSBackupTask = "file" | "create" | "restore";
|
||||
export type WSBackupState = "ongoing" | "done" | "failed" | "cancelled" | "unchanged";
|
||||
|
||||
export interface WSBackupProgressEvent {
|
||||
event: "backup-progress";
|
||||
task: WSBackupTask;
|
||||
id: string;
|
||||
progress: number; // percentage
|
||||
state: WSBackupState;
|
||||
ready: boolean;
|
||||
}
|
||||
|
||||
export type FSQueuedOpUnarchive = {
|
||||
op: "unarchive";
|
||||
src: string;
|
||||
};
|
||||
|
||||
export type FSQueuedOp = FSQueuedOpUnarchive;
|
||||
|
||||
export type FSOpUnarchive = {
|
||||
op: "unarchive";
|
||||
progress: number; // Note: 1 does not mean it's done
|
||||
id: string; // UUID
|
||||
|
||||
mime: string;
|
||||
src: string;
|
||||
state:
|
||||
| "queued"
|
||||
| "ongoing"
|
||||
| "cancelled"
|
||||
| "done"
|
||||
| "failed-corrupted"
|
||||
| "failed-invalid-path"
|
||||
| "failed-cf-no-serverpack"
|
||||
| "failed-cf-not-available"
|
||||
| "failed-not-reachable";
|
||||
|
||||
current_file: string | null;
|
||||
failed_path?: string;
|
||||
bytes_processed: number;
|
||||
files_processed: number;
|
||||
started: string;
|
||||
};
|
||||
|
||||
export type FilesystemOp = FSOpUnarchive;
|
||||
|
||||
export interface WSFilesystemOpsEvent {
|
||||
event: "filesystem-ops";
|
||||
all: FilesystemOp[];
|
||||
}
|
||||
|
||||
export type WSEvent =
|
||||
| WSLogEvent
|
||||
| WSStatsEvent
|
||||
| WSPowerStateEvent
|
||||
| WSAuthExpiringEvent
|
||||
| WSAuthIncorrectEvent
|
||||
| WSInstallationResultEvent
|
||||
| WSAuthOkEvent
|
||||
| WSUptimeEvent
|
||||
| WSNewModEvent
|
||||
| WSBackupProgressEvent
|
||||
| WSFilesystemOpsEvent;
|
||||
|
||||
export interface Servers {
|
||||
servers: Server[];
|
||||
}
|
@ -46,7 +46,7 @@ export const NOTICE_LEVELS: Record<
|
||||
},
|
||||
}
|
||||
|
||||
const DISMISSABLE = {
|
||||
export const DISMISSABLE = {
|
||||
name: defineMessage({
|
||||
id: 'servers.notice.dismissable',
|
||||
defaultMessage: 'Dismissable',
|
||||
@ -57,7 +57,7 @@ const DISMISSABLE = {
|
||||
},
|
||||
}
|
||||
|
||||
const UNDISMISSABLE = {
|
||||
export const UNDISMISSABLE = {
|
||||
name: defineMessage({
|
||||
id: 'servers.notice.undismissable',
|
||||
defaultMessage: 'Undismissable',
|
||||
|
@ -7,3 +7,4 @@ export * from './projects'
|
||||
export * from './types'
|
||||
export * from './users'
|
||||
export * from './utils'
|
||||
export * from './servers'
|
||||
|
@ -23,6 +23,7 @@
|
||||
"dayjs": "^1.11.10",
|
||||
"highlight.js": "^11.9.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"ofetch": "^1.3.4",
|
||||
"xss": "^1.0.14"
|
||||
}
|
||||
}
|
||||
|
3
packages/utils/servers/errors/index.ts
Normal file
3
packages/utils/servers/errors/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './modrinth-servers-multi-error'
|
||||
export * from './modrinth-servers-fetch-error'
|
||||
export * from './modrinth-server-error'
|
59
packages/utils/servers/errors/modrinth-server-error.ts
Normal file
59
packages/utils/servers/errors/modrinth-server-error.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { FetchError } from 'ofetch'
|
||||
import { V1ErrorInfo } from '../types'
|
||||
|
||||
export class ModrinthServerError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly statusCode?: number,
|
||||
public readonly originalError?: Error,
|
||||
public readonly module?: string,
|
||||
public readonly v1Error?: V1ErrorInfo,
|
||||
) {
|
||||
let errorMessage = message
|
||||
let method = 'GET'
|
||||
let path = ''
|
||||
|
||||
if (originalError instanceof FetchError) {
|
||||
const matches = message.match(/\[([A-Z]+)\]\s+"([^"]+)":/)
|
||||
if (matches) {
|
||||
method = matches[1]
|
||||
path = matches[2].replace(/https?:\/\/[^/]+\/[^/]+\/v\d+\//, '')
|
||||
}
|
||||
|
||||
const statusMessage = (() => {
|
||||
if (!statusCode) return 'Unknown Error'
|
||||
switch (statusCode) {
|
||||
case 400:
|
||||
return 'Bad Request'
|
||||
case 401:
|
||||
return 'Unauthorized'
|
||||
case 403:
|
||||
return 'Forbidden'
|
||||
case 404:
|
||||
return 'Not Found'
|
||||
case 408:
|
||||
return 'Request Timeout'
|
||||
case 429:
|
||||
return 'Too Many Requests'
|
||||
case 500:
|
||||
return 'Internal Server Error'
|
||||
case 502:
|
||||
return 'Bad Gateway'
|
||||
case 503:
|
||||
return 'Service Unavailable'
|
||||
case 504:
|
||||
return 'Gateway Timeout'
|
||||
default:
|
||||
return `HTTP ${statusCode}`
|
||||
}
|
||||
})()
|
||||
|
||||
errorMessage = `[${method}] ${statusMessage} (${statusCode}) while fetching ${path}${module ? ` in ${module}` : ''}`
|
||||
} else {
|
||||
errorMessage = `${message}${statusCode ? ` (${statusCode})` : ''}${module ? ` in ${module}` : ''}`
|
||||
}
|
||||
|
||||
super(errorMessage)
|
||||
this.name = 'PyroServersFetchError'
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
export class ModrinthServersFetchError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public statusCode?: number,
|
||||
public originalError?: Error,
|
||||
) {
|
||||
super(message)
|
||||
this.name = 'PyroFetchError'
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
export class ModrinthServersMultiError extends Error {
|
||||
public readonly errors: Map<string, Error> = new Map()
|
||||
public readonly timestamp: number = Date.now()
|
||||
|
||||
constructor(message?: string) {
|
||||
super(message || 'Multiple errors occurred')
|
||||
this.name = 'MultipleErrors'
|
||||
}
|
||||
|
||||
addError(module: string, error: Error) {
|
||||
this.errors.set(module, error)
|
||||
this.message = this.buildErrorMessage()
|
||||
}
|
||||
|
||||
hasErrors() {
|
||||
return this.errors.size > 0
|
||||
}
|
||||
|
||||
private buildErrorMessage(): string {
|
||||
return (
|
||||
Array.from(this.errors.entries())
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.map(([_module, error]) => error.message)
|
||||
.join('\n')
|
||||
)
|
||||
}
|
||||
}
|
2
packages/utils/servers/index.ts
Normal file
2
packages/utils/servers/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './errors'
|
||||
export * from './types'
|
19
packages/utils/servers/types/api.ts
Normal file
19
packages/utils/servers/types/api.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { ModrinthServerError } from '../errors'
|
||||
|
||||
export interface V1ErrorInfo {
|
||||
context?: string
|
||||
error: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface JWTAuth {
|
||||
url: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export interface ModuleError {
|
||||
error: ModrinthServerError
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export type ModuleName = 'general' | 'content' | 'backups' | 'network' | 'startup' | 'ws' | 'fs'
|
28
packages/utils/servers/types/backup.ts
Normal file
28
packages/utils/servers/types/backup.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import type { WSBackupTask, WSBackupState } from './websocket'
|
||||
|
||||
export interface Backup {
|
||||
id: string
|
||||
name: string
|
||||
created_at: string
|
||||
locked: boolean
|
||||
automated: boolean
|
||||
interrupted: boolean
|
||||
ongoing: boolean
|
||||
task: {
|
||||
[K in WSBackupTask]?: {
|
||||
progress: number
|
||||
state: WSBackupState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface AutoBackupSettings {
|
||||
enabled: boolean
|
||||
interval: number
|
||||
}
|
||||
|
||||
export interface ServerBackup {
|
||||
id: string
|
||||
name: string
|
||||
created_at: string
|
||||
}
|
59
packages/utils/servers/types/common.ts
Normal file
59
packages/utils/servers/types/common.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import type { Project } from '../../types'
|
||||
import { Allocation } from './server'
|
||||
import { ServerBackup } from './backup'
|
||||
import { Mod } from './content'
|
||||
|
||||
export type ServerNotice = {
|
||||
id: number
|
||||
message: string
|
||||
title?: string
|
||||
level: 'info' | 'warn' | 'critical' | 'survey'
|
||||
dismissable: boolean
|
||||
announce_at: string
|
||||
expires: string
|
||||
assigned: {
|
||||
kind: 'server' | 'node'
|
||||
id: string
|
||||
name: string
|
||||
}[]
|
||||
dismissed_by: {
|
||||
server: string
|
||||
dismissed_on: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface Server {
|
||||
server_id: string
|
||||
name: string
|
||||
status: string
|
||||
net: {
|
||||
ip: string
|
||||
port: number
|
||||
domain: string
|
||||
allocations: Allocation[]
|
||||
}
|
||||
game: string
|
||||
loader: string | null
|
||||
loader_version: string | null
|
||||
mc_version: string | null
|
||||
backup_quota: number
|
||||
used_backup_quota: number
|
||||
backups: ServerBackup[]
|
||||
mods: Mod[]
|
||||
project: Project | null
|
||||
suspension_reason: string | null
|
||||
image: string | null
|
||||
upstream?: {
|
||||
kind: 'modpack'
|
||||
project_id: string
|
||||
version_id: string
|
||||
}
|
||||
motd: string
|
||||
flows: {
|
||||
intro?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface Servers {
|
||||
servers: Server[]
|
||||
}
|
13
packages/utils/servers/types/content.ts
Normal file
13
packages/utils/servers/types/content.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export interface Mod {
|
||||
filename: string
|
||||
project_id: string | undefined
|
||||
version_id: string | undefined
|
||||
name: string | undefined
|
||||
version_number: string | undefined
|
||||
icon_url: string | undefined
|
||||
owner: string | undefined
|
||||
disabled: boolean
|
||||
installing: boolean
|
||||
}
|
||||
|
||||
export type ContentType = 'mod' | 'plugin'
|
33
packages/utils/servers/types/filesystem.ts
Normal file
33
packages/utils/servers/types/filesystem.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import type { FSQueuedOp, FilesystemOp } from './websocket'
|
||||
import { JWTAuth } from './api'
|
||||
|
||||
export interface DirectoryItem {
|
||||
name: string
|
||||
type: 'directory' | 'file'
|
||||
count?: number
|
||||
modified: number
|
||||
created: number
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface FileUploadQuery {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
promise: Promise<any>
|
||||
onProgress: (
|
||||
callback: (progress: { loaded: number; total: number; progress: number }) => void,
|
||||
) => void
|
||||
cancel: () => void
|
||||
}
|
||||
|
||||
export interface DirectoryResponse {
|
||||
items: DirectoryItem[]
|
||||
total: number
|
||||
current?: number
|
||||
}
|
||||
|
||||
export interface FSModule {
|
||||
auth: JWTAuth
|
||||
ops: FilesystemOp[]
|
||||
queuedOps: FSQueuedOp[]
|
||||
opsQueuedForModification: string[]
|
||||
}
|
8
packages/utils/servers/types/index.ts
Normal file
8
packages/utils/servers/types/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from './api'
|
||||
export * from './content'
|
||||
export * from './server'
|
||||
export * from './backup'
|
||||
export * from './filesystem'
|
||||
export * from './websocket'
|
||||
export * from './stats'
|
||||
export * from './common'
|
76
packages/utils/servers/types/server.ts
Normal file
76
packages/utils/servers/types/server.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import type { Project } from '../../types'
|
||||
import type { ServerNotice } from './common'
|
||||
|
||||
export interface ServerGeneral {
|
||||
server_id: string
|
||||
name: string
|
||||
net: {
|
||||
ip: string
|
||||
port: number
|
||||
domain: string
|
||||
}
|
||||
game: string
|
||||
backup_quota: number
|
||||
used_backup_quota: number
|
||||
status: string
|
||||
suspension_reason: 'moderated' | 'paymentfailed' | 'cancelled' | 'upgrading' | 'other' | string
|
||||
loader: string
|
||||
loader_version: string
|
||||
mc_version: string
|
||||
upstream: {
|
||||
kind: 'modpack' | 'mod' | 'resourcepack'
|
||||
version_id: string
|
||||
project_id: string
|
||||
} | null
|
||||
motd?: string
|
||||
image?: string
|
||||
project?: Project
|
||||
sftp_username: string
|
||||
sftp_password: string
|
||||
sftp_host: string
|
||||
datacenter?: string
|
||||
notices?: ServerNotice[]
|
||||
node: {
|
||||
token: string
|
||||
instance: string
|
||||
}
|
||||
flows?: {
|
||||
intro?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface Allocation {
|
||||
port: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface Startup {
|
||||
invocation: string
|
||||
original_invocation: string
|
||||
jdk_version: 'lts8' | 'lts11' | 'lts17' | 'lts21'
|
||||
jdk_build: 'corretto' | 'temurin' | 'graal'
|
||||
}
|
||||
|
||||
export type PowerAction = 'Start' | 'Stop' | 'Restart' | 'Kill'
|
||||
export type JDKVersion = 'lts8' | 'lts11' | 'lts17' | 'lts21'
|
||||
export type JDKBuild = 'corretto' | 'temurin' | 'graal'
|
||||
|
||||
export type Loaders =
|
||||
| 'Fabric'
|
||||
| 'Quilt'
|
||||
| 'Forge'
|
||||
| 'NeoForge'
|
||||
| 'Paper'
|
||||
| 'Spigot'
|
||||
| 'Bukkit'
|
||||
| 'Vanilla'
|
||||
| 'Purpur'
|
||||
|
||||
export type ServerState =
|
||||
| 'starting'
|
||||
| 'running'
|
||||
| 'restarting'
|
||||
| 'stopping'
|
||||
| 'stopped'
|
||||
| 'crashed'
|
||||
| 'unknown'
|
20
packages/utils/servers/types/stats.ts
Normal file
20
packages/utils/servers/types/stats.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export interface Stats {
|
||||
current: {
|
||||
cpu_percent: number
|
||||
ram_usage_bytes: number
|
||||
ram_total_bytes: number
|
||||
storage_usage_bytes: number
|
||||
storage_total_bytes: number
|
||||
}
|
||||
past: {
|
||||
cpu_percent: number
|
||||
ram_usage_bytes: number
|
||||
ram_total_bytes: number
|
||||
storage_usage_bytes: number
|
||||
storage_total_bytes: number
|
||||
}
|
||||
graph: {
|
||||
cpu: number[]
|
||||
ram: number[]
|
||||
}
|
||||
}
|
124
packages/utils/servers/types/websocket.ts
Normal file
124
packages/utils/servers/types/websocket.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import type { Stats } from './stats'
|
||||
import type { ServerState } from './server'
|
||||
|
||||
export interface WSAuth {
|
||||
url: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export interface WSLogEvent {
|
||||
event: 'log'
|
||||
message: string
|
||||
}
|
||||
|
||||
type CurrentStats = Stats['current']
|
||||
|
||||
export interface WSStatsEvent extends CurrentStats {
|
||||
event: 'stats'
|
||||
}
|
||||
|
||||
export interface WSAuthExpiringEvent {
|
||||
event: 'auth-expiring'
|
||||
}
|
||||
|
||||
export interface WSPowerStateEvent {
|
||||
event: 'power-state'
|
||||
state: ServerState
|
||||
// if state "crashed"
|
||||
oom_killed?: boolean
|
||||
exit_code?: number
|
||||
}
|
||||
|
||||
export interface WSAuthIncorrectEvent {
|
||||
event: 'auth-incorrect'
|
||||
}
|
||||
|
||||
export interface WSInstallationResultOkEvent {
|
||||
event: 'installation-result'
|
||||
result: 'ok'
|
||||
}
|
||||
|
||||
export interface WSInstallationResultErrEvent {
|
||||
event: 'installation-result'
|
||||
result: 'err'
|
||||
reason: string
|
||||
}
|
||||
|
||||
export type WSInstallationResultEvent = WSInstallationResultOkEvent | WSInstallationResultErrEvent
|
||||
|
||||
export interface WSAuthOkEvent {
|
||||
event: 'auth-ok'
|
||||
}
|
||||
|
||||
export interface WSUptimeEvent {
|
||||
event: 'uptime'
|
||||
uptime: number // seconds
|
||||
}
|
||||
|
||||
export interface WSNewModEvent {
|
||||
event: 'new-mod'
|
||||
}
|
||||
|
||||
export type WSBackupTask = 'file' | 'create' | 'restore'
|
||||
export type WSBackupState = 'ongoing' | 'done' | 'failed' | 'cancelled' | 'unchanged'
|
||||
|
||||
export interface WSBackupProgressEvent {
|
||||
event: 'backup-progress'
|
||||
task: WSBackupTask
|
||||
id: string
|
||||
progress: number // percentage
|
||||
state: WSBackupState
|
||||
ready: boolean
|
||||
}
|
||||
|
||||
export type FSQueuedOpUnarchive = {
|
||||
op: 'unarchive'
|
||||
src: string
|
||||
}
|
||||
|
||||
export type FSQueuedOp = FSQueuedOpUnarchive
|
||||
|
||||
export type FSOpUnarchive = {
|
||||
op: 'unarchive'
|
||||
progress: number // Note: 1 does not mean it's done
|
||||
id: string // UUID
|
||||
|
||||
mime: string
|
||||
src: string
|
||||
state:
|
||||
| 'queued'
|
||||
| 'ongoing'
|
||||
| 'cancelled'
|
||||
| 'done'
|
||||
| 'failed-corrupted'
|
||||
| 'failed-invalid-path'
|
||||
| 'failed-cf-no-serverpack'
|
||||
| 'failed-cf-not-available'
|
||||
| 'failed-not-reachable'
|
||||
|
||||
current_file: string | null
|
||||
failed_path?: string
|
||||
bytes_processed: number
|
||||
files_processed: number
|
||||
started: string
|
||||
}
|
||||
|
||||
export type FilesystemOp = FSOpUnarchive
|
||||
|
||||
export interface WSFilesystemOpsEvent {
|
||||
event: 'filesystem-ops'
|
||||
all: FilesystemOp[]
|
||||
}
|
||||
|
||||
export type WSEvent =
|
||||
| WSLogEvent
|
||||
| WSStatsEvent
|
||||
| WSPowerStateEvent
|
||||
| WSAuthExpiringEvent
|
||||
| WSAuthIncorrectEvent
|
||||
| WSInstallationResultEvent
|
||||
| WSAuthOkEvent
|
||||
| WSUptimeEvent
|
||||
| WSNewModEvent
|
||||
| WSBackupProgressEvent
|
||||
| WSFilesystemOpsEvent
|
@ -294,22 +294,3 @@ export type Report = {
|
||||
created: string
|
||||
body: string
|
||||
}
|
||||
|
||||
export type ServerNotice = {
|
||||
id: number
|
||||
message: string
|
||||
title?: string
|
||||
level: 'info' | 'warn' | 'critical' | 'survey'
|
||||
dismissable: boolean
|
||||
announce_at: string
|
||||
expires: string
|
||||
assigned: {
|
||||
kind: 'server' | 'node'
|
||||
id: string
|
||||
name: string
|
||||
}[]
|
||||
dismissed_by: {
|
||||
server: string
|
||||
dismissed_on: string
|
||||
}[]
|
||||
}
|
||||
|
31
pnpm-lock.yaml
generated
31
pnpm-lock.yaml
generated
@ -520,6 +520,9 @@ importers:
|
||||
markdown-it:
|
||||
specifier: ^14.1.0
|
||||
version: 14.1.0
|
||||
ofetch:
|
||||
specifier: ^1.3.4
|
||||
version: 1.4.1
|
||||
xss:
|
||||
specifier: ^1.0.14
|
||||
version: 1.0.15
|
||||
@ -9456,7 +9459,7 @@ snapshots:
|
||||
|
||||
'@nuxtjs/eslint-config-typescript@12.1.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)':
|
||||
dependencies:
|
||||
'@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
|
||||
'@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
@ -9469,10 +9472,10 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))':
|
||||
'@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))':
|
||||
dependencies:
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-n: 15.7.0(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-node: 11.1.0(eslint@9.13.0(jiti@2.4.1))
|
||||
@ -12075,10 +12078,10 @@ snapshots:
|
||||
dependencies:
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
|
||||
eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1)):
|
||||
eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1)):
|
||||
dependencies:
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-n: 15.7.0(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-promise: 6.4.0(eslint@9.13.0(jiti@2.4.1))
|
||||
|
||||
@ -12104,7 +12107,7 @@ snapshots:
|
||||
debug: 4.4.0(supports-color@9.4.0)
|
||||
enhanced-resolve: 5.17.1
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.1))
|
||||
fast-glob: 3.3.2
|
||||
get-tsconfig: 4.7.5
|
||||
@ -12116,7 +12119,7 @@ snapshots:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1)):
|
||||
eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
@ -12127,6 +12130,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.8.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.13.0(jiti@2.4.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 7.16.1(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-es@3.0.1(eslint@9.13.0(jiti@2.4.1)):
|
||||
dependencies:
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
@ -12166,7 +12179,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.13.0(jiti@2.4.1))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.15.0
|
||||
is-glob: 4.0.3
|
||||
@ -12193,7 +12206,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.13.0(jiti@2.4.1))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.15.0
|
||||
is-glob: 4.0.3
|
||||
|
Loading…
x
Reference in New Issue
Block a user