Paper and Purpur + Backups (#3004)
* feat: init selecting paper+purpur on purchase flow Signed-off-by: Evan Song <theevansong@gmail.com> * feat: properly implement Paper/Purpur in Platform Signed-off-by: Evan Song <theevansong@gmail.com> * chore: correct wording Signed-off-by: Evan Song <theevansong@gmail.com> * feat: redo platform modal Signed-off-by: Evan Song <theevansong@gmail.com> * Switch to HCaptcha for Auth-related captchas (#2945) * Switch to HCaptcha for Auth-related captchas * run fmt * fix hcaptcha not loading * fix: more robust loader dropdown logic Signed-off-by: Evan Song <theevansong@gmail.com> * fix: handle "not yet supported" install err Signed-off-by: Evan Song <theevansong@gmail.com> * chore: fix icon kerfuffles Signed-off-by: Evan Song <theevansong@gmail.com> * chore: improve vanilla install modal title Signed-off-by: Evan Song <theevansong@gmail.com> * fix: spacing Signed-off-by: Evan Song <theevansong@gmail.com> * chore: improve no loader state Signed-off-by: Evan Song <theevansong@gmail.com> * fix: type error Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust mod version modal title Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust modpack warning copy Signed-off-by: Evan Song <theevansong@gmail.com> * feat: vanilla empty state in content page Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust copy Signed-off-by: Evan Song <theevansong@gmail.com> * chore: update icon Signed-off-by: Evan Song <theevansong@gmail.com> * fix: loader type Signed-off-by: Evan Song <theevansong@gmail.com> * fix: loader type Signed-off-by: Evan Song <theevansong@gmail.com> * feat: always show dropdown if possible Signed-off-by: Evan Song <theevansong@gmail.com> * chore: improve spacing Signed-off-by: Evan Song <theevansong@gmail.com> * chore: appear disabled Signed-off-by: Evan Song <theevansong@gmail.com> * h Signed-off-by: Evan Song <theevansong@gmail.com> * chore: if reinstalling, show it on the modal title Signed-off-by: Evan Song <theevansong@gmail.com> * feat: put it in the dropdown, they said Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust style Signed-off-by: Evan Song <theevansong@gmail.com> * chore: sort paper-purpur versions desc Signed-off-by: Evan Song <theevansong@gmail.com> * fix: do not consider backup limit in reinstall prompt Signed-off-by: Evan Song <theevansong@gmail.com> * feat: backup locking, plugin support * fix: content type error Signed-off-by: Evan Song <theevansong@gmail.com> * fix: casing Signed-off-by: Evan Song <theevansong@gmail.com> * fix: plugins pt 2 * feat: backups, mrpack * fix: type errors come on Signed-off-by: Evan Song <theevansong@gmail.com> * fix: spacing Signed-off-by: Evan Song <theevansong@gmail.com> * fix: type maxing * chore: show copy button on allocation rows Signed-off-by: Evan Song <theevansong@gmail.com> * feat: suspend improvement --------- Signed-off-by: Evan Song <theevansong@gmail.com> Co-authored-by: Evan Song <theevansong@gmail.com> Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com>
This commit is contained in:
parent
eff3189ded
commit
742c0edd9e
@ -44,7 +44,7 @@ import { ButtonStyled, NewModal } from "@modrinth/ui";
|
|||||||
import { PlusIcon, XIcon, InfoIcon } from "@modrinth/assets";
|
import { PlusIcon, XIcon, InfoIcon } from "@modrinth/assets";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits(["backupCreated"]);
|
const emit = defineEmits(["backupCreated"]);
|
||||||
|
@ -104,7 +104,7 @@ const modal = ref<InstanceType<typeof NewModal>>();
|
|||||||
|
|
||||||
const initialSettings = ref<{ interval: number; enabled: boolean } | null>(null);
|
const initialSettings = ref<{ interval: number; enabled: boolean } | null>(null);
|
||||||
const autoBackupEnabled = ref(false);
|
const autoBackupEnabled = ref(false);
|
||||||
const autoBackupInterval = ref(1);
|
const autoBackupInterval = ref(6);
|
||||||
const isLoadingSettings = ref(true);
|
const isLoadingSettings = ref(true);
|
||||||
const isSaving = ref(false);
|
const isSaving = ref(false);
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ const fetchSettings = async () => {
|
|||||||
const settings = await props.server.backups?.getAutoBackup();
|
const settings = await props.server.backups?.getAutoBackup();
|
||||||
initialSettings.value = settings as { interval: number; enabled: boolean };
|
initialSettings.value = settings as { interval: number; enabled: boolean };
|
||||||
autoBackupEnabled.value = settings?.enabled ?? false;
|
autoBackupEnabled.value = settings?.enabled ?? false;
|
||||||
autoBackupInterval.value = settings?.interval || 1;
|
autoBackupInterval.value = settings?.interval || 6;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching backup settings:", error);
|
console.error("Error fetching backup settings:", error);
|
||||||
addNotification({
|
addNotification({
|
||||||
|
@ -1,52 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex w-full flex-col gap-1 rounded-2xl bg-table-alternateRow p-2">
|
||||||
v-for="loader in loaders"
|
<div
|
||||||
:key="loader.name"
|
v-for="loader in vanillaLoaders"
|
||||||
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
|
:key="loader.name"
|
||||||
>
|
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
|
||||||
<div class="flex items-center gap-4">
|
>
|
||||||
|
<UiServersLoaderSelectorCard
|
||||||
|
:loader="loader"
|
||||||
|
:is-current="isCurrentLoader(loader.name)"
|
||||||
|
:loader-version="data.loader_version"
|
||||||
|
:current-loader="data.loader"
|
||||||
|
@select="selectLoader"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<h2 class="mb-2 px-2 text-lg font-bold text-contrast">Mod loaders</h2>
|
||||||
|
<div class="flex w-full flex-col gap-1 rounded-2xl bg-table-alternateRow p-2">
|
||||||
<div
|
<div
|
||||||
class="grid size-10 place-content-center rounded-xl border-[1px] border-solid border-button-border bg-button-bg shadow-sm"
|
v-for="loader in modLoaders"
|
||||||
:class="isCurrentLoader(loader.name) ? '[&&]:bg-bg-green' : ''"
|
:key="loader.name"
|
||||||
|
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
|
||||||
>
|
>
|
||||||
<UiServersIconsLoaderIcon
|
<UiServersLoaderSelectorCard
|
||||||
:loader="loader.name"
|
:loader="loader"
|
||||||
class="[&&]:size-6"
|
:is-current="isCurrentLoader(loader.name)"
|
||||||
:class="isCurrentLoader(loader.name) ? 'text-brand' : ''"
|
:loader-version="data.loader_version"
|
||||||
|
:current-loader="data.loader"
|
||||||
|
@select="selectLoader"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-0.5">
|
</div>
|
||||||
<div class="flex flex-row items-center gap-2">
|
</div>
|
||||||
<h1 class="m-0 text-xl font-bold leading-none text-contrast">
|
|
||||||
{{ loader.displayName }}
|
<div class="mt-4">
|
||||||
</h1>
|
<h2 class="mb-2 px-2 text-lg font-bold text-contrast">Plugin loaders</h2>
|
||||||
<span
|
<div class="flex w-full flex-col gap-1 rounded-2xl bg-table-alternateRow p-2">
|
||||||
v-if="isCurrentLoader(loader.name)"
|
<div
|
||||||
class="hidden items-center gap-1 rounded-full bg-bg-green p-1 px-1.5 text-xs font-semibold text-brand sm:flex"
|
v-for="loader in pluginLoaders"
|
||||||
>
|
:key="loader.name"
|
||||||
<CheckIcon class="h-4 w-4" />
|
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
|
||||||
Current
|
>
|
||||||
</span>
|
<UiServersLoaderSelectorCard
|
||||||
</div>
|
:loader="loader"
|
||||||
<p v-if="isCurrentLoader(loader.name)" class="m-0 text-xs text-secondary">
|
:is-current="isCurrentLoader(loader.name)"
|
||||||
{{ data.loader_version }}
|
:loader-version="data.loader_version"
|
||||||
</p>
|
:current-loader="data.loader"
|
||||||
|
@select="selectLoader"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ButtonStyled>
|
|
||||||
<button @click="selectLoader(loader.name)">
|
|
||||||
<DownloadIcon class="h-5 w-5" />
|
|
||||||
{{ isCurrentLoader(loader.name) ? "Reinstall" : "Install" }}
|
|
||||||
</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CheckIcon, DownloadIcon } from "@modrinth/assets";
|
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: {
|
data: {
|
||||||
loader: string | null;
|
loader: string | null;
|
||||||
@ -58,14 +66,20 @@ const emit = defineEmits<{
|
|||||||
(e: "selectLoader", loader: string): void;
|
(e: "selectLoader", loader: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const loaders = [
|
const vanillaLoaders = [{ name: "Vanilla" as const, displayName: "Vanilla" }];
|
||||||
{ name: "Vanilla" as const, displayName: "Vanilla" },
|
|
||||||
|
const modLoaders = [
|
||||||
{ name: "Fabric" as const, displayName: "Fabric" },
|
{ name: "Fabric" as const, displayName: "Fabric" },
|
||||||
{ name: "Quilt" as const, displayName: "Quilt" },
|
{ name: "Quilt" as const, displayName: "Quilt" },
|
||||||
{ name: "Forge" as const, displayName: "Forge" },
|
{ name: "Forge" as const, displayName: "Forge" },
|
||||||
{ name: "NeoForge" as const, displayName: "NeoForge" },
|
{ name: "NeoForge" as const, displayName: "NeoForge" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const pluginLoaders = [
|
||||||
|
{ name: "Paper" as const, displayName: "Paper" },
|
||||||
|
{ name: "Purpur" as const, displayName: "Purpur" },
|
||||||
|
];
|
||||||
|
|
||||||
const isCurrentLoader = (loaderName: string) => {
|
const isCurrentLoader = (loaderName: string) => {
|
||||||
return props.data.loader?.toLowerCase() === loaderName.toLowerCase();
|
return props.data.loader?.toLowerCase() === loaderName.toLowerCase();
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex w-full items-center justify-between">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div
|
||||||
|
class="grid size-10 place-content-center rounded-xl border-[1px] border-solid border-button-border bg-button-bg shadow-sm"
|
||||||
|
:class="isCurrentLoader ? '[&&]:bg-bg-green' : ''"
|
||||||
|
>
|
||||||
|
<UiServersIconsLoaderIcon
|
||||||
|
:loader="loader.name"
|
||||||
|
class="[&&]:size-6"
|
||||||
|
:class="isCurrentLoader ? 'text-brand' : ''"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-0.5">
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<h1 class="m-0 text-xl font-bold leading-none text-contrast">
|
||||||
|
{{ loader.displayName }}
|
||||||
|
</h1>
|
||||||
|
<span
|
||||||
|
v-if="isCurrentLoader"
|
||||||
|
class="hidden items-center gap-1 rounded-full bg-bg-green p-1 px-1.5 text-xs font-semibold text-brand sm:flex"
|
||||||
|
>
|
||||||
|
<CheckIcon class="h-4 w-4" />
|
||||||
|
Current
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p v-if="isCurrentLoader" class="m-0 text-xs text-secondary">
|
||||||
|
{{ loaderVersion }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ButtonStyled>
|
||||||
|
<button @click="onSelect">
|
||||||
|
<DownloadIcon class="h-5 w-5" />
|
||||||
|
{{ isCurrentLoader ? "Reinstall" : "Install" }}
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CheckIcon, DownloadIcon } from "@modrinth/assets";
|
||||||
|
import { ButtonStyled } from "@modrinth/ui";
|
||||||
|
|
||||||
|
interface LoaderInfo {
|
||||||
|
name: "Vanilla" | "Fabric" | "Forge" | "Quilt" | "Paper" | "NeoForge" | "Purpur";
|
||||||
|
displayName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
loader: LoaderInfo;
|
||||||
|
currentLoader: string | null;
|
||||||
|
loaderVersion: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "select", loader: string): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const isCurrentLoader = computed(() => {
|
||||||
|
return props.currentLoader?.toLowerCase() === props.loader.name.toLowerCase();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSelect = () => {
|
||||||
|
emit("select", props.loader.name);
|
||||||
|
};
|
||||||
|
</script>
|
@ -1,11 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<NuxtLink class="contents" :to="`/servers/manage/${props.server_id}`">
|
<NuxtLink
|
||||||
|
class="contents"
|
||||||
|
:to="status === 'suspended' ? '' : `/servers/manage/${props.server_id}`"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="flex cursor-pointer flex-row items-center overflow-x-hidden rounded-3xl bg-bg-raised p-4 transition-transform duration-100 active:scale-95"
|
v-tooltip="
|
||||||
|
status === 'suspended'
|
||||||
|
? `This server is suspended visit the billing page to learn more`
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
class="flex cursor-pointer flex-row items-center overflow-x-hidden rounded-3xl bg-bg-raised p-4 transition-transform duration-100"
|
||||||
|
:class="status === 'suspended' ? 'opacity-50' : 'active:scale-95'"
|
||||||
data-pyro-server-listing
|
data-pyro-server-listing
|
||||||
:data-pyro-server-listing-id="server_id"
|
:data-pyro-server-listing-id="server_id"
|
||||||
>
|
>
|
||||||
<UiServersServerIcon :image="image" />
|
<UiServersServerIcon v-if="status !== 'suspended'" :image="image" />
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="bg-bg-secondary flex size-24 items-center justify-center rounded-xl border-[1px] border-solid border-button-border bg-button-bg shadow-sm"
|
||||||
|
>
|
||||||
|
<LockIcon class="size-20 text-secondary" />
|
||||||
|
</div>
|
||||||
<div class="ml-8 flex flex-col gap-2.5">
|
<div class="ml-8 flex flex-col gap-2.5">
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<h2 class="m-0 text-xl font-bold text-contrast">{{ name }}</h2>
|
<h2 class="m-0 text-xl font-bold text-contrast">{{ name }}</h2>
|
||||||
@ -40,7 +55,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ChevronRightIcon } from "@modrinth/assets";
|
import { ChevronRightIcon, LockIcon } from "@modrinth/assets";
|
||||||
import type { Project, Server } from "~/types/servers";
|
import type { Project, Server } from "~/types/servers";
|
||||||
|
|
||||||
const props = defineProps<Partial<Server>>();
|
const props = defineProps<Partial<Server>>();
|
||||||
|
@ -153,6 +153,65 @@
|
|||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
<svg
|
||||||
|
v-else-if="loader === 'Purpur'"
|
||||||
|
xml:space="preserve"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-miterlimit="1.5"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<path
|
||||||
|
id="purpur"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.68"
|
||||||
|
d="m264 41.95 8-4v8l-8 4v-8Z"
|
||||||
|
></path>
|
||||||
|
</defs>
|
||||||
|
<path fill="none" d="M0 0h24v24H0z"></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.77"
|
||||||
|
d="m264 29.95-8 4 8 4.42 8-4.42-8-4Z"
|
||||||
|
transform="matrix(1.125 0 0 1.1372 -285 -31.69)"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.77"
|
||||||
|
d="m272 38.37-8 4.42-8-4.42"
|
||||||
|
transform="matrix(1.125 0 0 1.1372 -285 -31.69)"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.77"
|
||||||
|
d="m260 31.95 8 4.21V45"
|
||||||
|
transform="matrix(1.125 0 0 1.1372 -285 -31.69)"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.77"
|
||||||
|
d="M260 45v-8.84l8-4.21"
|
||||||
|
transform="matrix(1.125 0 0 1.1372 -285 -31.69)"
|
||||||
|
></path>
|
||||||
|
<use
|
||||||
|
xlink:href="#purpur"
|
||||||
|
stroke-width="1.68"
|
||||||
|
transform="matrix(1.125 0 0 1.2569 -285 -40.78)"
|
||||||
|
></use>
|
||||||
|
<use
|
||||||
|
xlink:href="#purpur"
|
||||||
|
stroke-width="1.68"
|
||||||
|
transform="matrix(-1.125 0 0 1.2569 309 -40.78)"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
<svg v-else-if="loader === 'Vanilla'" viewBox="0 0 20 20" fill="currentColor">
|
<svg v-else-if="loader === 'Vanilla'" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
@ -165,8 +224,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { LoaderIcon } from "@modrinth/assets";
|
import { LoaderIcon } from "@modrinth/assets";
|
||||||
|
import type { Loaders } from "~/types/servers";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
loader: "Fabric" | "Quilt" | "Forge" | "NeoForge" | "Paper" | "Spigot" | "Bukkit" | "Vanilla";
|
loader: Loaders;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
@ -213,6 +213,7 @@ interface Backup {
|
|||||||
name: string;
|
name: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
ongoing: boolean;
|
ongoing: boolean;
|
||||||
|
locked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AutoBackupSettings {
|
interface AutoBackupSettings {
|
||||||
@ -225,6 +226,8 @@ interface JWTAuth {
|
|||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContentType = "Mod" | "Plugin";
|
||||||
|
|
||||||
const constructServerProperties = (properties: any): string => {
|
const constructServerProperties = (properties: any): string => {
|
||||||
let fileContent = `#Minecraft server properties\n#${new Date().toUTCString()}\n`;
|
let fileContent = `#Minecraft server properties\n#${new Date().toUTCString()}\n`;
|
||||||
|
|
||||||
@ -483,13 +486,16 @@ const setMotd = async (motd: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------ MODS ------------------ //
|
// ------------------ CONTENT ------------------ //
|
||||||
|
|
||||||
const installMod = async (projectId: string, versionId: string) => {
|
const installContent = async (contentType: ContentType, projectId: string, versionId: string) => {
|
||||||
try {
|
try {
|
||||||
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods`, {
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { rinth_ids: { project_id: projectId, version_id: versionId } },
|
body: {
|
||||||
|
install_as: contentType,
|
||||||
|
rinth_ids: { project_id: projectId, version_id: versionId },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error installing mod:", error);
|
console.error("Error installing mod:", error);
|
||||||
@ -497,12 +503,13 @@ const installMod = async (projectId: string, versionId: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeMod = async (modId: string) => {
|
const removeContent = async (contentType: ContentType, contentId: string) => {
|
||||||
try {
|
try {
|
||||||
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/deleteMod`, {
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/deleteMod`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
path: modId,
|
install_as: contentType,
|
||||||
|
path: contentId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -511,11 +518,15 @@ const removeMod = async (modId: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const reinstallMod = async (modId: string, versionId: string) => {
|
const reinstallContent = async (
|
||||||
|
contentType: ContentType,
|
||||||
|
contentId: string,
|
||||||
|
newContentId: string,
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods/${modId}`, {
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods/${contentId}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: { version_id: versionId },
|
body: { install_as: contentType, version_id: newContentId },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error reinstalling mod:", error);
|
console.error("Error reinstalling mod:", error);
|
||||||
@ -527,10 +538,11 @@ const reinstallMod = async (modId: string, versionId: string) => {
|
|||||||
|
|
||||||
const createBackup = async (backupName: string) => {
|
const createBackup = async (backupName: string) => {
|
||||||
try {
|
try {
|
||||||
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups`, {
|
const response = (await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { name: backupName },
|
body: { name: backupName },
|
||||||
});
|
})) as { id: string };
|
||||||
|
return response.id;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating backup:", error);
|
console.error("Error creating backup:", error);
|
||||||
throw error;
|
throw error;
|
||||||
@ -604,6 +616,34 @@ const getAutoBackup = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const lockBackup = async (backupId: string) => {
|
||||||
|
try {
|
||||||
|
return await PyroFetch(
|
||||||
|
`servers/${internalServerRefrence.value.serverId}/backups/${backupId}/lock`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error locking backup:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unlockBackup = async (backupId: string) => {
|
||||||
|
try {
|
||||||
|
return await PyroFetch(
|
||||||
|
`servers/${internalServerRefrence.value.serverId}/backups/${backupId}/unlock`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error locking backup:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ------------------ NETWORK ------------------ //
|
// ------------------ NETWORK ------------------ //
|
||||||
|
|
||||||
const reserveAllocation = async (name: string): Promise<Allocation> => {
|
const reserveAllocation = async (name: string): Promise<Allocation> => {
|
||||||
@ -858,7 +898,7 @@ const modules: any = {
|
|||||||
setMotd,
|
setMotd,
|
||||||
fetchConfigFile,
|
fetchConfigFile,
|
||||||
},
|
},
|
||||||
mods: {
|
content: {
|
||||||
get: async (serverId: string) => {
|
get: async (serverId: string) => {
|
||||||
try {
|
try {
|
||||||
const mods = await PyroFetch<Mod[]>(`servers/${serverId}/mods`);
|
const mods = await PyroFetch<Mod[]>(`servers/${serverId}/mods`);
|
||||||
@ -873,9 +913,9 @@ const modules: any = {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
install: installMod,
|
install: installContent,
|
||||||
remove: removeMod,
|
remove: removeContent,
|
||||||
reinstall: reinstallMod,
|
reinstall: reinstallContent,
|
||||||
},
|
},
|
||||||
backups: {
|
backups: {
|
||||||
get: async (serverId: string) => {
|
get: async (serverId: string) => {
|
||||||
@ -893,6 +933,8 @@ const modules: any = {
|
|||||||
download: downloadBackup,
|
download: downloadBackup,
|
||||||
updateAutoBackup,
|
updateAutoBackup,
|
||||||
getAutoBackup,
|
getAutoBackup,
|
||||||
|
lock: lockBackup,
|
||||||
|
unlock: unlockBackup,
|
||||||
},
|
},
|
||||||
network: {
|
network: {
|
||||||
get: async (serverId: string) => {
|
get: async (serverId: string) => {
|
||||||
@ -1018,9 +1060,9 @@ type GeneralFunctions = {
|
|||||||
fetchConfigFile: (fileName: string) => Promise<any>;
|
fetchConfigFile: (fileName: string) => Promise<any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ModFunctions = {
|
type ContentFunctions = {
|
||||||
/**
|
/**
|
||||||
* INTERNAL: Gets the mods of a server.
|
* INTERNAL: Gets the list content of a server.
|
||||||
* @param serverId - The ID of the server.
|
* @param serverId - The ID of the server.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
@ -1028,23 +1070,26 @@ type ModFunctions = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs a mod to a server.
|
* Installs a mod to a server.
|
||||||
|
* @param contentType - The type of content to install.
|
||||||
* @param projectId - The ID of the project.
|
* @param projectId - The ID of the project.
|
||||||
* @param versionId - The ID of the version.
|
* @param versionId - The ID of the version.
|
||||||
*/
|
*/
|
||||||
install: (projectId: string, versionId: string) => Promise<void>;
|
install: (contentType: ContentType, projectId: string, versionId: string) => Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a mod from a server.
|
* Removes a mod from a server.
|
||||||
* @param modId - The ID of the mod.
|
* @param contentType - The type of content to remove.
|
||||||
|
* @param contentId - The ID of the content.
|
||||||
*/
|
*/
|
||||||
remove: (modId: string) => Promise<void>;
|
remove: (contentType: ContentType, contentId: string) => Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reinstalls a mod to a server.
|
* Reinstalls a mod to a server.
|
||||||
* @param modId - The ID of the mod.
|
* @param contentType - The type of content to reinstall.
|
||||||
* @param versionId - The ID of the version.
|
* @param contentId - The ID of the content.
|
||||||
|
* @param newContentId - The ID of the new version.
|
||||||
*/
|
*/
|
||||||
reinstall: (modId: string, versionId: string) => Promise<void>;
|
reinstall: (contentType: ContentType, contentId: string, newContentId: string) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type BackupFunctions = {
|
type BackupFunctions = {
|
||||||
@ -1058,6 +1103,7 @@ type BackupFunctions = {
|
|||||||
/**
|
/**
|
||||||
* Creates a new backup for the server.
|
* Creates a new backup for the server.
|
||||||
* @param backupName - The name of the backup.
|
* @param backupName - The name of the backup.
|
||||||
|
* @returns The ID of the backup.
|
||||||
*/
|
*/
|
||||||
create: (backupName: string) => Promise<void>;
|
create: (backupName: string) => Promise<void>;
|
||||||
|
|
||||||
@ -1098,6 +1144,18 @@ type BackupFunctions = {
|
|||||||
* Gets the auto backup settings of the server.
|
* Gets the auto backup settings of the server.
|
||||||
*/
|
*/
|
||||||
getAutoBackup: () => Promise<AutoBackupSettings>;
|
getAutoBackup: () => Promise<AutoBackupSettings>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks a backup for the server.
|
||||||
|
* @param backupId - The ID of the backup.
|
||||||
|
*/
|
||||||
|
lock: (backupId: string) => Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlocks a backup for the server.
|
||||||
|
* @param backupId - The ID of the backup.
|
||||||
|
*/
|
||||||
|
unlock: (backupId: string) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NetworkFunctions = {
|
type NetworkFunctions = {
|
||||||
@ -1231,7 +1289,7 @@ type FSFunctions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type GeneralModule = General & GeneralFunctions;
|
type GeneralModule = General & GeneralFunctions;
|
||||||
type ModsModule = { data: Mod[] } & ModFunctions;
|
type ContentModule = { data: Mod[] } & ContentFunctions;
|
||||||
type BackupsModule = { data: Backup[] } & BackupFunctions;
|
type BackupsModule = { data: Backup[] } & BackupFunctions;
|
||||||
type NetworkModule = { allocations: Allocation[] } & NetworkFunctions;
|
type NetworkModule = { allocations: Allocation[] } & NetworkFunctions;
|
||||||
type StartupModule = Startup & StartupFunctions;
|
type StartupModule = Startup & StartupFunctions;
|
||||||
@ -1239,7 +1297,7 @@ type FSModule = { auth: JWTAuth } & FSFunctions;
|
|||||||
|
|
||||||
type ModulesMap = {
|
type ModulesMap = {
|
||||||
general: GeneralModule;
|
general: GeneralModule;
|
||||||
mods: ModsModule;
|
content: ContentModule;
|
||||||
backups: BackupsModule;
|
backups: BackupsModule;
|
||||||
network: NetworkModule;
|
network: NetworkModule;
|
||||||
startup: StartupModule;
|
startup: StartupModule;
|
||||||
@ -1247,7 +1305,7 @@ type ModulesMap = {
|
|||||||
fs: FSModule;
|
fs: FSModule;
|
||||||
};
|
};
|
||||||
|
|
||||||
type avaliableModules = ("general" | "mods" | "backups" | "network" | "startup" | "ws" | "fs")[];
|
type avaliableModules = ("general" | "content" | "backups" | "network" | "startup" | "ws" | "fs")[];
|
||||||
|
|
||||||
export type Server<T extends avaliableModules> = {
|
export type Server<T extends avaliableModules> = {
|
||||||
[K in T[number]]?: ModulesMap[K];
|
[K in T[number]]?: ModulesMap[K];
|
||||||
|
@ -274,7 +274,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="
|
v-if="
|
||||||
result.installed ||
|
result.installed ||
|
||||||
server.mods.data.find((x) => x.project_id === result.project_id) ||
|
server.content.data.find((x) => x.project_id === result.project_id) ||
|
||||||
server.general?.project?.id === result.project_id
|
server.general?.project?.id === result.project_id
|
||||||
"
|
"
|
||||||
disabled
|
disabled
|
||||||
@ -430,7 +430,7 @@ const serverOverrideGameVersions = ref(false);
|
|||||||
const serverOverrideLoaders = ref(false);
|
const serverOverrideLoaders = ref(false);
|
||||||
|
|
||||||
if (route.query.sid) {
|
if (route.query.sid) {
|
||||||
server.value = await usePyroServer(route.query.sid, ["general", "mods"]);
|
server.value = await usePyroServer(route.query.sid, ["general", "content"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.query.shi && projectType.value.id !== "modpack") {
|
if (route.query.shi && projectType.value.id !== "modpack") {
|
||||||
@ -462,8 +462,12 @@ async function serverInstall(project) {
|
|||||||
project.installed = true;
|
project.installed = true;
|
||||||
navigateTo(`/servers/manage/${route.query.sid}/options/loader`);
|
navigateTo(`/servers/manage/${route.query.sid}/options/loader`);
|
||||||
} else if (projectType.value.id === "mod") {
|
} else if (projectType.value.id === "mod") {
|
||||||
await server.value.mods.install(version.project_id, version.id);
|
await server.value.content.install("mod", version.project_id, version.id);
|
||||||
await server.value.refresh(["mods"]);
|
await server.value.refresh(["content"]);
|
||||||
|
project.installed = true;
|
||||||
|
} else if (projectType.value.id === "plugin") {
|
||||||
|
await server.value.content.install("plugin", version.project_id, version.id);
|
||||||
|
await server.value.refresh(["content"]);
|
||||||
project.installed = true;
|
project.installed = true;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -509,7 +513,7 @@ const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (server.value && serverHideInstalled.value) {
|
if (server.value && serverHideInstalled.value) {
|
||||||
const installedMods = server.value.mods.data
|
const installedMods = server.value.content.data
|
||||||
.filter((x) => x.project_id)
|
.filter((x) => x.project_id)
|
||||||
.map((x) => x.project_id);
|
.map((x) => x.project_id);
|
||||||
|
|
||||||
|
@ -1,7 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="contents">
|
<div class="contents">
|
||||||
<div
|
<div
|
||||||
v-if="server.error && server.error.message.includes('Forbidden')"
|
v-if="serverData?.status === 'suspended'"
|
||||||
|
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">
|
||||||
|
<div class="flex flex-col items-center text-center">
|
||||||
|
<div class="flex flex-col items-center gap-4">
|
||||||
|
<div class="grid place-content-center rounded-full bg-bg-orange p-4">
|
||||||
|
<LockIcon class="size-12 text-orange" />
|
||||||
|
</div>
|
||||||
|
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">Server Suspended</h1>
|
||||||
|
</div>
|
||||||
|
<p class="text-lg text-secondary">
|
||||||
|
{{
|
||||||
|
serverData.suspension_reason
|
||||||
|
? `Your server has been suspended: ${serverData.suspension_reason}`
|
||||||
|
: "Your server has been suspended."
|
||||||
|
}}
|
||||||
|
<br />
|
||||||
|
This is most likely due to a billing issue. Please check your billing information and
|
||||||
|
contact Modrinth support if you believe this is an error.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ButtonStyled size="large" color="brand" @click="() => router.push('/settings/billing')">
|
||||||
|
<button class="mt-6 !w-full">Go to billing</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="server.error && server.error.message.includes('Forbidden')"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
||||||
@ -128,11 +156,11 @@
|
|||||||
class="mx-auto mb-4 flex justify-between gap-2 rounded-2xl border-2 border-solid border-red bg-bg-red p-4 font-semibold text-contrast"
|
class="mx-auto mb-4 flex justify-between gap-2 rounded-2xl border-2 border-solid border-red bg-bg-red p-4 font-semibold text-contrast"
|
||||||
>
|
>
|
||||||
<div class="flex flex-row gap-4">
|
<div class="flex flex-row gap-4">
|
||||||
<IssuesIcon class="hidden h-8 w-8 text-red sm:block" />
|
<IssuesIcon class="hidden h-8 w-8 shrink-0 text-red sm:block" />
|
||||||
<div class="flex flex-col gap-2 leading-[150%]">
|
<div class="flex flex-col gap-2 leading-[150%]">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<IssuesIcon class="block h-8 w-8 text-red sm:hidden" />
|
<IssuesIcon class="flex h-8 w-8 shrink-0 text-red sm:hidden" />
|
||||||
<div class="flex gap-2 text-xl font-bold">{{ errorTitle }}</div>
|
<div class="flex gap-2 text-2xl font-bold">{{ errorTitle }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -175,6 +203,14 @@
|
|||||||
reinstalling your server, and if the problem persists, please contact Modrinth
|
reinstalling your server, and if the problem persists, please contact Modrinth
|
||||||
support with your server's debug information.
|
support with your server's debug information.
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="errorMessage.toLocaleLowerCase() === 'this version is not yet supported'"
|
||||||
|
>
|
||||||
|
An error occurred while installing your server because Modrinth Servers does not
|
||||||
|
support the version of Minecraft or the loader you specified. Try reinstalling
|
||||||
|
your server with a different version or loader, and if the problem persists,
|
||||||
|
please contact Modrinth support with your server's debug information.
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="errorTitle === 'Installation error'"
|
v-if="errorTitle === 'Installation error'"
|
||||||
@ -262,6 +298,7 @@ import {
|
|||||||
CheckIcon,
|
CheckIcon,
|
||||||
FileIcon,
|
FileIcon,
|
||||||
TransferIcon,
|
TransferIcon,
|
||||||
|
LockIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
import { ButtonStyled } from "@modrinth/ui";
|
||||||
@ -294,7 +331,7 @@ const router = useRouter();
|
|||||||
const serverId = route.params.id as string;
|
const serverId = route.params.id as string;
|
||||||
const server = await usePyroServer(serverId, [
|
const server = await usePyroServer(serverId, [
|
||||||
"general",
|
"general",
|
||||||
"mods",
|
"content",
|
||||||
"backups",
|
"backups",
|
||||||
"network",
|
"network",
|
||||||
"startup",
|
"startup",
|
||||||
@ -305,6 +342,7 @@ const server = await usePyroServer(serverId, [
|
|||||||
watch(
|
watch(
|
||||||
() => server.error,
|
() => server.error,
|
||||||
(newError) => {
|
(newError) => {
|
||||||
|
if (server.general?.status === "suspended") return;
|
||||||
if (newError && !newError.message.includes("Forbidden")) {
|
if (newError && !newError.message.includes("Forbidden")) {
|
||||||
startPolling();
|
startPolling();
|
||||||
}
|
}
|
||||||
@ -518,7 +556,7 @@ const handleWebSocketMessage = (data: WSEvent) => {
|
|||||||
handleInstallationResult(data);
|
handleInstallationResult(data);
|
||||||
break;
|
break;
|
||||||
case "new-mod":
|
case "new-mod":
|
||||||
server.refresh(["mods"]);
|
server.refresh(["content"]);
|
||||||
console.log("New mod:", data);
|
console.log("New mod:", data);
|
||||||
break;
|
break;
|
||||||
case "auth-ok":
|
case "auth-ok":
|
||||||
|
@ -107,6 +107,7 @@
|
|||||||
v-tooltip="'Backup in progress'"
|
v-tooltip="'Backup in progress'"
|
||||||
class="size-6 animate-spin"
|
class="size-6 animate-spin"
|
||||||
/>
|
/>
|
||||||
|
<LockIcon v-else-if="backup.locked" class="size-8" />
|
||||||
<BoxIcon v-else class="size-8" />
|
<BoxIcon v-else class="size-8" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex min-w-0 flex-col gap-2">
|
<div class="flex min-w-0 flex-col gap-2">
|
||||||
@ -159,6 +160,16 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ id: 'download', action: () => initiateDownload(backup.id) },
|
{ id: 'download', action: () => initiateDownload(backup.id) },
|
||||||
|
{
|
||||||
|
id: 'lock',
|
||||||
|
action: () => {
|
||||||
|
if (backup.locked) {
|
||||||
|
unlockBackup(backup.id);
|
||||||
|
} else {
|
||||||
|
lockBackup(backup.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'delete',
|
id: 'delete',
|
||||||
action: () => {
|
action: () => {
|
||||||
@ -172,6 +183,8 @@
|
|||||||
<MoreHorizontalIcon class="h-5 w-5 bg-transparent" />
|
<MoreHorizontalIcon class="h-5 w-5 bg-transparent" />
|
||||||
<template #rename> <EditIcon /> Rename </template>
|
<template #rename> <EditIcon /> Rename </template>
|
||||||
<template #restore> <ClipboardCopyIcon /> Restore </template>
|
<template #restore> <ClipboardCopyIcon /> Restore </template>
|
||||||
|
<template v-if="backup.locked" #lock> <OpenLockIcon /> Unlock </template>
|
||||||
|
<template v-else #lock> <LockIcon /> Lock </template>
|
||||||
<template #download> <DownloadIcon /> Download </template>
|
<template #download> <DownloadIcon /> Download </template>
|
||||||
<template #delete> <TrashIcon /> Delete </template>
|
<template #delete> <TrashIcon /> Delete </template>
|
||||||
</UiServersTeleportOverflowMenu>
|
</UiServersTeleportOverflowMenu>
|
||||||
@ -217,6 +230,8 @@ import {
|
|||||||
TrashIcon,
|
TrashIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
BoxIcon,
|
BoxIcon,
|
||||||
|
LockIcon,
|
||||||
|
OpenLockIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import type { Server } from "~/composables/pyroServers";
|
import type { Server } from "~/composables/pyroServers";
|
||||||
@ -335,6 +350,24 @@ const initiateDownload = async (backupId: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const lockBackup = async (backupId: string) => {
|
||||||
|
try {
|
||||||
|
await props.server.backups?.lock(backupId);
|
||||||
|
await props.server.refresh(["backups"]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to toggle lock:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unlockBackup = async (backupId: string) => {
|
||||||
|
try {
|
||||||
|
await props.server.backups?.unlock(backupId);
|
||||||
|
await props.server.refresh(["backups"]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to toggle lock:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
const hasOngoingBackups = backups.value.some((backup) => backup.ongoing);
|
const hasOngoingBackups = backups.value.some((backup) => backup.ongoing);
|
||||||
|
@ -10,7 +10,7 @@ import type { Server } from "~/composables/pyroServers";
|
|||||||
const route = useNativeRoute();
|
const route = useNativeRoute();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const data = computed(() => props.server.general);
|
const data = computed(() => props.server.general);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<NewModal ref="modModal" header="Edit mod version">
|
<NewModal ref="modModal" header="Editing mod version">
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-4 flex flex-col gap-4">
|
<div class="mb-4 flex flex-col gap-4">
|
||||||
<div class="inline-flex flex-wrap items-center">
|
<div class="inline-flex flex-wrap items-center">
|
||||||
@ -19,7 +19,8 @@
|
|||||||
<InfoIcon class="hidden sm:block" />
|
<InfoIcon class="hidden sm:block" />
|
||||||
<span class="text-sm text-secondary">
|
<span class="text-sm text-secondary">
|
||||||
Your server was created from a modpack. Changing the mod version may cause unexpected
|
Your server was created from a modpack. Changing the mod version may cause unexpected
|
||||||
issues.
|
issues. You can update the modpack version in your server's Options > Platform
|
||||||
|
settings.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -72,7 +73,7 @@
|
|||||||
type="search"
|
type="search"
|
||||||
name="search"
|
name="search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
placeholder="Search mods..."
|
:placeholder="`Search ${type}s...`"
|
||||||
@input="debouncedSearch"
|
@input="debouncedSearch"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -80,7 +81,7 @@
|
|||||||
<UiServersTeleportOverflowMenu
|
<UiServersTeleportOverflowMenu
|
||||||
position="bottom"
|
position="bottom"
|
||||||
direction="left"
|
direction="left"
|
||||||
aria-label="Filter mods"
|
:aria-label="`Filter ${type}s`"
|
||||||
:options="[
|
:options="[
|
||||||
{ id: 'all', action: () => (filterMethod = 'all') },
|
{ id: 'all', action: () => (filterMethod = 'all') },
|
||||||
{ id: 'enabled', action: () => (filterMethod = 'enabled') },
|
{ id: 'enabled', action: () => (filterMethod = 'enabled') },
|
||||||
@ -92,7 +93,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<FilterIcon aria-hidden="true" />
|
<FilterIcon aria-hidden="true" />
|
||||||
<DropdownIcon aria-hidden="true" class="h-5 w-5 text-secondary" />
|
<DropdownIcon aria-hidden="true" class="h-5 w-5 text-secondary" />
|
||||||
<template #all> All mods </template>
|
<template #all> All {{ type }}s </template>
|
||||||
<template #enabled> Only enabled </template>
|
<template #enabled> Only enabled </template>
|
||||||
<template #disabled> Only disabled </template>
|
<template #disabled> Only disabled </template>
|
||||||
</UiServersTeleportOverflowMenu>
|
</UiServersTeleportOverflowMenu>
|
||||||
@ -101,10 +102,10 @@
|
|||||||
<ButtonStyled v-if="hasMods" color="brand" type="outlined">
|
<ButtonStyled v-if="hasMods" color="brand" type="outlined">
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
class="w-full text-nowrap sm:w-fit"
|
class="w-full text-nowrap sm:w-fit"
|
||||||
:to="`/mods?sid=${props.server.serverId}`"
|
:to="`/${type}s?sid=${props.server.serverId}`"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
Add content
|
Add {{ type }}
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
@ -227,14 +228,48 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mt-4 flex h-full flex-col items-center justify-center text-center">
|
<!-- no mods has platform -->
|
||||||
<PackageClosedIcon class="size-24 text-neutral-500" />
|
<div
|
||||||
<p class="m-0 pb-2 pt-3 text-neutral-200">No mods found!</p>
|
v-else-if="
|
||||||
<p class="m-0 pb-3 text-neutral-400">Add some mods to your server to manage them here.</p>
|
!hasMods &&
|
||||||
<ButtonStyled color="brand" class="mt-8">
|
props.server.general?.loader &&
|
||||||
<nuxt-link :to="`/mods?sid=${props.server.serverId}`">Add content</nuxt-link>
|
props.server.general?.loader.toLocaleLowerCase() !== 'vanilla'
|
||||||
|
"
|
||||||
|
class="mt-4 flex h-full flex-col items-center justify-center gap-4 text-center"
|
||||||
|
>
|
||||||
|
<PackageClosedIcon class="size-24" />
|
||||||
|
<p class="m-0 font-bold text-contrast">No {{ type }}s found!</p>
|
||||||
|
<p class="m-0">Add some {{ type }}s to your server to manage them here.</p>
|
||||||
|
<ButtonStyled color="brand">
|
||||||
|
<NuxtLink :to="`/${type}s?sid=${props.server.serverId}`">
|
||||||
|
<PlusIcon />
|
||||||
|
Add {{ type }}
|
||||||
|
</NuxtLink>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="mt-4 flex h-full flex-col items-center justify-center gap-4 text-center">
|
||||||
|
<UiServersIconsLoaderIcon loader="Vanilla" class="size-24" />
|
||||||
|
<p class="m-0 pt-3 font-bold text-contrast">Your server is running Vanilla Minecraft</p>
|
||||||
|
<p class="m-0">
|
||||||
|
Add content to your server by installing a modpack or choosing a different platform that
|
||||||
|
supports {{ type }}s.
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-row items-center gap-4">
|
||||||
|
<ButtonStyled class="mt-8">
|
||||||
|
<NuxtLink :to="`/modpacks?sid=${props.server.serverId}`">
|
||||||
|
<CompassIcon />
|
||||||
|
Find a modpack
|
||||||
|
</NuxtLink>
|
||||||
|
</ButtonStyled>
|
||||||
|
<div>or</div>
|
||||||
|
<ButtonStyled class="mt-8">
|
||||||
|
<NuxtLink :to="`/${type}s?sid=${props.server.serverId}`">
|
||||||
|
<WrenchIcon />
|
||||||
|
Change platform
|
||||||
|
</NuxtLink>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -251,15 +286,22 @@ import {
|
|||||||
XIcon,
|
XIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
|
CompassIcon,
|
||||||
|
WrenchIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
|
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
|
||||||
import type { Server } from "~/composables/pyroServers";
|
import type { Server } from "~/composables/pyroServers";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const type = computed(() => {
|
||||||
|
const loader = props.server.general?.loader?.toLowerCase();
|
||||||
|
return loader === "paper" || loader === "purpur" ? "plugin" : "mod";
|
||||||
|
});
|
||||||
|
|
||||||
interface Mod {
|
interface Mod {
|
||||||
name?: string;
|
name?: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
@ -291,7 +333,7 @@ const filterMethodLabel = computed(() => {
|
|||||||
case "enabled":
|
case "enabled":
|
||||||
return "Only enabled";
|
return "Only enabled";
|
||||||
default:
|
default:
|
||||||
return "All mods";
|
return `All ${type.value}s`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -350,7 +392,7 @@ onUnmounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.server.mods?.data,
|
() => props.server.content?.data,
|
||||||
(newMods) => {
|
(newMods) => {
|
||||||
if (newMods) {
|
if (newMods) {
|
||||||
localMods.value = [...newMods];
|
localMods.value = [...newMods];
|
||||||
@ -399,7 +441,7 @@ async function toggleMod(mod: Mod) {
|
|||||||
|
|
||||||
await props.server.fs?.moveFileOrFolder(sourcePath, destinationPath);
|
await props.server.fs?.moveFileOrFolder(sourcePath, destinationPath);
|
||||||
|
|
||||||
await props.server.refresh(["general", "mods"]);
|
await props.server.refresh(["general", "content"]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mod.filename = originalFilename;
|
mod.filename = originalFilename;
|
||||||
mod.disabled = originalFilename.endsWith(".disabled");
|
mod.disabled = originalFilename.endsWith(".disabled");
|
||||||
@ -418,8 +460,8 @@ async function removeMod(mod: Mod) {
|
|||||||
mod.changing = true;
|
mod.changing = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await props.server.mods?.remove(`/mods/${mod.filename}`);
|
await props.server.content?.remove(type.value, `/${type.value}s/${mod.filename}`);
|
||||||
await props.server.refresh(["general", "mods"]);
|
await props.server.refresh(["general", "content"]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error removing mod:", error);
|
console.error("Error removing mod:", error);
|
||||||
|
|
||||||
@ -439,7 +481,12 @@ const currentVersion = ref();
|
|||||||
|
|
||||||
async function beginChangeModVersion(mod: Mod) {
|
async function beginChangeModVersion(mod: Mod) {
|
||||||
currentMod.value = mod;
|
currentMod.value = mod;
|
||||||
currentVersions.value = await useBaseFetch(`project/${mod.project_id}/version`, {}, false, true);
|
currentVersions.value = await useBaseFetch(`project/${mod.project_id}/version`, {}, false);
|
||||||
|
|
||||||
|
currentVersions.value = currentVersions.value.filter((version: any) =>
|
||||||
|
version.loaders.includes(props.server.general?.loader?.toLowerCase()),
|
||||||
|
);
|
||||||
|
|
||||||
currentVersion.value = currentVersions.value.find(
|
currentVersion.value = currentVersions.value.find(
|
||||||
(version: any) => version.id === mod.version_id,
|
(version: any) => version.id === mod.version_id,
|
||||||
);
|
);
|
||||||
@ -450,9 +497,12 @@ async function changeModVersion() {
|
|||||||
currentMod.value.changing = true;
|
currentMod.value.changing = true;
|
||||||
try {
|
try {
|
||||||
modModal.value.hide();
|
modModal.value.hide();
|
||||||
await props.server.mods?.remove(`/mods/${currentMod.value.filename}`);
|
await props.server.content?.reinstall(
|
||||||
await props.server.mods?.install(currentMod.value.project_id, currentVersion.value.id);
|
type.value,
|
||||||
await props.server.refresh(["general", "mods"]);
|
currentMod.value.version_id,
|
||||||
|
currentVersion.value.id,
|
||||||
|
);
|
||||||
|
await props.server.refresh(["general", "content"]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error changing mod version:", error);
|
console.error("Error changing mod version:", error);
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ import { UploadIcon, FolderOpenIcon } from "@modrinth/assets";
|
|||||||
import type { Server } from "~/composables/pyroServers";
|
import type { Server } from "~/composables/pyroServers";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -254,6 +254,7 @@ const inspectError = async () => {
|
|||||||
|
|
||||||
mcError.value = response;
|
mcError.value = response;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const analysis = (await $fetch(`https://api.mclo.gs/1/insights/${response.id}`, {
|
const analysis = (await $fetch(`https://api.mclo.gs/1/insights/${response.id}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -19,7 +19,7 @@ const route = useRoute();
|
|||||||
const serverId = route.params.id as string;
|
const serverId = route.params.id as string;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div class="card flex flex-col gap-4">
|
<div class="card flex flex-col gap-4">
|
||||||
<label for="server-name-field" class="flex flex-col gap-2">
|
<label for="server-name-field" class="flex flex-col gap-2">
|
||||||
<span class="text-lg font-bold text-contrast">Server name</span>
|
<span class="text-lg font-bold text-contrast">Server name</span>
|
||||||
<span> Change the name of your server. This name is only visible on Modrinth.</span>
|
<span> Change your server's name. This name is only visible on Modrinth.</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<input
|
<input
|
||||||
@ -51,7 +51,7 @@
|
|||||||
/>
|
/>
|
||||||
.modrinth.gg
|
.modrinth.gg
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col text-sm text-rose-400">
|
<div v-if="!isValidSubdomain" class="flex flex-col text-sm text-rose-400">
|
||||||
<span v-if="!isValidLengthSubdomain">
|
<span v-if="!isValidLengthSubdomain">
|
||||||
Subdomain must be at least 5 characters long.
|
Subdomain must be at least 5 characters long.
|
||||||
</span>
|
</span>
|
||||||
@ -134,7 +134,7 @@ import ButtonStyled from "@modrinth/ui/src/components/base/ButtonStyled.vue";
|
|||||||
import type { Server } from "~/composables/pyroServers";
|
import type { Server } from "~/composables/pyroServers";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const data = computed(() => props.server.general);
|
const data = computed(() => props.server.general);
|
||||||
|
@ -123,7 +123,7 @@ const route = useNativeRoute();
|
|||||||
const serverId = route.params.id as string;
|
const serverId = route.params.id as string;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const data = computed(() => props.server.general);
|
const data = computed(() => props.server.general);
|
||||||
|
@ -1,51 +1,152 @@
|
|||||||
<template>
|
<template>
|
||||||
<NewModal ref="editModal" header="Select modpack">
|
|
||||||
<UiServersProjectSelect type="modpack" @select="reinstallNew" />
|
|
||||||
</NewModal>
|
|
||||||
|
|
||||||
<NewModal
|
<NewModal
|
||||||
ref="versionSelectModal"
|
ref="versionSelectModal"
|
||||||
:header="isSecondPhase ? 'Confirm reinstallation' : 'Select version'"
|
:header="
|
||||||
|
isSecondPhase
|
||||||
|
? 'Confirming reinstallation'
|
||||||
|
: `${data?.loader === selectedLoader ? 'Reinstalling' : 'Installing'}
|
||||||
|
${selectedLoader.toLowerCase() === 'vanilla' ? 'Vanilla Minecraft' : selectedLoader}`
|
||||||
|
"
|
||||||
@hide="onHide"
|
@hide="onHide"
|
||||||
@show="onShow"
|
@show="onShow"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-4 md:w-[600px]">
|
<div class="flex flex-col gap-4 md:w-[600px]">
|
||||||
<p
|
<p
|
||||||
|
v-if="isSecondPhase"
|
||||||
:style="{
|
:style="{
|
||||||
lineHeight: isSecondPhase ? '1.5' : undefined,
|
lineHeight: isSecondPhase ? '1.5' : undefined,
|
||||||
marginBottom: isSecondPhase ? '-12px' : '0',
|
marginBottom: isSecondPhase ? '-12px' : '0',
|
||||||
marginTop: isSecondPhase ? '-4px' : '-2px',
|
marginTop: isSecondPhase ? '-4px' : '-2px',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{
|
This will reinstall your server and erase all data. You may want to back up your server
|
||||||
isSecondPhase
|
before proceeding. Are you sure you want to continue?
|
||||||
? "This will reinstall your server and erase all data. You may want to back up your server before proceeding. Are you sure you want to continue?"
|
|
||||||
: "Choose the version of Minecraft you want to use for this server."
|
|
||||||
}}
|
|
||||||
</p>
|
</p>
|
||||||
<div v-if="!isSecondPhase" class="flex flex-col gap-2">
|
<div v-if="!isSecondPhase" class="flex flex-col gap-4">
|
||||||
<UiServersTeleportDropdownMenu
|
<div class="mx-auto flex flex-row items-center gap-4">
|
||||||
v-model="selectedMCVersion"
|
<div
|
||||||
name="mcVersion"
|
class="grid size-16 place-content-center rounded-2xl border-[2px] border-solid border-button-border bg-button-bg shadow-sm"
|
||||||
:options="mcVersions"
|
>
|
||||||
placeholder="Select Minecraft version..."
|
<UiServersIconsLoaderIcon class="size-10" :loader="selectedLoader" />
|
||||||
/>
|
</div>
|
||||||
<UiServersTeleportDropdownMenu
|
<svg
|
||||||
v-if="selectedMCVersion && selectedLoader.toLowerCase() !== 'vanilla'"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
v-model="selectedLoaderVersion"
|
width="24"
|
||||||
name="loaderVersion"
|
height="24"
|
||||||
:options="selectedLoaderVersions"
|
viewBox="0 0 24 24"
|
||||||
placeholder="Select loader version..."
|
fill="none"
|
||||||
/>
|
stroke="currentColor"
|
||||||
<div class="mt-2 flex items-center gap-2">
|
stroke-width="2"
|
||||||
<input
|
stroke-linecap="round"
|
||||||
id="hard-reset"
|
stroke-linejoin="round"
|
||||||
:checked="hardReset"
|
class="size-10"
|
||||||
class="switch stylized-toggle"
|
>
|
||||||
type="checkbox"
|
<path d="M5 9v6" />
|
||||||
@change="hardReset = ($event.target as HTMLInputElement).checked"
|
<path d="M9 9h3V5l7 7-7 7v-4H9V9z" />
|
||||||
|
</svg>
|
||||||
|
<div
|
||||||
|
class="grid size-16 place-content-center rounded-2xl border-[2px] border-solid border-button-border bg-table-alternateRow shadow-sm"
|
||||||
|
>
|
||||||
|
<ServerIcon class="size-10" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4">
|
||||||
|
<div class="text-sm font-bold text-contrast">Minecraft version</div>
|
||||||
|
<UiServersTeleportDropdownMenu
|
||||||
|
v-model="selectedMCVersion"
|
||||||
|
name="mcVersion"
|
||||||
|
:options="mcVersions"
|
||||||
|
class="w-full max-w-[100%]"
|
||||||
|
placeholder="Select Minecraft version..."
|
||||||
/>
|
/>
|
||||||
<label for="hard-reset">Clean reinstall</label>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="selectedLoader.toLowerCase() !== 'vanilla'"
|
||||||
|
class="flex w-full flex-col gap-2 rounded-2xl p-4"
|
||||||
|
:class="{
|
||||||
|
'bg-table-alternateRow':
|
||||||
|
!selectedMCVersion || isLoading || selectedLoaderVersions.length > 0,
|
||||||
|
'bg-highlight-red':
|
||||||
|
selectedMCVersion && !isLoading && selectedLoaderVersions.length === 0,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="text-sm font-bold text-contrast">{{ selectedLoader }} version</div>
|
||||||
|
|
||||||
|
<template v-if="!selectedMCVersion">
|
||||||
|
<div
|
||||||
|
class="relative flex h-9 w-full select-none items-center rounded-xl bg-button-bg px-4 opacity-50"
|
||||||
|
>
|
||||||
|
Select a Minecraft version to see available versions
|
||||||
|
<DropdownIcon class="absolute right-4" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="isLoading">
|
||||||
|
<div
|
||||||
|
class="relative flex h-9 w-full items-center rounded-xl bg-button-bg px-4 opacity-50"
|
||||||
|
>
|
||||||
|
<UiServersIconsLoadingIcon class="mr-2 animate-spin" />
|
||||||
|
Loading versions...
|
||||||
|
<DropdownIcon class="absolute right-4" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="selectedLoaderVersions.length > 0">
|
||||||
|
<UiServersTeleportDropdownMenu
|
||||||
|
v-model="selectedLoaderVersion"
|
||||||
|
name="loaderVersion"
|
||||||
|
:options="selectedLoaderVersions"
|
||||||
|
class="w-full max-w-[100%]"
|
||||||
|
:placeholder="
|
||||||
|
selectedLoader.toLowerCase() === 'paper' ||
|
||||||
|
selectedLoader.toLowerCase() === 'purpur'
|
||||||
|
? `Select build number...`
|
||||||
|
: `Select loader version...`
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div>No versions available for Minecraft {{ selectedMCVersion }}.</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4">
|
||||||
|
<div class="flex w-full flex-row items-center justify-between">
|
||||||
|
<label class="w-full text-lg font-bold text-contrast" for="hard-reset">
|
||||||
|
Erase all data
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="hard-reset"
|
||||||
|
:checked="hardReset"
|
||||||
|
class="switch stylized-toggle shrink-0"
|
||||||
|
type="checkbox"
|
||||||
|
@change="hardReset = ($event.target as HTMLInputElement).checked"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Removes all data on your server, including your worlds, mods, and configuration files,
|
||||||
|
then reinstalls it with the selected version.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4">
|
||||||
|
<div class="flex w-full flex-row items-center justify-between">
|
||||||
|
<label class="w-full text-lg font-bold text-contrast" for="backup-server">
|
||||||
|
Backup server
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="backup-server"
|
||||||
|
:checked="backupServer"
|
||||||
|
class="switch stylized-toggle shrink-0"
|
||||||
|
type="checkbox"
|
||||||
|
@change="backupServer = ($event.target as HTMLInputElement).checked"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Creates a backup of your server before proceeding with the installation or
|
||||||
|
reinstallation.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex justify-start gap-4">
|
<div class="mt-4 flex justify-start gap-4">
|
||||||
@ -53,13 +154,15 @@
|
|||||||
<button :disabled="canInstall" @click="handleReinstall">
|
<button :disabled="canInstall" @click="handleReinstall">
|
||||||
<RightArrowIcon />
|
<RightArrowIcon />
|
||||||
{{
|
{{
|
||||||
isSecondPhase
|
isBackingUp
|
||||||
? "Erase and install"
|
? "Backing up..."
|
||||||
: loadingServerCheck
|
: isSecondPhase
|
||||||
? "Loading..."
|
? "Erase and install"
|
||||||
: isDangerous
|
: loadingServerCheck
|
||||||
? "Erase and install"
|
? "Loading..."
|
||||||
: "Install"
|
: isDangerous
|
||||||
|
? "Erase and install"
|
||||||
|
: "Install"
|
||||||
}}
|
}}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
@ -75,51 +178,130 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
{{ isSecondPhase ? "No" : "Cancel" }}
|
{{ isSecondPhase ? "Go back" : "Cancel" }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NewModal>
|
</NewModal>
|
||||||
|
|
||||||
<NewModal ref="mrpackModal" header="Upload mrpack" @hide="onHide" @show="onShow">
|
<NewModal ref="mrpackModal" header="Uploading mrpack" @hide="onHide" @show="onShow">
|
||||||
<div>
|
<div class="flex flex-col gap-4 md:w-[600px]">
|
||||||
<div class="mt-2 flex items-center gap-2">
|
<p
|
||||||
<input
|
v-if="isSecondPhase"
|
||||||
id="hard-reset"
|
:style="{
|
||||||
:checked="hardReset"
|
lineHeight: isSecondPhase ? '1.5' : undefined,
|
||||||
class="switch stylized-toggle"
|
marginBottom: isSecondPhase ? '-12px' : '0',
|
||||||
type="checkbox"
|
marginTop: isSecondPhase ? '-4px' : '-2px',
|
||||||
@change="hardReset = ($event.target as HTMLInputElement).checked"
|
}"
|
||||||
/>
|
>
|
||||||
<label for="hard-reset">Clean reinstall</label>
|
This will reinstall your server and erase all data. You may want to back up your server
|
||||||
|
before proceeding. Are you sure you want to continue?
|
||||||
|
</p>
|
||||||
|
<div v-if="!isSecondPhase" class="flex flex-col gap-4">
|
||||||
|
<div class="mx-auto flex flex-row items-center gap-4">
|
||||||
|
<div
|
||||||
|
class="grid size-16 place-content-center rounded-2xl border-[2px] border-solid border-button-border bg-button-bg shadow-sm"
|
||||||
|
>
|
||||||
|
<UploadIcon class="size-10" />
|
||||||
|
</div>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="size-10"
|
||||||
|
>
|
||||||
|
<path d="M5 9v6" />
|
||||||
|
<path d="M9 9h3V5l7 7-7 7v-4H9V9z" />
|
||||||
|
</svg>
|
||||||
|
<div
|
||||||
|
class="grid size-16 place-content-center rounded-2xl border-[2px] border-solid border-button-border bg-table-alternateRow shadow-sm"
|
||||||
|
>
|
||||||
|
<ServerIcon class="size-10" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4">
|
||||||
|
<div class="text-sm font-bold text-contrast">Upload mrpack</div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".mrpack"
|
||||||
|
class=""
|
||||||
|
:disabled="isLoading"
|
||||||
|
@change="uploadMrpack"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4">
|
||||||
|
<div class="flex w-full flex-row items-center justify-between">
|
||||||
|
<label class="w-full text-lg font-bold text-contrast" for="hard-reset">
|
||||||
|
Erase all data
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="hard-reset"
|
||||||
|
:checked="hardReset"
|
||||||
|
class="switch stylized-toggle shrink-0"
|
||||||
|
type="checkbox"
|
||||||
|
@change="hardReset = ($event.target as HTMLInputElement).checked"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Removes all data on your server, including your worlds, mods, and configuration files,
|
||||||
|
then reinstalls it with the selected version.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4">
|
||||||
|
<div class="flex w-full flex-row items-center justify-between">
|
||||||
|
<label class="w-full text-lg font-bold text-contrast" for="backup-server-mrpack">
|
||||||
|
Backup server
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="backup-server-mrpack"
|
||||||
|
:checked="backupServer"
|
||||||
|
class="switch stylized-toggle shrink-0"
|
||||||
|
type="checkbox"
|
||||||
|
@change="backupServer = ($event.target as HTMLInputElement).checked"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>Creates a backup of your server before proceeding.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept=".mrpack"
|
|
||||||
class="mt-4"
|
|
||||||
:disabled="isLoading"
|
|
||||||
@change="uploadMrpack"
|
|
||||||
/>
|
|
||||||
<div class="mt-4 flex justify-start gap-4">
|
<div class="mt-4 flex justify-start gap-4">
|
||||||
<ButtonStyled :color="isDangerous ? 'red' : 'brand'">
|
<ButtonStyled :color="isDangerous ? 'red' : 'brand'">
|
||||||
<button :disabled="!mrpackFile || isLoading" @click="reinstallMrpack">
|
<button :disabled="canInstallUpload" @click="handleReinstallUpload">
|
||||||
<RightArrowIcon />
|
<RightArrowIcon />
|
||||||
{{
|
{{
|
||||||
isSecondPhase
|
isBackingUp
|
||||||
? "Erase and install"
|
? "Backing up..."
|
||||||
: loadingServerCheck
|
: isSecondPhase
|
||||||
? "Loading..."
|
? "Erase and install"
|
||||||
: isDangerous
|
: loadingServerCheck
|
||||||
? "Erase and install"
|
? "Loading..."
|
||||||
: "Install"
|
: isDangerous
|
||||||
|
? "Erase and install"
|
||||||
|
: "Install"
|
||||||
}}
|
}}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled>
|
<ButtonStyled>
|
||||||
<button :disabled="isLoading" @click="mrpackModal?.hide">
|
<button
|
||||||
|
:disabled="isLoading"
|
||||||
|
@click="
|
||||||
|
if (isSecondPhase) {
|
||||||
|
isSecondPhase = false;
|
||||||
|
} else {
|
||||||
|
mrpackModal?.hide();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
Cancel
|
{{ isSecondPhase ? "Go back" : "Cancel" }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
@ -199,7 +381,7 @@
|
|||||||
<div v-else class="flex w-full flex-col items-center gap-2 sm:w-fit sm:flex-row">
|
<div v-else class="flex w-full flex-col items-center gap-2 sm:w-fit sm:flex-row">
|
||||||
<ButtonStyled>
|
<ButtonStyled>
|
||||||
<nuxt-link class="!w-full sm:!w-auto" :to="`/modpacks?sid=${props.server.serverId}`">
|
<nuxt-link class="!w-full sm:!w-auto" :to="`/modpacks?sid=${props.server.serverId}`">
|
||||||
<DownloadIcon class="size-4" /> Install a modpack
|
<CompassIcon class="size-4" /> Find a modpack
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<span class="hidden sm:block">or</span>
|
<span class="hidden sm:block">or</span>
|
||||||
@ -213,18 +395,21 @@
|
|||||||
|
|
||||||
<div class="card flex flex-col gap-4">
|
<div class="card flex flex-col gap-4">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h2 class="m-0 text-lg font-bold text-contrast">Mod loader</h2>
|
<h2 class="m-0 text-lg font-bold text-contrast">Platform</h2>
|
||||||
<p class="m-0">Mod loaders allow you to run mods on your server.</p>
|
<p class="m-0">
|
||||||
|
Your server's platform is the software that runs your server. Different platforms
|
||||||
|
support different mods and plugins.
|
||||||
|
</p>
|
||||||
<div v-if="data.upstream" class="mt-2 flex items-center gap-2">
|
<div v-if="data.upstream" class="mt-2 flex items-center gap-2">
|
||||||
<InfoIcon class="hidden sm:block" />
|
<InfoIcon class="hidden sm:block" />
|
||||||
<span class="text-sm text-secondary">
|
<span class="text-sm text-secondary">
|
||||||
Your server was installed from a modpack, which automatically chooses the appropriate
|
Your server was installed from a modpack, which automatically chooses the appropriate
|
||||||
mod loader.
|
platform.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex w-full flex-col gap-1 rounded-2xl bg-table-alternateRow p-2"
|
class="flex w-full flex-col gap-1 rounded-2xl"
|
||||||
:class="{
|
:class="{
|
||||||
'pointer-events-none cursor-not-allowed select-none opacity-50':
|
'pointer-events-none cursor-not-allowed select-none opacity-50':
|
||||||
props.server.general?.status === 'installing' && isError,
|
props.server.general?.status === 'installing' && isError,
|
||||||
@ -236,7 +421,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UiServersPyroLoading v-else />
|
<div v-else />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -249,14 +434,18 @@ import {
|
|||||||
InfoIcon,
|
InfoIcon,
|
||||||
RightArrowIcon,
|
RightArrowIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
|
CompassIcon,
|
||||||
|
DropdownIcon,
|
||||||
|
ServerIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import type { Server } from "~/composables/pyroServers";
|
import type { Server } from "~/composables/pyroServers";
|
||||||
|
import type { Loaders } from "~/types/servers";
|
||||||
|
|
||||||
const route = useNativeRoute();
|
const route = useNativeRoute();
|
||||||
const serverId = route.params.id as string;
|
const serverId = route.params.id as string;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -274,6 +463,7 @@ const isError = computed(() => props.server.general?.status === "error");
|
|||||||
const isDangerous = computed(() => hardReset.value);
|
const isDangerous = computed(() => hardReset.value);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const isBackupLimited = computed(() => (props.server.backups?.data?.length || 0) >= 15);
|
const isBackupLimited = computed(() => (props.server.backups?.data?.length || 0) >= 15);
|
||||||
|
const isBackingUp = ref(false);
|
||||||
|
|
||||||
const versionStrings = ["forge", "fabric", "quilt", "neo"] as const;
|
const versionStrings = ["forge", "fabric", "quilt", "neo"] as const;
|
||||||
|
|
||||||
@ -312,7 +502,6 @@ const loaderVersions = (await Promise.all(
|
|||||||
}[]
|
}[]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const editModal = ref();
|
|
||||||
const versionSelectModal = ref();
|
const versionSelectModal = ref();
|
||||||
const mrpackModal = ref();
|
const mrpackModal = ref();
|
||||||
|
|
||||||
@ -330,6 +519,16 @@ const canInstall = computed(() => {
|
|||||||
return conds || !selectedLoaderVersion.value;
|
return conds || !selectedLoaderVersion.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const canInstallUpload = computed(() => {
|
||||||
|
const conds =
|
||||||
|
!mrpackFile.value ||
|
||||||
|
isLoading.value ||
|
||||||
|
loadingServerCheck.value ||
|
||||||
|
serverCheckError.value.trim().length > 0;
|
||||||
|
|
||||||
|
return conds;
|
||||||
|
});
|
||||||
|
|
||||||
const mcVersions = tags.value.gameVersions
|
const mcVersions = tags.value.gameVersions
|
||||||
.filter((x) => x.version_type === "release")
|
.filter((x) => x.version_type === "release")
|
||||||
.map((x) => x.version)
|
.map((x) => x.version)
|
||||||
@ -343,27 +542,37 @@ const mcVersions = tags.value.gameVersions
|
|||||||
});
|
});
|
||||||
|
|
||||||
const selectedLoaderVersions = computed(() => {
|
const selectedLoaderVersions = computed(() => {
|
||||||
/*
|
const loader = selectedLoader.value.toLowerCase();
|
||||||
loaderVersions[
|
|
||||||
selectedLoader.value.toLowerCase() === "neoforge" ? "neo" : selectedLoader.toLowerCase()
|
if (loader === "paper") {
|
||||||
]
|
return paperVersions.value[selectedMCVersion.value] || [];
|
||||||
.find((x) => x.id === selectedMCVersion)
|
|
||||||
?.loaders.map((x) => x.id) || []
|
|
||||||
*/
|
|
||||||
let loader = selectedLoader.value.toLowerCase();
|
|
||||||
if (loader === "neoforge") {
|
|
||||||
loader = "neo";
|
|
||||||
}
|
}
|
||||||
const backwardsCompatibleVersion = loaderVersions[loader].find(
|
|
||||||
|
if (loader === "purpur") {
|
||||||
|
return purpurVersions.value[selectedMCVersion.value] || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loader === "vanilla") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let apiLoader = loader;
|
||||||
|
if (loader === "neoforge") {
|
||||||
|
apiLoader = "neo";
|
||||||
|
}
|
||||||
|
|
||||||
|
const backwardsCompatibleVersion = loaderVersions[apiLoader]?.find(
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
(x) => x.id === "${modrinth.gameVersion}",
|
(x) => x.id === "${modrinth.gameVersion}",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (backwardsCompatibleVersion) {
|
if (backwardsCompatibleVersion) {
|
||||||
return backwardsCompatibleVersion.loaders.map((x) => x.id);
|
return backwardsCompatibleVersion.loaders.map((x) => x.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
loaderVersions[loader]
|
loaderVersions[apiLoader]
|
||||||
.find((x) => x.id === selectedMCVersion.value)
|
?.find((x) => x.id === selectedMCVersion.value)
|
||||||
?.loaders.map((x) => x.id) || []
|
?.loaders.map((x) => x.id) || []
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -395,7 +604,7 @@ const versionIds = computed(() =>
|
|||||||
const version = ref();
|
const version = ref();
|
||||||
const currentVersion = ref();
|
const currentVersion = ref();
|
||||||
|
|
||||||
const selectedLoader = ref("");
|
const selectedLoader = ref<Loaders>("Vanilla");
|
||||||
const selectedMCVersion = ref("");
|
const selectedMCVersion = ref("");
|
||||||
const selectedLoaderVersion = ref("");
|
const selectedLoaderVersion = ref("");
|
||||||
const isSecondPhase = ref(false);
|
const isSecondPhase = ref(false);
|
||||||
@ -409,8 +618,35 @@ const updateData = async () => {
|
|||||||
};
|
};
|
||||||
updateData();
|
updateData();
|
||||||
|
|
||||||
|
const paperVersions = ref<Record<string, number[]>>({});
|
||||||
|
const purpurVersions = ref<Record<string, string[]>>({});
|
||||||
|
|
||||||
|
const fetchPaperVersions = async (mcVersion: string) => {
|
||||||
|
try {
|
||||||
|
const res = await $fetch(`https://api.papermc.io/v2/projects/paper/versions/${mcVersion}`);
|
||||||
|
paperVersions.value[mcVersion] = (res as any).builds.sort((a: number, b: number) => b - a);
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchPurpurVersions = async (mcVersion: string) => {
|
||||||
|
try {
|
||||||
|
const res = await $fetch(`https://api.purpurmc.org/v2/purpur/${mcVersion}`);
|
||||||
|
purpurVersions.value[mcVersion] = (res as any).builds.all.sort(
|
||||||
|
(a: string, b: string) => parseInt(b) - parseInt(a),
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const selectLoader = (loader: string) => {
|
const selectLoader = (loader: string) => {
|
||||||
selectedLoader.value = loader;
|
selectedLoader.value = loader as Loaders;
|
||||||
versionSelectModal.value.show();
|
versionSelectModal.value.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -421,24 +657,48 @@ const cachedVersions: Record<string, any> = {};
|
|||||||
|
|
||||||
watch(selectedMCVersion, async () => {
|
watch(selectedMCVersion, async () => {
|
||||||
if (selectedMCVersion.value.trim().length < 3) return;
|
if (selectedMCVersion.value.trim().length < 3) return;
|
||||||
// const res = await fetch(
|
|
||||||
// `/loader-versions?loader=minecraft&version=${selectedMCVersion.value}`,
|
|
||||||
// ).then((r) => r.json());
|
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
loadingServerCheck.value = true;
|
loadingServerCheck.value = true;
|
||||||
const res =
|
|
||||||
cachedVersions[selectedMCVersion.value] ||
|
|
||||||
(await $fetch(`/loader-versions?loader=minecraft&version=${selectedMCVersion.value}`));
|
|
||||||
|
|
||||||
cachedVersions[selectedMCVersion.value] = res;
|
try {
|
||||||
|
// Check if Minecraft version exists
|
||||||
|
const mcRes =
|
||||||
|
cachedVersions[selectedMCVersion.value] ||
|
||||||
|
(await $fetch(`/loader-versions?loader=minecraft&version=${selectedMCVersion.value}`));
|
||||||
|
|
||||||
loadingServerCheck.value = false;
|
cachedVersions[selectedMCVersion.value] = mcRes;
|
||||||
|
|
||||||
|
if (!mcRes.downloads?.server) {
|
||||||
|
serverCheckError.value =
|
||||||
|
"We couldn't find a server.jar for this version. Please pick another one.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch Paper/Purpur versions if needed
|
||||||
|
if (selectedLoader.value.toLowerCase() === "paper") {
|
||||||
|
const paperRes = await fetchPaperVersions(selectedMCVersion.value);
|
||||||
|
if (!paperRes) {
|
||||||
|
serverCheckError.value = "This Minecraft version is not supported by Paper.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedLoader.value.toLowerCase() === "purpur") {
|
||||||
|
const purpurRes = await fetchPurpurVersions(selectedMCVersion.value);
|
||||||
|
if (!purpurRes) {
|
||||||
|
serverCheckError.value = "This Minecraft version is not supported by Purpur.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (res.downloads.server) {
|
|
||||||
serverCheckError.value = "";
|
serverCheckError.value = "";
|
||||||
} else {
|
} catch (error) {
|
||||||
serverCheckError.value =
|
console.error(error);
|
||||||
"We couldn't find a server.jar for this version. Please pick another one.";
|
serverCheckError.value = "Failed to fetch versions. Please try again.";
|
||||||
|
} finally {
|
||||||
|
loadingServerCheck.value = false;
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -510,7 +770,36 @@ const handleReinstall = async () => {
|
|||||||
});
|
});
|
||||||
const backupName = `Reinstallation - ${format}`;
|
const backupName = `Reinstallation - ${format}`;
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
await props.server.backups?.create(backupName);
|
const backupId = (await props.server.backups?.create(backupName)) as unknown as string;
|
||||||
|
|
||||||
|
isBackingUp.value = true;
|
||||||
|
|
||||||
|
let attempts = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
attempts += 1;
|
||||||
|
|
||||||
|
if (attempts > 100) {
|
||||||
|
addNotification({
|
||||||
|
group: "server",
|
||||||
|
title: "Backup Failed",
|
||||||
|
text: "An unexpected error occurred while backing up. Please try again later.",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
isLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await props.server.refresh(["backups"]);
|
||||||
|
const backups = await props.server.backups?.data;
|
||||||
|
const backup = backupId ? backups?.find((x) => x.id === backupId) : undefined;
|
||||||
|
if (backup && !backup.ongoing) {
|
||||||
|
console.log("Backup Finished");
|
||||||
|
isBackingUp.value = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
addNotification({
|
addNotification({
|
||||||
group: "server",
|
group: "server",
|
||||||
@ -551,24 +840,6 @@ const handleReinstall = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const reinstallNew = async (project: any, versionNumber: string) => {
|
|
||||||
editModal.value.hide();
|
|
||||||
try {
|
|
||||||
const versions = (await useBaseFetch(`project/${project.project_id}/version`)) as any;
|
|
||||||
const version = versions.find((x: any) => x.version_number === versionNumber);
|
|
||||||
|
|
||||||
if (!version?.id) {
|
|
||||||
throw new Error("Version not found");
|
|
||||||
}
|
|
||||||
await props.server.general?.reinstall(serverId, false, project.project_id, version.id);
|
|
||||||
emit("reinstall");
|
|
||||||
await nextTick();
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
} catch (error) {
|
|
||||||
handleReinstallError(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const mrpackFile = ref<File | null>(null);
|
const mrpackFile = ref<File | null>(null);
|
||||||
|
|
||||||
const uploadMrpack = (event: Event) => {
|
const uploadMrpack = (event: Event) => {
|
||||||
@ -579,19 +850,86 @@ const uploadMrpack = (event: Event) => {
|
|||||||
mrpackFile.value = target.files[0];
|
mrpackFile.value = target.files[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
const reinstallMrpack = async () => {
|
const handleReinstallUpload = async () => {
|
||||||
if (!mrpackFile.value) {
|
if (hardReset.value && !backupServer.value && !isSecondPhase.value) {
|
||||||
|
isSecondPhase.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mrpack = new File([mrpackFile.value], mrpackFile.value.name, {
|
if (backupServer.value) {
|
||||||
type: mrpackFile.value.type,
|
try {
|
||||||
});
|
const date = new Date();
|
||||||
|
const format = date.toLocaleString(navigator.language || "en-US", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
second: "numeric",
|
||||||
|
timeZoneName: "short",
|
||||||
|
});
|
||||||
|
const backupName = `Reinstallation - ${format}`;
|
||||||
|
isLoading.value = true;
|
||||||
|
const backupId = (await props.server.backups?.create(backupName)) as unknown as string;
|
||||||
|
|
||||||
|
isBackingUp.value = true;
|
||||||
|
|
||||||
|
let attempts = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
attempts += 1;
|
||||||
|
|
||||||
|
if (attempts > 100) {
|
||||||
|
addNotification({
|
||||||
|
group: "server",
|
||||||
|
title: "Backup Failed",
|
||||||
|
text: "An unexpected error occurred while backing up. Please try again later.",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
isLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await props.server.refresh(["backups"]);
|
||||||
|
const backups = await props.server.backups?.data;
|
||||||
|
const backup = backupId ? backups?.find((x) => x.id === backupId) : undefined;
|
||||||
|
if (backup && !backup.ongoing) {
|
||||||
|
console.log("Backup Finished");
|
||||||
|
isBackingUp.value = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
addNotification({
|
||||||
|
group: "server",
|
||||||
|
title: "Backup Failed",
|
||||||
|
text: "An unexpected error occurred while backing up. Please try again later.",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
isLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isLoading.value = true;
|
if (!mrpackFile.value) {
|
||||||
|
throw new Error("No mrpack file selected");
|
||||||
|
}
|
||||||
|
const mrpack = new File([mrpackFile.value], mrpackFile.value.name, {
|
||||||
|
type: mrpackFile.value.type,
|
||||||
|
});
|
||||||
|
|
||||||
await props.server.general?.reinstallFromMrpack(mrpack, hardReset.value);
|
await props.server.general?.reinstallFromMrpack(mrpack, hardReset.value);
|
||||||
emit("reinstall");
|
|
||||||
|
emit("reinstall", {
|
||||||
|
loader: "mrpack",
|
||||||
|
lVersion: "",
|
||||||
|
mVersion: "",
|
||||||
|
});
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -204,6 +204,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex w-full flex-row items-center gap-2 sm:w-auto">
|
<div class="flex w-full flex-row items-center gap-2 sm:w-auto">
|
||||||
|
<UiCopyCode :text="`${serverIP}:${allocation.port}`" />
|
||||||
<ButtonStyled icon-only>
|
<ButtonStyled icon-only>
|
||||||
<button
|
<button
|
||||||
class="!w-full sm:!w-auto"
|
class="!w-full sm:!w-auto"
|
||||||
@ -252,7 +253,7 @@ import { ref, computed, nextTick } from "vue";
|
|||||||
import type { Server } from "~/composables/pyroServers";
|
import type { Server } from "~/composables/pyroServers";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const isUpdating = ref(false);
|
const isUpdating = ref(false);
|
||||||
|
@ -49,7 +49,7 @@ const route = useNativeRoute();
|
|||||||
const serverId = route.params.id as string;
|
const serverId = route.params.id as string;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const preferences = {
|
const preferences = {
|
||||||
|
@ -124,7 +124,7 @@ import Fuse from "fuse.js";
|
|||||||
import type { Server } from "~/composables/pyroServers";
|
import type { Server } from "~/composables/pyroServers";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tags = useTags();
|
const tags = useTags();
|
||||||
|
@ -90,7 +90,7 @@ import { ButtonStyled } from "@modrinth/ui";
|
|||||||
import type { Server } from "~/composables/pyroServers";
|
import type { Server } from "~/composables/pyroServers";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
|
server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const data = computed(() => props.server.general);
|
const data = computed(() => props.server.general);
|
||||||
|
@ -149,6 +149,17 @@ export type ServerState = "running" | "stopped" | "crashed";
|
|||||||
// state: ServerState;
|
// state: ServerState;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
export type Loaders =
|
||||||
|
| "Fabric"
|
||||||
|
| "Quilt"
|
||||||
|
| "Forge"
|
||||||
|
| "NeoForge"
|
||||||
|
| "Paper"
|
||||||
|
| "Spigot"
|
||||||
|
| "Bukkit"
|
||||||
|
| "Vanilla"
|
||||||
|
| "Purpur";
|
||||||
|
|
||||||
export interface WSLogEvent {
|
export interface WSLogEvent {
|
||||||
event: "log";
|
event: "log";
|
||||||
message: string;
|
message: string;
|
||||||
|
1
packages/assets/icons/lock-open.svg
Normal file
1
packages/assets/icons/lock-open.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lock-open"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/></svg>
|
After Width: | Height: | Size: 311 B |
@ -99,6 +99,7 @@ import _LinkIcon from './icons/link.svg?component'
|
|||||||
import _ListIcon from './icons/list.svg?component'
|
import _ListIcon from './icons/list.svg?component'
|
||||||
import _ListEndIcon from './icons/list-end.svg?component'
|
import _ListEndIcon from './icons/list-end.svg?component'
|
||||||
import _LockIcon from './icons/lock.svg?component'
|
import _LockIcon from './icons/lock.svg?component'
|
||||||
|
import _OpenLockIcon from './icons/lock-open.svg?component'
|
||||||
import _LogInIcon from './icons/log-in.svg?component'
|
import _LogInIcon from './icons/log-in.svg?component'
|
||||||
import _LogOutIcon from './icons/log-out.svg?component'
|
import _LogOutIcon from './icons/log-out.svg?component'
|
||||||
import _MailIcon from './icons/mail.svg?component'
|
import _MailIcon from './icons/mail.svg?component'
|
||||||
@ -279,6 +280,7 @@ export const LinkIcon = _LinkIcon
|
|||||||
export const ListIcon = _ListIcon
|
export const ListIcon = _ListIcon
|
||||||
export const ListEndIcon = _ListEndIcon
|
export const ListEndIcon = _ListEndIcon
|
||||||
export const LockIcon = _LockIcon
|
export const LockIcon = _LockIcon
|
||||||
|
export const OpenLockIcon = _OpenLockIcon
|
||||||
export const LogInIcon = _LogInIcon
|
export const LogInIcon = _LogInIcon
|
||||||
export const LogOutIcon = _LogOutIcon
|
export const LogOutIcon = _LogOutIcon
|
||||||
export const MailIcon = _MailIcon
|
export const MailIcon = _MailIcon
|
||||||
|
@ -75,7 +75,15 @@
|
|||||||
/> -->
|
/> -->
|
||||||
<div class="grid lg:grid-cols-5 grid-cols-3 gap-4">
|
<div class="grid lg:grid-cols-5 grid-cols-3 gap-4">
|
||||||
<button
|
<button
|
||||||
v-for="loader in ['Vanilla', 'Fabric', 'Forge', 'Quilt', 'NeoForge']"
|
v-for="loader in [
|
||||||
|
'Vanilla',
|
||||||
|
'Fabric',
|
||||||
|
'Forge',
|
||||||
|
'Quilt',
|
||||||
|
'NeoForge',
|
||||||
|
'Paper',
|
||||||
|
'Purpur',
|
||||||
|
]"
|
||||||
:key="loader"
|
:key="loader"
|
||||||
class="!h-24 btn flex !flex-col !items-center !justify-between !pt-4 !pb-3 !w-full"
|
class="!h-24 btn flex !flex-col !items-center !justify-between !pt-4 !pb-3 !w-full"
|
||||||
:style="{
|
:style="{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user