From 742c0edd9ec05e30090ab8800f2f6bc71ad1f93a Mon Sep 17 00:00:00 2001 From: TheWander02 <48934424+thewander02@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:49:50 -0700 Subject: [PATCH] Paper and Purpur + Backups (#3004) * feat: init selecting paper+purpur on purchase flow Signed-off-by: Evan Song * feat: properly implement Paper/Purpur in Platform Signed-off-by: Evan Song * chore: correct wording Signed-off-by: Evan Song * feat: redo platform modal Signed-off-by: Evan Song * 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 * fix: handle "not yet supported" install err Signed-off-by: Evan Song * chore: fix icon kerfuffles Signed-off-by: Evan Song * chore: improve vanilla install modal title Signed-off-by: Evan Song * fix: spacing Signed-off-by: Evan Song * chore: improve no loader state Signed-off-by: Evan Song * fix: type error Signed-off-by: Evan Song * chore: adjust mod version modal title Signed-off-by: Evan Song * chore: adjust modpack warning copy Signed-off-by: Evan Song * feat: vanilla empty state in content page Signed-off-by: Evan Song * chore: adjust copy Signed-off-by: Evan Song * chore: update icon Signed-off-by: Evan Song * fix: loader type Signed-off-by: Evan Song * fix: loader type Signed-off-by: Evan Song * feat: always show dropdown if possible Signed-off-by: Evan Song * chore: improve spacing Signed-off-by: Evan Song * chore: appear disabled Signed-off-by: Evan Song * h Signed-off-by: Evan Song * chore: if reinstalling, show it on the modal title Signed-off-by: Evan Song * feat: put it in the dropdown, they said Signed-off-by: Evan Song * chore: adjust style Signed-off-by: Evan Song * chore: sort paper-purpur versions desc Signed-off-by: Evan Song * fix: do not consider backup limit in reinstall prompt Signed-off-by: Evan Song * feat: backup locking, plugin support * fix: content type error Signed-off-by: Evan Song * fix: casing Signed-off-by: Evan Song * fix: plugins pt 2 * feat: backups, mrpack * fix: type errors come on Signed-off-by: Evan Song * fix: spacing Signed-off-by: Evan Song * fix: type maxing * chore: show copy button on allocation rows Signed-off-by: Evan Song * feat: suspend improvement --------- Signed-off-by: Evan Song Co-authored-by: Evan Song Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> Co-authored-by: Jai A Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com> --- .../ui/servers/BackupCreateModal.vue | 2 +- .../ui/servers/BackupSettingsModal.vue | 4 +- .../components/ui/servers/LoaderSelector.vue | 94 +-- .../ui/servers/LoaderSelectorCard.vue | 70 +++ .../components/ui/servers/ServerListing.vue | 23 +- .../ui/servers/icons/LoaderIcon.vue | 62 +- apps/frontend/src/composables/pyroServers.ts | 108 +++- .../src/pages/search/[searchProjectType].vue | 14 +- .../src/pages/servers/manage/[id].vue | 50 +- .../src/pages/servers/manage/[id]/backups.vue | 33 + .../src/pages/servers/manage/[id]/content.vue | 2 +- .../servers/manage/[id]/content/index.vue | 96 ++- .../src/pages/servers/manage/[id]/files.vue | 2 +- .../src/pages/servers/manage/[id]/index.vue | 1 + .../src/pages/servers/manage/[id]/options.vue | 2 +- .../servers/manage/[id]/options/index.vue | 6 +- .../servers/manage/[id]/options/info.vue | 2 +- .../servers/manage/[id]/options/loader.vue | 594 ++++++++++++++---- .../servers/manage/[id]/options/network.vue | 3 +- .../manage/[id]/options/preferences.vue | 2 +- .../manage/[id]/options/properties.vue | 2 +- .../servers/manage/[id]/options/startup.vue | 2 +- apps/frontend/src/types/servers.ts | 11 + packages/assets/icons/lock-open.svg | 1 + packages/assets/index.ts | 2 + .../src/components/billing/PurchaseModal.vue | 10 +- 26 files changed, 951 insertions(+), 247 deletions(-) create mode 100644 apps/frontend/src/components/ui/servers/LoaderSelectorCard.vue create mode 100644 packages/assets/icons/lock-open.svg diff --git a/apps/frontend/src/components/ui/servers/BackupCreateModal.vue b/apps/frontend/src/components/ui/servers/BackupCreateModal.vue index fc4753254..31c3a6578 100644 --- a/apps/frontend/src/components/ui/servers/BackupCreateModal.vue +++ b/apps/frontend/src/components/ui/servers/BackupCreateModal.vue @@ -44,7 +44,7 @@ import { ButtonStyled, NewModal } from "@modrinth/ui"; import { PlusIcon, XIcon, InfoIcon } from "@modrinth/assets"; 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"]); diff --git a/apps/frontend/src/components/ui/servers/BackupSettingsModal.vue b/apps/frontend/src/components/ui/servers/BackupSettingsModal.vue index e85646309..52892bdf5 100644 --- a/apps/frontend/src/components/ui/servers/BackupSettingsModal.vue +++ b/apps/frontend/src/components/ui/servers/BackupSettingsModal.vue @@ -104,7 +104,7 @@ const modal = ref>(); const initialSettings = ref<{ interval: number; enabled: boolean } | null>(null); const autoBackupEnabled = ref(false); -const autoBackupInterval = ref(1); +const autoBackupInterval = ref(6); const isLoadingSettings = ref(true); const isSaving = ref(false); @@ -134,7 +134,7 @@ const fetchSettings = async () => { const settings = await props.server.backups?.getAutoBackup(); initialSettings.value = settings as { interval: number; enabled: boolean }; autoBackupEnabled.value = settings?.enabled ?? false; - autoBackupInterval.value = settings?.interval || 1; + autoBackupInterval.value = settings?.interval || 6; } catch (error) { console.error("Error fetching backup settings:", error); addNotification({ diff --git a/apps/frontend/src/components/ui/servers/LoaderSelector.vue b/apps/frontend/src/components/ui/servers/LoaderSelector.vue index ceb3e3143..f1e1a14ff 100644 --- a/apps/frontend/src/components/ui/servers/LoaderSelector.vue +++ b/apps/frontend/src/components/ui/servers/LoaderSelector.vue @@ -1,52 +1,60 @@ diff --git a/apps/frontend/src/components/ui/servers/ServerListing.vue b/apps/frontend/src/components/ui/servers/ServerListing.vue index 18be551ff..96021cca7 100644 --- a/apps/frontend/src/components/ui/servers/ServerListing.vue +++ b/apps/frontend/src/components/ui/servers/ServerListing.vue @@ -1,11 +1,26 @@ diff --git a/apps/frontend/src/composables/pyroServers.ts b/apps/frontend/src/composables/pyroServers.ts index 811ca15d7..1610c1f61 100644 --- a/apps/frontend/src/composables/pyroServers.ts +++ b/apps/frontend/src/composables/pyroServers.ts @@ -213,6 +213,7 @@ interface Backup { name: string; created_at: string; ongoing: boolean; + locked: boolean; } interface AutoBackupSettings { @@ -225,6 +226,8 @@ interface JWTAuth { token: string; } +type ContentType = "Mod" | "Plugin"; + const constructServerProperties = (properties: any): string => { 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 { await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods`, { 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) { 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 { await PyroFetch(`servers/${internalServerRefrence.value.serverId}/deleteMod`, { method: "POST", body: { - path: modId, + install_as: contentType, + path: contentId, }, }); } 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 { - await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods/${modId}`, { + await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods/${contentId}`, { method: "PUT", - body: { version_id: versionId }, + body: { install_as: contentType, version_id: newContentId }, }); } catch (error) { console.error("Error reinstalling mod:", error); @@ -527,10 +538,11 @@ const reinstallMod = async (modId: string, versionId: string) => { const createBackup = async (backupName: string) => { try { - await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups`, { + const response = (await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups`, { method: "POST", body: { name: backupName }, - }); + })) as { id: string }; + return response.id; } catch (error) { console.error("Error creating backup:", 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 ------------------ // const reserveAllocation = async (name: string): Promise => { @@ -858,7 +898,7 @@ const modules: any = { setMotd, fetchConfigFile, }, - mods: { + content: { get: async (serverId: string) => { try { const mods = await PyroFetch(`servers/${serverId}/mods`); @@ -873,9 +913,9 @@ const modules: any = { return undefined; } }, - install: installMod, - remove: removeMod, - reinstall: reinstallMod, + install: installContent, + remove: removeContent, + reinstall: reinstallContent, }, backups: { get: async (serverId: string) => { @@ -893,6 +933,8 @@ const modules: any = { download: downloadBackup, updateAutoBackup, getAutoBackup, + lock: lockBackup, + unlock: unlockBackup, }, network: { get: async (serverId: string) => { @@ -1018,9 +1060,9 @@ type GeneralFunctions = { fetchConfigFile: (fileName: string) => Promise; }; -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. * @returns */ @@ -1028,23 +1070,26 @@ type ModFunctions = { /** * Installs a mod to a server. + * @param contentType - The type of content to install. * @param projectId - The ID of the project. * @param versionId - The ID of the version. */ - install: (projectId: string, versionId: string) => Promise; + install: (contentType: ContentType, projectId: string, versionId: string) => Promise; /** * 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; + remove: (contentType: ContentType, contentId: string) => Promise; /** * Reinstalls a mod to a server. - * @param modId - The ID of the mod. - * @param versionId - The ID of the version. + * @param contentType - The type of content to reinstall. + * @param contentId - The ID of the content. + * @param newContentId - The ID of the new version. */ - reinstall: (modId: string, versionId: string) => Promise; + reinstall: (contentType: ContentType, contentId: string, newContentId: string) => Promise; }; type BackupFunctions = { @@ -1058,6 +1103,7 @@ type BackupFunctions = { /** * Creates a new backup for the server. * @param backupName - The name of the backup. + * @returns The ID of the backup. */ create: (backupName: string) => Promise; @@ -1098,6 +1144,18 @@ type BackupFunctions = { * Gets the auto backup settings of the server. */ getAutoBackup: () => Promise; + + /** + * Locks a backup for the server. + * @param backupId - The ID of the backup. + */ + lock: (backupId: string) => Promise; + + /** + * Unlocks a backup for the server. + * @param backupId - The ID of the backup. + */ + unlock: (backupId: string) => Promise; }; type NetworkFunctions = { @@ -1231,7 +1289,7 @@ type FSFunctions = { }; type GeneralModule = General & GeneralFunctions; -type ModsModule = { data: Mod[] } & ModFunctions; +type ContentModule = { data: Mod[] } & ContentFunctions; type BackupsModule = { data: Backup[] } & BackupFunctions; type NetworkModule = { allocations: Allocation[] } & NetworkFunctions; type StartupModule = Startup & StartupFunctions; @@ -1239,7 +1297,7 @@ type FSModule = { auth: JWTAuth } & FSFunctions; type ModulesMap = { general: GeneralModule; - mods: ModsModule; + content: ContentModule; backups: BackupsModule; network: NetworkModule; startup: StartupModule; @@ -1247,7 +1305,7 @@ type ModulesMap = { fs: FSModule; }; -type avaliableModules = ("general" | "mods" | "backups" | "network" | "startup" | "ws" | "fs")[]; +type avaliableModules = ("general" | "content" | "backups" | "network" | "startup" | "ws" | "fs")[]; export type Server = { [K in T[number]]?: ModulesMap[K]; diff --git a/apps/frontend/src/pages/search/[searchProjectType].vue b/apps/frontend/src/pages/search/[searchProjectType].vue index 407d7523d..4bb05ab47 100644 --- a/apps/frontend/src/pages/search/[searchProjectType].vue +++ b/apps/frontend/src/pages/search/[searchProjectType].vue @@ -274,7 +274,7 @@