chore(pyroservers): introduce deferred modules

This commit is contained in:
Evan Song
2025-02-14 03:04:20 -07:00
parent e3700421a4
commit 58e2efb250
4 changed files with 119 additions and 63 deletions

View File

@@ -287,11 +287,17 @@ const constructServerProperties = (properties: any): string => {
};
const processImage = async (iconUrl: string | undefined) => {
const image = ref<string | null>(null);
const sharedImage = useState<string | undefined>(
`server-icon-${internalServerRefrence.value.serverId}`,
() => undefined,
);
if (sharedImage.value) {
return sharedImage.value;
}
const image = ref<string | null>(null);
const auth = await PyroFetch<JWTAuth>(`servers/${internalServerRefrence.value.serverId}/fs`);
try {
const fileData = await PyroFetch(`/download?path=/server-icon-original.png`, {
@@ -313,7 +319,7 @@ const processImage = async (iconUrl: string | undefined) => {
const dataURL = canvas.toDataURL("image/png");
internalServerRefrence.value.general.image = dataURL;
image.value = dataURL;
sharedImage.value = dataURL; // Store in useState
sharedImage.value = dataURL;
resolve();
};
});
@@ -380,6 +386,7 @@ const processImage = async (iconUrl: string | undefined) => {
}
}
}
return image.value;
};
@@ -580,10 +587,14 @@ const reinstallContent = async (replace: string, projectId: string, versionId: s
const createBackup = async (backupName: string) => {
try {
const response = (await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups`, {
method: "POST",
body: { name: backupName },
})) as { id: string };
const response = await PyroFetch<{ id: string }>(
`servers/${internalServerRefrence.value.serverId}/backups`,
{
method: "POST",
body: { name: backupName },
},
);
await internalServerRefrence.value.refresh(["backups"]);
return response.id;
} catch (error) {
console.error("Error creating backup:", error);
@@ -597,6 +608,7 @@ const renameBackup = async (backupId: string, newName: string) => {
method: "POST",
body: { name: newName },
});
await internalServerRefrence.value.refresh(["backups"]);
} catch (error) {
console.error("Error renaming backup:", error);
throw error;
@@ -608,6 +620,7 @@ const deleteBackup = async (backupId: string) => {
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups/${backupId}`, {
method: "DELETE",
});
await internalServerRefrence.value.refresh(["backups"]);
} catch (error) {
console.error("Error deleting backup:", error);
throw error;
@@ -622,6 +635,7 @@ const restoreBackup = async (backupId: string) => {
method: "POST",
},
);
await internalServerRefrence.value.refresh(["backups"]);
} catch (error) {
console.error("Error restoring backup:", error);
throw error;
@@ -660,12 +674,10 @@ const getAutoBackup = async () => {
const lockBackup = async (backupId: string) => {
try {
return await PyroFetch(
`servers/${internalServerRefrence.value.serverId}/backups/${backupId}/lock`,
{
method: "POST",
},
);
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups/${backupId}/lock`, {
method: "POST",
});
await internalServerRefrence.value.refresh(["backups"]);
} catch (error) {
console.error("Error locking backup:", error);
throw error;
@@ -674,14 +686,12 @@ const lockBackup = async (backupId: string) => {
const unlockBackup = async (backupId: string) => {
try {
return await PyroFetch(
`servers/${internalServerRefrence.value.serverId}/backups/${backupId}/unlock`,
{
method: "POST",
},
);
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups/${backupId}/unlock`, {
method: "POST",
});
await internalServerRefrence.value.refresh(["backups"]);
} catch (error) {
console.error("Error locking backup:", error);
console.error("Error unlocking backup:", error);
throw error;
}
};
@@ -970,10 +980,31 @@ const modules: any = {
`https://api.modrinth.com/v2/project/${data.upstream.project_id}`,
);
data.project = res as Project;
if (data.project?.icon_url) {
const sharedImage = useState<string | undefined>(
`server-icon-${serverId}`,
() => undefined,
);
if (!sharedImage.value) {
data.image = (await processImage(data.project.icon_url)) ?? undefined;
sharedImage.value = data.image;
} else {
data.image = sharedImage.value;
}
}
}
const sharedImage = useState<string | undefined>(
`server-icon-${serverId}`,
() => undefined,
);
if (import.meta.client) {
data.image = (await processImage(data.project?.icon_url)) ?? undefined;
data.image =
sharedImage.value ?? (await processImage(data.project?.icon_url)) ?? undefined;
}
const motd = await getMotd();
if (motd === "A Minecraft Server") {
await setMotd(
@@ -1417,6 +1448,7 @@ export type Server<T extends avaliableModules> = {
preserveInstallState?: boolean;
},
) => Promise<void>;
loadModules: (modulesToLoad: avaliableModules) => Promise<void>;
setError: (error: Error) => void;
error?: Error;
serverId: string;
@@ -1469,18 +1501,37 @@ export const usePyroServer = async (serverId: string, includedModules: avaliable
await Promise.allSettled(modulePromises);
},
loadModules: async (modulesToLoad: avaliableModules) => {
const newModules = modulesToLoad.filter((module) => !server[module]);
if (newModules.length === 0) return;
newModules.forEach((module) => {
server[module] = modules[module];
});
await server.refresh(newModules);
},
setError: (error: Error) => {
server.error = error;
},
serverId,
});
includedModules.forEach((module) => {
const initialModules = includedModules.filter((module) => ["general", "ws"].includes(module));
const deferredModules = includedModules.filter((module) => !["general", "ws"].includes(module));
initialModules.forEach((module) => {
server[module] = modules[module];
});
internalServerRefrence.value = server;
await server.refresh();
await server.refresh(initialModules);
if (deferredModules.length > 0) {
nextTick(async () => {
await server.loadModules(deferredModules);
});
}
return server as Server<typeof includedModules>;
};

View File

@@ -258,7 +258,8 @@
<button
v-if="
result.installed ||
server.content.data.find((x) => x.project_id === result.project_id) ||
(server?.content?.data &&
server.content.data.find((x) => x.project_id === result.project_id)) ||
server.general?.project?.id === result.project_id
"
disabled
@@ -376,7 +377,9 @@ async function updateServerContext() {
if (!auth.value.user) {
router.push("/auth/sign-in?redirect=" + encodeURIComponent(route.fullPath));
} else if (route.query.sid !== null) {
server.value = await usePyroServer(route.query.sid, ["general", "content"]);
server.value = await usePyroServer(route.query.sid, ["general", "content"], {
waitForModules: true,
});
}
}
@@ -495,8 +498,8 @@ async function serverInstall(project) {
) ?? versions[0];
if (projectType.value.id === "modpack") {
await server.value.general?.reinstall(
route.query.sid,
await server.value.general.reinstall(
server.value.serverId,
false,
project.project_id,
version.id,
@@ -504,7 +507,7 @@ async function serverInstall(project) {
eraseDataOnInstall.value,
);
project.installed = true;
navigateTo(`/servers/manage/${route.query.sid}/options/loader`);
navigateTo(`/servers/manage/${server.value.serverId}/options/loader`);
} else if (projectType.value.id === "mod") {
await server.value.content.install("mod", version.project_id, version.id);
await server.value.refresh(["content"]);

View File

@@ -363,7 +363,6 @@
</div>
</div>
</div>
<NuxtPage
:route="route"
:is-connected="isConnected"
@@ -425,15 +424,14 @@ const createdAt = ref(
const route = useNativeRoute();
const router = useRouter();
const serverId = route.params.id as string;
const server = await usePyroServer(serverId, [
"general",
"content",
"backups",
"network",
"startup",
"ws",
"fs",
]);
const server = await usePyroServer(serverId, ["general", "ws"]);
const loadModulesPromise = Promise.resolve().then(() =>
server.loadModules(["content", "backups", "network", "startup", "fs"]),
);
provide("modulesLoaded", loadModulesPromise);
watch(
() => server.error,
@@ -664,6 +662,26 @@ const newLoader = ref<string | null>(null);
const newLoaderVersion = ref<string | null>(null);
const newMCVersion = ref<string | null>(null);
const onReinstall = (potentialArgs: any) => {
if (!serverData.value) return;
serverData.value.status = "installing";
if (potentialArgs?.loader) {
newLoader.value = potentialArgs.loader;
}
if (potentialArgs?.lVersion) {
newLoaderVersion.value = potentialArgs.lVersion;
}
if (potentialArgs?.mVersion) {
newMCVersion.value = potentialArgs.mVersion;
}
error.value = null;
errorTitle.value = "Error";
errorMessage.value = "An unexpected error occurred.";
};
const handleInstallationResult = async (data: WSInstallationResultEvent) => {
switch (data.result) {
case "ok": {
@@ -737,26 +755,6 @@ const handleInstallationResult = async (data: WSInstallationResultEvent) => {
}
};
const onReinstall = (potentialArgs: any) => {
if (!serverData.value) return;
serverData.value.status = "installing";
if (potentialArgs?.loader) {
newLoader.value = potentialArgs.loader;
}
if (potentialArgs?.lVersion) {
newLoaderVersion.value = potentialArgs.lVersion;
}
if (potentialArgs?.mVersion) {
newMCVersion.value = potentialArgs.mVersion;
}
error.value = null;
errorTitle.value = "Error";
errorMessage.value = "An unexpected error occurred.";
};
const updateStats = (currentStats: Stats["current"]) => {
isConnected.value = true;
stats.value = {

View File

@@ -297,33 +297,37 @@ const showbackupSettingsModal = () => {
backupSettingsModal.value?.show();
};
const handleBackupCreated = (payload: { success: boolean; message: string }) => {
const handleBackupCreated = async (payload: { success: boolean; message: string }) => {
if (payload.success) {
addNotification({ type: "success", text: payload.message });
await props.server.refresh(["backups"]);
} else {
addNotification({ type: "error", text: payload.message });
}
};
const handleBackupRenamed = (payload: { success: boolean; message: string }) => {
const handleBackupRenamed = async (payload: { success: boolean; message: string }) => {
if (payload.success) {
addNotification({ type: "success", text: payload.message });
await props.server.refresh(["backups"]);
} else {
addNotification({ type: "error", text: payload.message });
}
};
const handleBackupRestored = (payload: { success: boolean; message: string }) => {
const handleBackupRestored = async (payload: { success: boolean; message: string }) => {
if (payload.success) {
addNotification({ type: "success", text: payload.message });
await props.server.refresh(["backups"]);
} else {
addNotification({ type: "error", text: payload.message });
}
};
const handleBackupDeleted = (payload: { success: boolean; message: string }) => {
const handleBackupDeleted = async (payload: { success: boolean; message: string }) => {
if (payload.success) {
addNotification({ type: "success", text: payload.message });
await props.server.refresh(["backups"]);
} else {
addNotification({ type: "error", text: payload.message });
}
@@ -387,8 +391,8 @@ onMounted(() => {
}
if (hasOngoingBackups) {
refreshInterval.value = setInterval(() => {
props.server.refresh(["backups"]);
refreshInterval.value = setInterval(async () => {
await props.server.refresh(["backups"]);
}, 10000);
}
});