From 3cd18b72053c08f56f6deab1533cdde678901341 Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Mon, 27 Apr 2026 13:02:46 +0100 Subject: [PATCH 01/24] feat: world switching page implementation --- .../src/pages/hosting/manage/Worlds.vue | 7 + .../src/pages/hosting/manage/index.js | 3 +- apps/app-frontend/src/routes.js | 8 + .../src/pages/hosting/manage/[id]/worlds.vue | 30 ++ .../ui/src/components/servers/WorldCard.vue | 311 ++++++++++++++++++ .../layouts/wrapped/hosting/manage/root.vue | 122 ++++--- .../layouts/wrapped/hosting/manage/worlds.vue | 288 ++++++++++++++++ packages/ui/src/layouts/wrapped/index.ts | 1 + 8 files changed, 719 insertions(+), 51 deletions(-) create mode 100644 apps/app-frontend/src/pages/hosting/manage/Worlds.vue create mode 100644 apps/frontend/src/pages/hosting/manage/[id]/worlds.vue create mode 100644 packages/ui/src/components/servers/WorldCard.vue create mode 100644 packages/ui/src/layouts/wrapped/hosting/manage/worlds.vue diff --git a/apps/app-frontend/src/pages/hosting/manage/Worlds.vue b/apps/app-frontend/src/pages/hosting/manage/Worlds.vue new file mode 100644 index 0000000000..3df6f264ee --- /dev/null +++ b/apps/app-frontend/src/pages/hosting/manage/Worlds.vue @@ -0,0 +1,7 @@ + + + diff --git a/apps/app-frontend/src/pages/hosting/manage/index.js b/apps/app-frontend/src/pages/hosting/manage/index.js index 50052e3f9e..e06f46e2a2 100644 --- a/apps/app-frontend/src/pages/hosting/manage/index.js +++ b/apps/app-frontend/src/pages/hosting/manage/index.js @@ -3,5 +3,6 @@ import Content from './Content.vue' import Files from './Files.vue' import Index from './Index.vue' import Overview from './Overview.vue' +import Worlds from './Worlds.vue' -export { Backups, Content, Files, Index, Overview } +export { Backups, Content, Files, Index, Overview, Worlds } diff --git a/apps/app-frontend/src/routes.js b/apps/app-frontend/src/routes.js index c12306b5ad..410869c46a 100644 --- a/apps/app-frontend/src/routes.js +++ b/apps/app-frontend/src/routes.js @@ -57,6 +57,14 @@ export default new createRouter({ breadcrumb: [{ name: '?Server' }], }, }, + { + path: 'worlds', + name: 'ServerManageWorlds', + component: Hosting.Worlds, + meta: { + breadcrumb: [{ name: '?Server' }], + }, + }, { path: 'files', name: 'ServerManageFiles', diff --git a/apps/frontend/src/pages/hosting/manage/[id]/worlds.vue b/apps/frontend/src/pages/hosting/manage/[id]/worlds.vue new file mode 100644 index 0000000000..5305138f0a --- /dev/null +++ b/apps/frontend/src/pages/hosting/manage/[id]/worlds.vue @@ -0,0 +1,30 @@ + + + diff --git a/packages/ui/src/components/servers/WorldCard.vue b/packages/ui/src/components/servers/WorldCard.vue new file mode 100644 index 0000000000..db1aacf0bf --- /dev/null +++ b/packages/ui/src/components/servers/WorldCard.vue @@ -0,0 +1,311 @@ + + + diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/root.vue b/packages/ui/src/layouts/wrapped/hosting/manage/root.vue index 36bf2a3e63..ea861824b1 100644 --- a/packages/ui/src/layouts/wrapped/hosting/manage/root.vue +++ b/packages/ui/src/layouts/wrapped/hosting/manage/root.vue @@ -22,8 +22,8 @@ class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast" >

- Your server's node, where your Modrinth Server is physically hosted, is not accessible - at the moment. We are working to resolve the issue as quickly as possible. + {{ formatMessage(messages.nodeUnavailableDescription) }}

- Your data is safe and will not be lost, and your server will be back online as soon as - the issue is resolved. + {{ formatMessage(messages.nodeUnavailableDataDescription) }}

- If reloading does not work initially, please contact Modrinth Support via the chat - bubble in the bottom right corner and we'll be happy to help. + {{ formatMessage(messages.nodeUnavailableSupportDescription) }}

@@ -132,7 +129,7 @@ >
- An internal error occurred while installing your server. Don't fret — try - reinstalling your server, and if the problem persists, please contact Modrinth - support with your server's debug information. + {{ formatMessage(messages.installInternalErrorDescription) }}
- An error occurred while installing your server because Modrinth Hosting 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. + {{ formatMessage(messages.installUnsupportedVersionDescription) }}
-
+
- + @@ -271,7 +257,7 @@ @click="openServerSettingsModal('installation')" > - Change Loader + {{ formatMessage(messages.changeLoader) }}
@@ -295,7 +281,7 @@ class="mb-4 flex w-full flex-row items-center gap-4 rounded-2xl bg-bg-red p-4 text-contrast" > - Something went wrong... + {{ formatMessage(messages.websocketError) }}
- Hang on, we're reconnecting to your server. + {{ formatMessage(messages.websocketReconnecting) }}
-

Server data

+

+ {{ formatMessage(messages.serverDataTitle) }} +

{{
 			safeStringify(serverData)
 		}}
@@ -360,6 +348,7 @@ import { SettingsIcon, TransferIcon, TriangleAlertIcon, + WorldIcon, XIcon, } from '@modrinth/assets' import type { Stats } from '@modrinth/utils' @@ -487,6 +476,33 @@ const settingsHintMessages = defineMessages({ }, }) +const messages = defineMessages({ + serverSettings: { + id: 'servers.manage.server-settings', + defaultMessage: 'Server settings', + }, + overviewNav: { + id: 'servers.manage.nav.overview', + defaultMessage: 'Overview', + }, + contentNav: { + id: 'servers.manage.nav.content', + defaultMessage: 'Content', + }, + worldsNav: { + id: 'servers.manage.nav.worlds', + defaultMessage: 'Worlds', + }, + filesNav: { + id: 'servers.manage.nav.files', + defaultMessage: 'Files', + }, + backupsNav: { + id: 'servers.manage.nav.backups', + defaultMessage: 'Backups', + }, +}) + // disabled, keeping the animation logic cos it's really nice and we might want to re-enable in future const DISABLE_LOADING_ANIM = true @@ -799,25 +815,31 @@ watch(serverData, (data) => { const navLinks = computed(() => [ { - label: 'Overview', + label: formatMessage(messages.overviewNav), href: `/hosting/manage/${props.serverId}`, icon: LayoutTemplateIcon, subpages: [], }, { - label: 'Content', + label: formatMessage(messages.contentNav), href: `/hosting/manage/${props.serverId}/content`, icon: BoxesIcon, subpages: ['mods', 'datapacks'], }, { - label: 'Files', + label: formatMessage(messages.worldsNav), + href: `/hosting/manage/${props.serverId}/worlds`, + icon: WorldIcon, + subpages: [], + }, + { + label: formatMessage(messages.filesNav), href: `/hosting/manage/${props.serverId}/files`, icon: FolderOpenIcon, subpages: [], }, { - label: 'Backups', + label: formatMessage(messages.backupsNav), href: `/hosting/manage/${props.serverId}/backups`, icon: DatabaseBackupIcon, subpages: [], diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/worlds.vue b/packages/ui/src/layouts/wrapped/hosting/manage/worlds.vue new file mode 100644 index 0000000000..9ec8be03a2 --- /dev/null +++ b/packages/ui/src/layouts/wrapped/hosting/manage/worlds.vue @@ -0,0 +1,288 @@ + + + diff --git a/packages/ui/src/layouts/wrapped/index.ts b/packages/ui/src/layouts/wrapped/index.ts index d91f53ee78..4916f26783 100644 --- a/packages/ui/src/layouts/wrapped/index.ts +++ b/packages/ui/src/layouts/wrapped/index.ts @@ -5,3 +5,4 @@ export { default as ServersManageFilesPage } from './hosting/manage/files.vue' export { default as ServersManagePageIndex } from './hosting/manage/index.vue' export { default as ServersManageOverviewPage } from './hosting/manage/overview.vue' export { default as ServersManageRootLayout } from './hosting/manage/root.vue' +export { default as ServersManageWorldsPage } from './hosting/manage/worlds.vue' From fcaaf534a8041e3344c21f6a8ee23287b0766bff Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Mon, 27 Apr 2026 13:06:08 +0100 Subject: [PATCH 02/24] fix: lint + i18n --- apps/frontend/src/locales/en-US/index.json | 3 + .../ui/src/components/servers/WorldCard.vue | 6 +- .../layouts/wrapped/hosting/manage/root.vue | 265 ++++++++++++++++-- .../layouts/wrapped/hosting/manage/worlds.vue | 10 +- packages/ui/src/locales/en-US/index.json | 186 ++++++++++++ 5 files changed, 431 insertions(+), 39 deletions(-) diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json index ae6e2336a8..9265133a0d 100644 --- a/apps/frontend/src/locales/en-US/index.json +++ b/apps/frontend/src/locales/en-US/index.json @@ -3290,6 +3290,9 @@ "servers.manage.content.title": { "message": "Content - {serverName} - Modrinth" }, + "servers.manage.worlds.meta.title": { + "message": "Worlds - {server} - Modrinth" + }, "servers.notice.actions": { "message": "Actions" }, diff --git a/packages/ui/src/components/servers/WorldCard.vue b/packages/ui/src/components/servers/WorldCard.vue index db1aacf0bf..c338121c9f 100644 --- a/packages/ui/src/components/servers/WorldCard.vue +++ b/packages/ui/src/components/servers/WorldCard.vue @@ -95,9 +95,9 @@
{{ world.name }} - @@ -178,8 +178,8 @@ diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/root.vue b/packages/ui/src/layouts/wrapped/hosting/manage/root.vue index ea861824b1..96c8213a81 100644 --- a/packages/ui/src/layouts/wrapped/hosting/manage/root.vue +++ b/packages/ui/src/layouts/wrapped/hosting/manage/root.vue @@ -477,6 +477,139 @@ const settingsHintMessages = defineMessages({ }) const messages = defineMessages({ + serverPreparingTitle: { + id: 'servers.manage.status.preparing.title', + defaultMessage: "We're getting your server ready", + }, + serverPreparingDescription: { + id: 'servers.manage.status.preparing.description', + defaultMessage: "Your server's hardware is being prepared and will be available shortly!", + }, + serverUpgradingTitle: { + id: 'servers.manage.status.upgrading.title', + defaultMessage: 'Server upgrading', + }, + serverUpgradingDescription: { + id: 'servers.manage.status.upgrading.description', + defaultMessage: + "Your server's hardware is currently being upgraded and will be back online shortly!", + }, + serverSuspendedTitle: { + id: 'servers.manage.status.suspended.title', + defaultMessage: 'Server suspended', + }, + suspendedCancelledDescription: { + id: 'servers.manage.status.suspended.cancelled-description', + defaultMessage: + 'Your subscription has been cancelled.\nContact Modrinth Support if you believe this is an error.', + }, + suspendedReasonDescription: { + id: 'servers.manage.status.suspended.reason-description', + defaultMessage: + 'Your server has been suspended: {reason}\nContact Modrinth Support if you believe this is an error.', + }, + suspendedDescription: { + id: 'servers.manage.status.suspended.description', + defaultMessage: + 'Your server has been suspended.\nContact Modrinth Support if you believe this is an error.', + }, + generalErrorTitle: { + id: 'servers.manage.error.general.title', + defaultMessage: 'An error occurred.', + }, + genericErrorMessage: { + id: 'servers.manage.error.general.message', + defaultMessage: 'An unexpected error occurred.', + }, + contactSupportDescription: { + id: 'servers.manage.error.contact-support', + defaultMessage: 'Please contact Modrinth Support.', + }, + nodeUnavailableTitle: { + id: 'servers.manage.error.node-unavailable.title', + defaultMessage: 'Server Node Unavailable', + }, + nodeUnavailableDescription: { + id: 'servers.manage.error.node-unavailable.description', + defaultMessage: + "Your server's node, where your Modrinth Server is physically hosted, is not accessible at the moment. We are working to resolve the issue as quickly as possible.", + }, + nodeUnavailableDataDescription: { + id: 'servers.manage.error.node-unavailable.data-description', + defaultMessage: + 'Your data is safe and will not be lost, and your server will be back online as soon as the issue is resolved.', + }, + nodeUnavailableSupportDescription: { + id: 'servers.manage.error.node-unavailable.support-description', + defaultMessage: + "If reloading does not work initially, please contact Modrinth Support via the chat bubble in the bottom right corner and we'll be happy to help.", + }, + installInvalidVersionDescription: { + id: 'servers.manage.error.install.invalid-version.description', + defaultMessage: + 'An invalid loader or Minecraft version was specified and could not be installed.', + }, + installRecentMinecraftVersionNotice: { + id: 'servers.manage.error.install.invalid-version.recent-minecraft', + defaultMessage: + 'If this version of Minecraft was released recently, please check if Modrinth Hosting supports it.', + }, + installModpackCompatibilityNotice: { + id: 'servers.manage.error.install.invalid-version.modpack-compatibility', + defaultMessage: + "If you've installed a modpack, it may have been packaged incorrectly or may not be compatible with the loader.", + }, + installChangeLoaderNotice: { + id: 'servers.manage.error.install.invalid-version.change-loader', + defaultMessage: + 'Your server may need to be reinstalled with a valid mod loader and version. You can change the loader by clicking the "Change Loader" button.', + }, + installSupportNotice: { + id: 'servers.manage.error.install.invalid-version.support', + defaultMessage: "If you're stuck, please contact Modrinth Support with the information below:", + }, + installInternalErrorDescription: { + id: 'servers.manage.error.install.internal.description', + defaultMessage: + "An internal error occurred while installing your server. Don't fret - try reinstalling your server, and if the problem persists, please contact Modrinth support with your server's debug information.", + }, + installUnsupportedVersionDescription: { + id: 'servers.manage.error.install.unsupported-version.description', + defaultMessage: + "An error occurred while installing your server because Modrinth Hosting 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.", + }, + installationErrorTitle: { + id: 'servers.manage.error.install.title', + defaultMessage: 'Installation error', + }, + unknownError: { + id: 'servers.manage.error.unknown', + defaultMessage: 'Unknown error', + }, + copyDebugInfo: { + id: 'servers.manage.error.copy-debug-info', + defaultMessage: 'Copy Debug Info', + }, + openInstallationLog: { + id: 'servers.manage.error.open-installation-log', + defaultMessage: 'Open Installation Log', + }, + changeLoader: { + id: 'servers.manage.error.change-loader', + defaultMessage: 'Change Loader', + }, + websocketError: { + id: 'servers.manage.websocket.error', + defaultMessage: 'Something went wrong...', + }, + websocketReconnecting: { + id: 'servers.manage.websocket.reconnecting', + defaultMessage: "Hang on, we're reconnecting to your server.", + }, + serverDataTitle: { + id: 'servers.manage.debug.server-data', + defaultMessage: 'Server data', + }, serverSettings: { id: 'servers.manage.server-settings', defaultMessage: 'Server settings', @@ -501,6 +634,67 @@ const messages = defineMessages({ id: 'servers.manage.nav.backups', defaultMessage: 'Backups', }, + errorDismissingNotice: { + id: 'servers.manage.notice.dismiss-error', + defaultMessage: 'Error dismissing notice', + }, + failedToRetryInstallation: { + id: 'servers.manage.install.retry-error', + defaultMessage: 'Failed to retry installation', + }, + serverIdLabel: { + id: 'servers.manage.error-details.server-id', + defaultMessage: 'Server ID', + }, + nodeLabel: { + id: 'servers.manage.error-details.node', + defaultMessage: 'Node', + }, + errorMessageLabel: { + id: 'servers.manage.error-details.error-message', + defaultMessage: 'Error message', + }, + timestampLabel: { + id: 'servers.manage.error-details.timestamp', + defaultMessage: 'Timestamp', + }, + errorNameLabel: { + id: 'servers.manage.error-details.error-name', + defaultMessage: 'Error Name', + }, + originalErrorLabel: { + id: 'servers.manage.error-details.original-error', + defaultMessage: 'Original Error', + }, + stackTraceLabel: { + id: 'servers.manage.error-details.stack-trace', + defaultMessage: 'Stack Trace', + }, + unknownLabel: { + id: 'servers.manage.error-details.unknown', + defaultMessage: 'Unknown', + }, + nodePingFailed: { + id: 'servers.manage.error.node-ping-failed', + defaultMessage: 'Unable to reach node. Ping test failed.', + }, + goToBillingSettings: { + id: 'servers.manage.action.go-to-billing-settings', + defaultMessage: 'Go to billing settings', + }, + goBackToAllServers: { + id: 'servers.manage.action.go-back-to-all-servers', + defaultMessage: 'Go back to all servers', + }, + reload: { + id: 'servers.manage.action.reload', + defaultMessage: 'Reload', + }, + debugInfo: { + id: 'servers.manage.error.debug-info', + defaultMessage: + 'Server ID: {serverId}\nError: {error}\nKind: {kind}\nProject ID: {projectId}\nVersion ID: {versionId}\nLog: {log}', + }, }) // disabled, keeping the animation logic cos it's really nice and we might want to re-enable in future @@ -519,8 +713,14 @@ const isLoading = ref(true) const isMounted = ref(true) const copied = ref(false) const installError = ref(null) -const errorTitle = ref('Error') -const errorMessage = ref('An unexpected error occurred.') +type InstallErrorTitle = 'generic' | 'installation' +const errorTitle = ref('generic') +const errorTitleLabel = computed(() => + errorTitle.value === 'installation' + ? formatMessage(messages.installationErrorTitle) + : formatMessage(messages.generalErrorTitle), +) +const errorMessage = ref(formatMessage(messages.genericErrorMessage)) const errorLog = ref('') const errorLogFile = ref('') const isOnboarding = computed(() => serverData.value?.flows?.intro) @@ -855,7 +1055,7 @@ const surveyNotice = computed(() => serverData.value?.notices?.find((n) => n.lev async function dismissNotice(noticeId: number) { await client.archon.servers_v0.dismissNotice(props.serverId, noticeId).catch((err) => { addNotification({ - title: 'Error dismissing notice', + title: formatMessage(messages.errorDismissingNotice), text: err, type: 'error', }) @@ -959,7 +1159,7 @@ async function handleContentRetry() { } catch (err) { addNotification({ type: 'error', - text: err instanceof Error ? err.message : 'Failed to retry installation', + text: err instanceof Error ? err.message : formatMessage(messages.failedToRetryInstallation), }) } } @@ -1009,9 +1209,9 @@ const handleInstallationResult = async (data: Archon.Websocket.v0.WSInstallation case 'err': { console.log('failed to install') console.log(data) - errorTitle.value = 'Installation error' - errorMessage.value = data.reason ?? 'Unknown error' - installError.value = new Error(data.reason ?? 'Unknown error') + errorTitle.value = 'installation' + errorMessage.value = data.reason ?? formatMessage(messages.unknownError) + installError.value = new Error(errorMessage.value) try { let files = await client.kyros.files_v0.listDirectory('/', 1, 100) @@ -1069,8 +1269,8 @@ const onReinstall = async ( } installError.value = null - errorTitle.value = 'Error' - errorMessage.value = 'An unexpected error occurred.' + errorTitle.value = 'generic' + errorMessage.value = formatMessage(messages.genericErrorMessage) modrinthServersConsole.clear() @@ -1139,62 +1339,64 @@ const nodeAccessible = ref(true) const nodeUnavailableDetails = computed(() => [ { - label: 'Server ID', + label: formatMessage(messages.serverIdLabel), value: props.serverId, type: 'inline' as const, }, { - label: 'Node', + label: formatMessage(messages.nodeLabel), value: (serverError.value?.responseData as { hostname?: string } | undefined)?.hostname ?? serverData.value?.datacenter ?? - 'Unknown', + formatMessage(messages.unknownLabel), type: 'inline' as const, }, { - label: 'Error message', + label: formatMessage(messages.errorMessageLabel), value: nodeAccessible.value - ? (serverError.value?.message ?? 'Unknown') - : 'Unable to reach node. Ping test failed.', + ? (serverError.value?.message ?? formatMessage(messages.unknownLabel)) + : formatMessage(messages.nodePingFailed), type: 'block' as const, }, ]) const suspendedDescription = computed(() => { if (serverData.value?.suspension_reason === 'cancelled') { - return 'Your subscription has been cancelled.\nContact Modrinth Support if you believe this is an error.' + return formatMessage(messages.suspendedCancelledDescription) } if (serverData.value?.suspension_reason) { - return `Your server has been suspended: ${serverData.value.suspension_reason}\nContact Modrinth Support if you believe this is an error.` + return formatMessage(messages.suspendedReasonDescription, { + reason: serverData.value.suspension_reason, + }) } - return 'Your server has been suspended.\nContact Modrinth Support if you believe this is an error.' + return formatMessage(messages.suspendedDescription) }) const generalErrorDetails = computed(() => [ { - label: 'Server ID', + label: formatMessage(messages.serverIdLabel), value: props.serverId, type: 'inline' as const, }, { - label: 'Timestamp', + label: formatMessage(messages.timestampLabel), value: String(new Date().toISOString()), type: 'inline' as const, }, { - label: 'Error Name', + label: formatMessage(messages.errorNameLabel), value: serverError.value?.name, type: 'inline' as const, }, { - label: 'Error Message', + label: formatMessage(messages.errorMessageLabel), value: serverError.value?.message, type: 'block' as const, }, ...(serverError.value?.originalError ? [ { - label: 'Original Error', + label: formatMessage(messages.originalErrorLabel), value: String(serverError.value.originalError), type: 'hidden' as const, }, @@ -1203,7 +1405,7 @@ const generalErrorDetails = computed(() => [ ...(serverError.value?.stack ? [ { - label: 'Stack Trace', + label: formatMessage(messages.stackTraceLabel), value: serverError.value.stack, type: 'hidden' as const, }, @@ -1212,26 +1414,33 @@ const generalErrorDetails = computed(() => [ ]) const suspendedAction = computed(() => ({ - label: 'Go to billing settings', + label: formatMessage(messages.goToBillingSettings), onClick: () => props.navigateToBilling?.(), color: 'brand' as const, })) const generalErrorAction = computed(() => ({ - label: 'Go back to all servers', + label: formatMessage(messages.goBackToAllServers), onClick: () => props.navigateToServers?.(), color: 'brand' as const, })) const nodeUnavailableAction = computed(() => ({ - label: 'Reload', + label: formatMessage(messages.reload), onClick: () => props.reloadPage(), color: 'brand' as const, disabled: false, })) const copyServerDebugInfo = () => { - const debugInfo = `Server ID: ${serverData.value?.server_id}\nError: ${errorMessage.value}\nKind: ${serverData.value?.upstream?.kind}\nProject ID: ${serverData.value?.upstream?.project_id}\nVersion ID: ${serverData.value?.upstream?.version_id}\nLog: ${errorLog.value}` + const debugInfo = formatMessage(messages.debugInfo, { + serverId: serverData.value?.server_id ?? '', + error: errorMessage.value, + kind: serverData.value?.upstream?.kind ?? '', + projectId: serverData.value?.upstream?.project_id ?? '', + versionId: serverData.value?.upstream?.version_id ?? '', + log: errorLog.value, + }) navigator.clipboard.writeText(debugInfo) copied.value = true setTimeout(() => { diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/worlds.vue b/packages/ui/src/layouts/wrapped/hosting/manage/worlds.vue index 9ec8be03a2..c21e42bb6d 100644 --- a/packages/ui/src/layouts/wrapped/hosting/manage/worlds.vue +++ b/packages/ui/src/layouts/wrapped/hosting/manage/worlds.vue @@ -10,10 +10,7 @@ class="min-h-[19.75rem] animate-pulse rounded-2xl border border-solid border-surface-5 bg-bg-raised shadow-xl" />
-
+
Do not attempt to purchase a new server." }, @@ -3545,15 +3641,39 @@ "servers.manage.error.title": { "defaultMessage": "Servers could not be loaded" }, + "servers.manage.error.unknown": { + "defaultMessage": "Unknown error" + }, "servers.manage.handle-error.title": { "defaultMessage": "An error occurred" }, + "servers.manage.install.retry-error": { + "defaultMessage": "Failed to retry installation" + }, + "servers.manage.nav.backups": { + "defaultMessage": "Backups" + }, + "servers.manage.nav.content": { + "defaultMessage": "Content" + }, + "servers.manage.nav.files": { + "defaultMessage": "Files" + }, + "servers.manage.nav.overview": { + "defaultMessage": "Overview" + }, + "servers.manage.nav.worlds": { + "defaultMessage": "Worlds" + }, "servers.manage.new-server-button": { "defaultMessage": "New server" }, "servers.manage.no-servers-found": { "defaultMessage": "No servers found." }, + "servers.manage.notice.dismiss-error": { + "defaultMessage": "Error dismissing notice" + }, "servers.manage.purchase-unavailable.text": { "defaultMessage": "Payment information is still loading. Opening checkout as soon as it is ready." }, @@ -3584,6 +3704,9 @@ "servers.manage.search-placeholder": { "defaultMessage": "Search {count} {count, plural, one {server} other {servers}}..." }, + "servers.manage.server-settings": { + "defaultMessage": "Server settings" + }, "servers.manage.servers-title": { "defaultMessage": "Modrinth Hosting" }, @@ -3596,6 +3719,69 @@ "servers.manage.settings-hint.title": { "defaultMessage": "Your server settings have moved" }, + "servers.manage.status.preparing.description": { + "defaultMessage": "Your server's hardware is being prepared and will be available shortly!" + }, + "servers.manage.status.preparing.title": { + "defaultMessage": "We're getting your server ready" + }, + "servers.manage.status.suspended.cancelled-description": { + "defaultMessage": "Your subscription has been cancelled.\nContact Modrinth Support if you believe this is an error." + }, + "servers.manage.status.suspended.description": { + "defaultMessage": "Your server has been suspended.\nContact Modrinth Support if you believe this is an error." + }, + "servers.manage.status.suspended.reason-description": { + "defaultMessage": "Your server has been suspended: {reason}\nContact Modrinth Support if you believe this is an error." + }, + "servers.manage.status.suspended.title": { + "defaultMessage": "Server suspended" + }, + "servers.manage.status.upgrading.description": { + "defaultMessage": "Your server's hardware is currently being upgraded and will be back online shortly!" + }, + "servers.manage.status.upgrading.title": { + "defaultMessage": "Server upgrading" + }, + "servers.manage.websocket.error": { + "defaultMessage": "Something went wrong..." + }, + "servers.manage.websocket.reconnecting": { + "defaultMessage": "Hang on, we're reconnecting to your server." + }, + "servers.manage.worlds.card.active": { + "defaultMessage": "Active" + }, + "servers.manage.worlds.card.create": { + "defaultMessage": "Create world" + }, + "servers.manage.worlds.card.created": { + "defaultMessage": "Created" + }, + "servers.manage.worlds.card.edit": { + "defaultMessage": "Edit world" + }, + "servers.manage.worlds.card.empty-description": { + "defaultMessage": "New world instance" + }, + "servers.manage.worlds.card.installed-content": { + "defaultMessage": "Installed content" + }, + "servers.manage.worlds.card.last-active": { + "defaultMessage": "Last active" + }, + "servers.manage.worlds.card.none": { + "defaultMessage": "None" + }, + "servers.manage.worlds.card.not-tracked-yet": { + "defaultMessage": "Not tracked yet" + }, + "servers.manage.worlds.card.settings": { + "defaultMessage": "World settings" + }, + "servers.manage.worlds.slot-name": { + "defaultMessage": "World #{index}" + }, "servers.medal-listing.countdown.remaining": { "defaultMessage": "{days} {days, plural, one {day} other {days}} {hours} {hours, plural, one {hour} other {hours}} {minutes} {minutes, plural, one {minute} other {minutes}} {seconds} {seconds, plural, one {second} other {seconds}} remaining..." }, From 7911ce8e4a28d4808c144ea66a4dd3d6790a0ceb Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Mon, 27 Apr 2026 14:13:17 +0100 Subject: [PATCH 03/24] feat: world card alignment --- packages/ui/src/components/servers/WorldCard.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/servers/WorldCard.vue b/packages/ui/src/components/servers/WorldCard.vue index c338121c9f..fbf4244d45 100644 --- a/packages/ui/src/components/servers/WorldCard.vue +++ b/packages/ui/src/components/servers/WorldCard.vue @@ -74,8 +74,8 @@
-

{{ world.name }}

-

+

{{ world.name }}

+

{{ formatMessage(messages.newWorldInstance) }}

@@ -83,7 +83,7 @@
- From 0192fb2f84d09e01a7946262bd7be1db71e950df Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Mon, 27 Apr 2026 14:10:10 +0100 Subject: [PATCH 04/24] feat: reshuffle layout for worlds --- apps/app-frontend/src/App.vue | 49 ++++ .../src/pages/hosting/manage/Backups.vue | 2 +- .../src/pages/hosting/manage/Content.vue | 2 +- .../src/pages/hosting/manage/Index.vue | 2 +- .../src/pages/hosting/manage/World.vue | 16 ++ .../src/pages/hosting/manage/index.js | 3 +- .../providers/setup/server-install-content.ts | 9 +- apps/app-frontend/src/routes.js | 34 +++ .../composables/use-server-install-content.ts | 14 +- .../middleware/server-world-tabs.global.ts | 40 ++++ .../hosting/manage/[id]/worlds/[world_id].vue | 11 + .../[id]/{ => worlds/[world_id]}/backups.vue | 2 +- .../[id]/{ => worlds/[world_id]}/files.vue | 0 .../[world_id]/index.vue} | 2 +- .../components/servers/ServerManageStats.vue | 6 +- .../servers/ServerSettingsModal.vue | 2 +- .../admonitions/ServerPanelAdmonitions.vue | 6 +- .../servers/backups/BackupCreateModal.vue | 4 +- .../servers/backups/BackupRenameModal.vue | 4 +- .../servers/backups/BackupRestoreModal.vue | 6 +- .../server-header/PanelServerActionButton.vue | 21 +- .../server-header/ServerManageHeader.vue | 144 +++++------- .../src/composables/server-backups-queue.ts | 2 +- .../server-settings/pages/installation.vue | 11 +- .../server-settings/pages/properties.vue | 6 +- .../hosting/manage/[id]/onboarding.vue | 6 +- .../wrapped/hosting/manage/backups.vue | 6 +- .../wrapped/hosting/manage/content.vue | 8 +- .../layouts/wrapped/hosting/manage/root.vue | 83 ++++--- .../wrapped/hosting/manage/world-root.vue | 213 ++++++++++++++++++ .../layouts/wrapped/hosting/manage/worlds.vue | 12 +- packages/ui/src/layouts/wrapped/index.ts | 1 + .../servers/ServerPanelAdmonitions.stories.ts | 2 +- 33 files changed, 550 insertions(+), 179 deletions(-) create mode 100644 apps/app-frontend/src/pages/hosting/manage/World.vue create mode 100644 apps/frontend/src/middleware/server-world-tabs.global.ts create mode 100644 apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id].vue rename apps/frontend/src/pages/hosting/manage/[id]/{ => worlds/[world_id]}/backups.vue (93%) rename apps/frontend/src/pages/hosting/manage/[id]/{ => worlds/[world_id]}/files.vue (100%) rename apps/frontend/src/pages/hosting/manage/[id]/{content.vue => worlds/[world_id]/index.vue} (96%) create mode 100644 packages/ui/src/layouts/wrapped/hosting/manage/world-root.vue diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index c841f5fc9c..f7f0fbccd2 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -482,6 +482,11 @@ const sidebarOverlayScrollbarsOptions = Object.freeze({ }, }) +router.beforeEach(async (to) => { + const redirect = await resolveLegacyServerWorldTabRedirect(to) + if (redirect) return redirect +}) + router.beforeEach(() => { suspensePending = false if (routerToken) loading.end(routerToken) @@ -513,6 +518,50 @@ function onSuspensePending() { suspenseToken = loading.begin() } +async function resolveLegacyServerWorldTabRedirect(to) { + if (!['ServerManageContent', 'ServerManageFiles', 'ServerManageBackups'].includes(to.name)) { + return null + } + + const serverId = getRouteParam(to.params.id) + if (!serverId) return null + + const tabPath = + to.name === 'ServerManageFiles' ? '/files' : to.name === 'ServerManageBackups' ? '/backups' : '' + const worldsPath = `/hosting/manage/${encodeURIComponent(serverId)}/worlds` + + try { + const serverFull = await tauriApiClient.archon.servers_v1.get(serverId) + const world = serverFull.worlds.find((item) => item.is_active) ?? serverFull.worlds[0] + if (world) { + return { + path: `${worldsPath}/${encodeURIComponent(world.id)}${tabPath}`, + query: to.query, + hash: to.hash, + replace: true, + } + } + } catch { + return { + path: worldsPath, + query: to.query, + hash: to.hash, + replace: true, + } + } + + return { + path: worldsPath, + query: to.query, + hash: to.hash, + replace: true, + } +} + +function getRouteParam(param) { + return Array.isArray(param) ? param[0] : param +} + function onSuspenseResolve() { if (suspenseToken) { loading.end(suspenseToken) diff --git a/apps/app-frontend/src/pages/hosting/manage/Backups.vue b/apps/app-frontend/src/pages/hosting/manage/Backups.vue index 0ebb7bd800..b62de2ba6d 100644 --- a/apps/app-frontend/src/pages/hosting/manage/Backups.vue +++ b/apps/app-frontend/src/pages/hosting/manage/Backups.vue @@ -13,7 +13,7 @@ const queryClient = useQueryClient() if (worldId.value) { try { await queryClient.ensureQueryData({ - queryKey: ['backups', 'list', serverId], + queryKey: ['backups', 'list', serverId, worldId.value], queryFn: () => client.archon.backups_v1.list(serverId, worldId.value!), staleTime: 30_000, }) diff --git a/apps/app-frontend/src/pages/hosting/manage/Content.vue b/apps/app-frontend/src/pages/hosting/manage/Content.vue index ab7d242452..1306234cc3 100644 --- a/apps/app-frontend/src/pages/hosting/manage/Content.vue +++ b/apps/app-frontend/src/pages/hosting/manage/Content.vue @@ -13,7 +13,7 @@ const queryClient = useQueryClient() if (worldId.value) { try { await queryClient.ensureQueryData({ - queryKey: ['content', 'list', 'v1', serverId], + queryKey: ['content', 'list', 'v1', serverId, worldId.value], queryFn: () => client.archon.content_v1.getAddons(serverId, worldId.value!, { from_modpack: false }), staleTime: 30_000, diff --git a/apps/app-frontend/src/pages/hosting/manage/Index.vue b/apps/app-frontend/src/pages/hosting/manage/Index.vue index f1e4388755..030f74a7e3 100644 --- a/apps/app-frontend/src/pages/hosting/manage/Index.vue +++ b/apps/app-frontend/src/pages/hosting/manage/Index.vue @@ -93,7 +93,7 @@ watch( breadcrumbs.setName('Server', server.name) breadcrumbs.setContext({ name: server.name, - link: `/hosting/manage/${serverId.value}/content`, + link: `/hosting/manage/${serverId.value}/worlds`, }) } }, diff --git a/apps/app-frontend/src/pages/hosting/manage/World.vue b/apps/app-frontend/src/pages/hosting/manage/World.vue new file mode 100644 index 0000000000..fe52492c50 --- /dev/null +++ b/apps/app-frontend/src/pages/hosting/manage/World.vue @@ -0,0 +1,16 @@ + + + diff --git a/apps/app-frontend/src/pages/hosting/manage/index.js b/apps/app-frontend/src/pages/hosting/manage/index.js index e06f46e2a2..6e77f9e638 100644 --- a/apps/app-frontend/src/pages/hosting/manage/index.js +++ b/apps/app-frontend/src/pages/hosting/manage/index.js @@ -3,6 +3,7 @@ import Content from './Content.vue' import Files from './Files.vue' import Index from './Index.vue' import Overview from './Overview.vue' +import World from './World.vue' import Worlds from './Worlds.vue' -export { Backups, Content, Files, Index, Overview, Worlds } +export { Backups, Content, Files, Index, Overview, World, Worlds } diff --git a/apps/app-frontend/src/providers/setup/server-install-content.ts b/apps/app-frontend/src/providers/setup/server-install-content.ts index eea327e639..7b82793a85 100644 --- a/apps/app-frontend/src/providers/setup/server-install-content.ts +++ b/apps/app-frontend/src/providers/setup/server-install-content.ts @@ -252,7 +252,7 @@ export function createServerInstallContent(opts: { if (serverFlowFrom.value === 'reset-server') { return `/hosting/manage/${sid}?openSettings=installation` } - return `/hosting/manage/${sid}/content` + return getServerWorldContentPath(sid, effectiveServerWorldId.value) }) const serverBackLabel = computed(() => { if (serverFlowFrom.value === 'onboarding') return 'Back to setup' @@ -556,7 +556,7 @@ export function createServerInstallContent(opts: { if (serverFlowFrom.value === 'onboarding') { await client.archon.servers_v1.endIntro(sid) - await router.push(`/hosting/manage/${sid}/content`) + await router.push(getServerWorldContentPath(sid, wid)) return } @@ -571,6 +571,11 @@ export function createServerInstallContent(opts: { serverContentProjectIds.value = new Set([...serverContentProjectIds.value, id]) } + function getServerWorldContentPath(serverId: string, worldId: string | null) { + const base = `/hosting/manage/${encodeURIComponent(serverId)}/worlds` + return worldId ? `${base}/${encodeURIComponent(worldId)}` : base + } + return { serverIdQuery, worldIdQuery, diff --git a/apps/app-frontend/src/routes.js b/apps/app-frontend/src/routes.js index 410869c46a..e0b131f3d7 100644 --- a/apps/app-frontend/src/routes.js +++ b/apps/app-frontend/src/routes.js @@ -65,6 +65,40 @@ export default new createRouter({ breadcrumb: [{ name: '?Server' }], }, }, + { + path: 'worlds/:world_id', + name: 'ServerManageWorld', + component: Hosting.World, + meta: { + breadcrumb: [{ name: '?Server' }], + }, + children: [ + { + path: '', + name: 'ServerManageWorldContent', + component: Hosting.Content, + meta: { + breadcrumb: [{ name: '?Server' }], + }, + }, + { + path: 'files', + name: 'ServerManageWorldFiles', + component: Hosting.Files, + meta: { + breadcrumb: [{ name: '?Server' }], + }, + }, + { + path: 'backups', + name: 'ServerManageWorldBackups', + component: Hosting.Backups, + meta: { + breadcrumb: [{ name: '?Server' }], + }, + }, + ], + }, { path: 'files', name: 'ServerManageFiles', diff --git a/apps/frontend/src/composables/use-server-install-content.ts b/apps/frontend/src/composables/use-server-install-content.ts index 75b3c0d232..871e709fbe 100644 --- a/apps/frontend/src/composables/use-server-install-content.ts +++ b/apps/frontend/src/composables/use-server-install-content.ts @@ -187,7 +187,10 @@ export function useServerInstallContent({ }, } - const contentQueryKey = computed(() => ['content', 'list', currentServerId.value ?? ''] as const) + const contentQueryKey = computed( + () => + ['content', 'list', 'v1', currentServerId.value ?? '', currentWorldId.value ?? null] as const, + ) const { data: serverContentData, error: serverContentError } = useQuery({ queryKey: contentQueryKey, queryFn: () => @@ -625,7 +628,7 @@ export function useServerInstallContent({ if (fromContext.value === 'onboarding') { await client.archon.servers_v1.endIntro(currentServerId.value) queryClient.invalidateQueries({ queryKey: ['servers', 'detail', currentServerId.value] }) - navigateTo(`/hosting/manage/${currentServerId.value}/content`) + navigateTo(getServerWorldContentPath(currentServerId.value, currentWorldId.value ?? null)) } else { navigateTo(`/hosting/manage/${currentServerId.value}?openSettings=installation`) } @@ -641,9 +644,14 @@ export function useServerInstallContent({ if (fromContext.value === 'onboarding') return `/hosting/manage/${id}?resumeModal=setup-type` if (fromContext.value === 'reset-server') return `/hosting/manage/${id}?openSettings=installation` - return `/hosting/manage/${id}/content` + return getServerWorldContentPath(id, currentWorldId.value) }) + function getServerWorldContentPath(serverId: string, worldId: string | null) { + const base = `/hosting/manage/${encodeURIComponent(serverId)}/worlds` + return worldId ? `${base}/${encodeURIComponent(worldId)}` : base + } + const serverBackLabel = computed(() => { if (fromContext.value === 'onboarding') return formatMessage(messages.backToSetup) if (fromContext.value === 'reset-server') return formatMessage(messages.cancelReset) diff --git a/apps/frontend/src/middleware/server-world-tabs.global.ts b/apps/frontend/src/middleware/server-world-tabs.global.ts new file mode 100644 index 0000000000..2a740b7dea --- /dev/null +++ b/apps/frontend/src/middleware/server-world-tabs.global.ts @@ -0,0 +1,40 @@ +import { createModrinthClient } from '~/helpers/api.ts' + +export default defineNuxtRouteMiddleware(async (to) => { + const match = to.path.match(/^\/hosting\/manage\/([^/]+)\/(content|files|backups)\/?$/) + if (!match) return + + const serverId = decodeURIComponent(match[1]) + const tab = match[2] + const worldsPath = `/hosting/manage/${encodeURIComponent(serverId)}/worlds` + const tabPath = tab === 'content' ? '' : `/${tab}` + const auth = await useAuth() + + if (auth.value.token) { + try { + const config = useRuntimeConfig() + const client = createModrinthClient(auth, { + apiBaseUrl: config.public.apiBaseUrl.replace('/v2/', '/'), + archonBaseUrl: config.public.pyroBaseUrl.replace('/v2/', '/'), + rateLimitKey: config.rateLimitKey, + }) + const serverFull = await client.archon.servers_v1.get(serverId) + const world = serverFull.worlds.find((item) => item.is_active) ?? serverFull.worlds[0] + + if (world) { + return navigateTo( + { + path: `${worldsPath}/${encodeURIComponent(world.id)}${tabPath}`, + query: to.query, + hash: to.hash, + }, + { replace: true }, + ) + } + } catch { + return navigateTo({ path: worldsPath, query: to.query, hash: to.hash }, { replace: true }) + } + } + + return navigateTo({ path: worldsPath, query: to.query, hash: to.hash }, { replace: true }) +}) diff --git a/apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id].vue b/apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id].vue new file mode 100644 index 0000000000..357dc14f4a --- /dev/null +++ b/apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id].vue @@ -0,0 +1,11 @@ + + + diff --git a/apps/frontend/src/pages/hosting/manage/[id]/backups.vue b/apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id]/backups.vue similarity index 93% rename from apps/frontend/src/pages/hosting/manage/[id]/backups.vue rename to apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id]/backups.vue index 467662653f..ab36852f6a 100644 --- a/apps/frontend/src/pages/hosting/manage/[id]/backups.vue +++ b/apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id]/backups.vue @@ -14,7 +14,7 @@ const flags = useFeatureFlags() if (worldId.value) { try { await queryClient.ensureQueryData({ - queryKey: ['backups', 'list', serverId], + queryKey: ['backups', 'list', serverId, worldId.value], queryFn: () => client.archon.backups_v1.list(serverId, worldId.value!), staleTime: 30_000, }) diff --git a/apps/frontend/src/pages/hosting/manage/[id]/files.vue b/apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id]/files.vue similarity index 100% rename from apps/frontend/src/pages/hosting/manage/[id]/files.vue rename to apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id]/files.vue diff --git a/apps/frontend/src/pages/hosting/manage/[id]/content.vue b/apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id]/index.vue similarity index 96% rename from apps/frontend/src/pages/hosting/manage/[id]/content.vue rename to apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id]/index.vue index 8530076bff..e5e8e9e517 100644 --- a/apps/frontend/src/pages/hosting/manage/[id]/content.vue +++ b/apps/frontend/src/pages/hosting/manage/[id]/worlds/[world_id]/index.vue @@ -38,7 +38,7 @@ const contentWorldId = await getContentWorldId() if (contentWorldId) { try { const content = await queryClient.ensureQueryData({ - queryKey: ['content', 'list', 'v1', serverId], + queryKey: ['content', 'list', 'v1', serverId, contentWorldId], queryFn: () => client.archon.content_v1.getAddons(serverId, contentWorldId, { from_modpack: false }), staleTime: 30_000, diff --git a/packages/ui/src/components/servers/ServerManageStats.vue b/packages/ui/src/components/servers/ServerManageStats.vue index b817cab303..77effe85ad 100644 --- a/packages/ui/src/components/servers/ServerManageStats.vue +++ b/packages/ui/src/components/servers/ServerManageStats.vue @@ -77,7 +77,7 @@ onMounted(() => { isClient.value = true }) -const { serverId } = injectModrinthServerContext() +const { serverId, worldId } = injectModrinthServerContext() const { featureFlags } = injectPageContext() const props = withDefaults( @@ -190,7 +190,9 @@ const metrics = computed(() => { showGraph: false, chartOptions: null as ReturnType | null, series: null as { name: string; data: number[] }[] | null, - link: `/hosting/manage/${encodeURIComponent(serverId)}/files`, + link: worldId.value + ? `/hosting/manage/${encodeURIComponent(serverId)}/worlds/${encodeURIComponent(worldId.value)}/files` + : `/hosting/manage/${encodeURIComponent(serverId)}/worlds`, } if (props.loading) { diff --git a/packages/ui/src/components/servers/ServerSettingsModal.vue b/packages/ui/src/components/servers/ServerSettingsModal.vue index a72001c73b..6575ad2afa 100644 --- a/packages/ui/src/components/servers/ServerSettingsModal.vue +++ b/packages/ui/src/components/servers/ServerSettingsModal.vue @@ -186,7 +186,7 @@ async function show({ serverId, tabIndex, tabId }: ShowOptions) { queryFn: () => client.archon.properties_v1.getProperties(targetServerId, worldId.value!), }) queryClient.prefetchQuery({ - queryKey: ['content', 'list', 'v1', targetServerId], + queryKey: ['content', 'list', 'v1', targetServerId, worldId.value], queryFn: () => client.archon.content_v1.getAddons(targetServerId, worldId.value!, { from_modpack: false, diff --git a/packages/ui/src/components/servers/admonitions/ServerPanelAdmonitions.vue b/packages/ui/src/components/servers/admonitions/ServerPanelAdmonitions.vue index c157327f99..c54fcb7b8d 100644 --- a/packages/ui/src/components/servers/admonitions/ServerPanelAdmonitions.vue +++ b/packages/ui/src/components/servers/admonitions/ServerPanelAdmonitions.vue @@ -53,8 +53,12 @@ const messages = defineMessages({ }, }) -const isOnContentTab = computed(() => route.path.includes('/content')) const isOnFilesTab = computed(() => route.path.includes('/files')) +const isOnContentTab = computed( + () => + route.path.includes('/content') || + (!!route.params.world_id && !isOnFilesTab.value && !route.path.includes('/backups')), +) const bannerCoversInstalling = computed( () => diff --git a/packages/ui/src/components/servers/backups/BackupCreateModal.vue b/packages/ui/src/components/servers/backups/BackupCreateModal.vue index bd9e53c4a1..c6aaf0f994 100644 --- a/packages/ui/src/components/servers/backups/BackupCreateModal.vue +++ b/packages/ui/src/components/servers/backups/BackupCreateModal.vue @@ -87,12 +87,12 @@ const props = defineProps<{ backups?: Archon.BackupsQueue.v1.BackupQueueBackup[] }>() -const backupsQueryKey = ['backups', 'queue', ctx.serverId] +const backupsQueryKey = computed(() => ['backups', 'queue', ctx.serverId, ctx.worldId.value]) const createMutation = useMutation({ mutationFn: (name: string) => client.archon.backups_queue_v1.create(ctx.serverId, ctx.worldId.value!, { name }), - onSuccess: () => queryClient.invalidateQueries({ queryKey: backupsQueryKey }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: backupsQueryKey.value }), }) const modal = ref>() diff --git a/packages/ui/src/components/servers/backups/BackupRenameModal.vue b/packages/ui/src/components/servers/backups/BackupRenameModal.vue index eaed166ea1..b9c65c8605 100644 --- a/packages/ui/src/components/servers/backups/BackupRenameModal.vue +++ b/packages/ui/src/components/servers/backups/BackupRenameModal.vue @@ -69,12 +69,12 @@ const props = defineProps<{ backups?: Archon.BackupsQueue.v1.BackupQueueBackup[] }>() -const backupsQueryKey = ['backups', 'queue', ctx.serverId] +const backupsQueryKey = computed(() => ['backups', 'queue', ctx.serverId, ctx.worldId.value]) const renameMutation = useMutation({ mutationFn: ({ backupId, name }: { backupId: string; name: string }) => client.archon.backups_v1.rename(ctx.serverId, ctx.worldId.value!, backupId, { name }), - onSuccess: () => queryClient.invalidateQueries({ queryKey: backupsQueryKey }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: backupsQueryKey.value }), }) const modal = ref>() diff --git a/packages/ui/src/components/servers/backups/BackupRestoreModal.vue b/packages/ui/src/components/servers/backups/BackupRestoreModal.vue index 975c27f654..3743774a06 100644 --- a/packages/ui/src/components/servers/backups/BackupRestoreModal.vue +++ b/packages/ui/src/components/servers/backups/BackupRestoreModal.vue @@ -39,7 +39,7 @@ import type { Archon } from '@modrinth/api-client' import { RotateCounterClockwiseIcon, SpinnerIcon, XIcon } from '@modrinth/assets' import { useMutation, useQueryClient } from '@tanstack/vue-query' -import { ref } from 'vue' +import { computed, ref } from 'vue' import { injectModrinthClient, @@ -56,7 +56,7 @@ const client = injectModrinthClient() const queryClient = useQueryClient() const ctx = injectModrinthServerContext() -const backupsQueryKey = ['backups', 'queue', ctx.serverId] +const backupsQueryKey = computed(() => ['backups', 'queue', ctx.serverId, ctx.worldId.value]) function safetyBackupName(backupName: string) { const base = `Before restoring "${backupName}"` @@ -66,7 +66,7 @@ function safetyBackupName(backupName: string) { const restoreMutation = useMutation({ mutationFn: ({ backupId, name }: { backupId: string; name: string }) => client.archon.backups_queue_v1.restore(ctx.serverId, ctx.worldId.value!, backupId, { name }), - onSuccess: () => queryClient.invalidateQueries({ queryKey: backupsQueryKey }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: backupsQueryKey.value }), }) const modal = ref>() diff --git a/packages/ui/src/components/servers/server-header/PanelServerActionButton.vue b/packages/ui/src/components/servers/server-header/PanelServerActionButton.vue index fd6cd6930d..256f98bf37 100644 --- a/packages/ui/src/components/servers/server-header/PanelServerActionButton.vue +++ b/packages/ui/src/components/servers/server-header/PanelServerActionButton.vue @@ -1,14 +1,14 @@ @@ -348,6 +342,7 @@ import { CheckIcon, CopyIcon, FileIcon, + GlobeIcon, IssuesIcon, LayoutTemplateIcon, LoaderCircleIcon, @@ -356,7 +351,6 @@ import { SettingsIcon, TransferIcon, TriangleAlertIcon, - WorldIcon, XIcon, } from '@modrinth/assets' import type { Stats } from '@modrinth/utils' @@ -374,11 +368,7 @@ import ServerNotice from '#ui/components/base/ServerNotice.vue' import ConfirmLeaveModal from '#ui/components/modal/ConfirmLeaveModal.vue' import ServerPanelAdmonitions from '#ui/components/servers/admonitions/ServerPanelAdmonitions.vue' import MedalServerCountdown from '#ui/components/servers/marketing/MedalServerCountdown.vue' -import { - PanelServerActionButton, - PanelServerOverflowMenu, - ServerManageHeader, -} from '#ui/components/servers/server-header' +import { PanelServerActionButton, ServerManageHeader } from '#ui/components/servers/server-header' import ServerSettingsModal from '#ui/components/servers/ServerSettingsModal.vue' import { useDebugLogger, @@ -419,7 +409,6 @@ const props = withDefaults( serverId: string reloadPage: () => void resolveViewer: () => Promise<{ userId: string | null; userRole: string | null }> - showCopyIdAction?: boolean showAdvancedDebugInfo?: boolean showUptime?: boolean additionalTabs?: Tab[] @@ -441,7 +430,6 @@ const props = withDefaults( }) => void | Promise }>(), { - showCopyIdAction: false, showAdvancedDebugInfo: false, showUptime: true, additionalTabs: () => [], @@ -1037,7 +1025,7 @@ const navLinks = computed(() => [ { label: formatMessage(messages.instancesNav), href: `/hosting/manage/${encodeURIComponent(props.serverId)}/instances`, - icon: WorldIcon, + icon: GlobeIcon, subpages: [], }, ...props.additionalTabs, From 5a7dfff358582a9087aab0a5b53845abcaa767b3 Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Mon, 18 May 2026 10:43:46 +0100 Subject: [PATCH 12/24] fix: lint --- .../composables/use-server-install-content.ts | 4 +- packages/ui/src/layouts/wrapped/index.ts | 4 +- packages/ui/src/locales/en-US/index.json | 96 +++++++++---------- 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/apps/frontend/src/composables/use-server-install-content.ts b/apps/frontend/src/composables/use-server-install-content.ts index f9bf933d04..4531601bb2 100644 --- a/apps/frontend/src/composables/use-server-install-content.ts +++ b/apps/frontend/src/composables/use-server-install-content.ts @@ -628,7 +628,9 @@ export function useServerInstallContent({ if (fromContext.value === 'onboarding') { await client.archon.servers_v1.endIntro(currentServerId.value) queryClient.invalidateQueries({ queryKey: ['servers', 'detail', currentServerId.value] }) - navigateTo(getServerInstanceContentPath(currentServerId.value, currentWorldId.value ?? null)) + navigateTo( + getServerInstanceContentPath(currentServerId.value, currentWorldId.value ?? null), + ) } else { navigateTo(`/hosting/manage/${currentServerId.value}?openSettings=installation`) } diff --git a/packages/ui/src/layouts/wrapped/index.ts b/packages/ui/src/layouts/wrapped/index.ts index dad1837365..b6e7993a30 100644 --- a/packages/ui/src/layouts/wrapped/index.ts +++ b/packages/ui/src/layouts/wrapped/index.ts @@ -1,9 +1,9 @@ export { default as ServersManageRootLayout } from './hosting/manage/[id]/index.vue' -export { default as ServerOnboardingPanelPage } from './hosting/manage/[id]/onboarding.vue' -export { default as ServersManageOverviewPage } from './hosting/manage/[id]/overview.vue' export { default as ServersManageBackupsPage } from './hosting/manage/[id]/instances/[instance-id]/backups.vue' export { default as ServersManageContentPage } from './hosting/manage/[id]/instances/[instance-id]/content.vue' export { default as ServersManageFilesPage } from './hosting/manage/[id]/instances/[instance-id]/files.vue' export { default as ServersManageInstanceRootLayout } from './hosting/manage/[id]/instances/[instance-id]/index.vue' export { default as ServersManageInstancesPage } from './hosting/manage/[id]/instances/index.vue' +export { default as ServerOnboardingPanelPage } from './hosting/manage/[id]/onboarding.vue' +export { default as ServersManageOverviewPage } from './hosting/manage/[id]/overview.vue' export { default as ServersManagePageIndex } from './hosting/manage/index.vue' diff --git a/packages/ui/src/locales/en-US/index.json b/packages/ui/src/locales/en-US/index.json index 98e5b30454..0a9f52c290 100644 --- a/packages/ui/src/locales/en-US/index.json +++ b/packages/ui/src/locales/en-US/index.json @@ -3650,6 +3650,51 @@ "servers.manage.install.retry-error": { "defaultMessage": "Failed to retry installation" }, + "servers.manage.instance.all-instances": { + "defaultMessage": "All instances" + }, + "servers.manage.instance.fallback-name": { + "defaultMessage": "Instance" + }, + "servers.manage.instance.last-active": { + "defaultMessage": "Last active {time}" + }, + "servers.manage.instance.settings": { + "defaultMessage": "Instance settings" + }, + "servers.manage.instances.card.active": { + "defaultMessage": "Active" + }, + "servers.manage.instances.card.create": { + "defaultMessage": "Create instance" + }, + "servers.manage.instances.card.created": { + "defaultMessage": "Created" + }, + "servers.manage.instances.card.edit": { + "defaultMessage": "Edit instance" + }, + "servers.manage.instances.card.empty-description": { + "defaultMessage": "New instance" + }, + "servers.manage.instances.card.installed-content": { + "defaultMessage": "Installed content" + }, + "servers.manage.instances.card.last-active": { + "defaultMessage": "Last active" + }, + "servers.manage.instances.card.none": { + "defaultMessage": "None" + }, + "servers.manage.instances.card.not-tracked-yet": { + "defaultMessage": "Not tracked yet" + }, + "servers.manage.instances.card.settings": { + "defaultMessage": "Instance settings" + }, + "servers.manage.instances.slot-name": { + "defaultMessage": "Instance #{index}" + }, "servers.manage.nav.backups": { "defaultMessage": "Backups" }, @@ -3659,12 +3704,12 @@ "servers.manage.nav.files": { "defaultMessage": "Files" }, - "servers.manage.nav.overview": { - "defaultMessage": "Overview" - }, "servers.manage.nav.instances": { "defaultMessage": "Instances" }, + "servers.manage.nav.overview": { + "defaultMessage": "Overview" + }, "servers.manage.new-server-button": { "defaultMessage": "New server" }, @@ -3749,51 +3794,6 @@ "servers.manage.websocket.reconnecting": { "defaultMessage": "Hang on, we're reconnecting to your server." }, - "servers.manage.instance.all-instances": { - "defaultMessage": "All instances" - }, - "servers.manage.instance.fallback-name": { - "defaultMessage": "Instance" - }, - "servers.manage.instance.last-active": { - "defaultMessage": "Last active {time}" - }, - "servers.manage.instance.settings": { - "defaultMessage": "Instance settings" - }, - "servers.manage.instances.card.active": { - "defaultMessage": "Active" - }, - "servers.manage.instances.card.create": { - "defaultMessage": "Create instance" - }, - "servers.manage.instances.card.created": { - "defaultMessage": "Created" - }, - "servers.manage.instances.card.edit": { - "defaultMessage": "Edit instance" - }, - "servers.manage.instances.card.empty-description": { - "defaultMessage": "New instance" - }, - "servers.manage.instances.card.installed-content": { - "defaultMessage": "Installed content" - }, - "servers.manage.instances.card.last-active": { - "defaultMessage": "Last active" - }, - "servers.manage.instances.card.none": { - "defaultMessage": "None" - }, - "servers.manage.instances.card.not-tracked-yet": { - "defaultMessage": "Not tracked yet" - }, - "servers.manage.instances.card.settings": { - "defaultMessage": "Instance settings" - }, - "servers.manage.instances.slot-name": { - "defaultMessage": "Instance #{index}" - }, "servers.medal-listing.countdown.remaining": { "defaultMessage": "{days} {days, plural, one {day} other {days}} {hours} {hours, plural, one {hour} other {hours}} {minutes} {minutes, plural, one {minute} other {minutes}} {seconds} {seconds, plural, one {second} other {seconds}} remaining..." }, From b29994a1e577ccdf569a980567403166bb3d618c Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Mon, 18 May 2026 11:12:49 +0100 Subject: [PATCH 13/24] fix: header issues --- .../app-frontend/src/pages/instance/Index.vue | 49 +++++-- .../server-header/ServerManageHeader.vue | 2 +- .../server-header/WorldManageHeader.vue | 127 ++++++++++++++++++ .../components/servers/server-header/index.ts | 1 + .../[id]/instances/[instance-id]/index.vue | 99 +++++--------- .../servers/WorldManageHeader.stories.ts | 51 +++++++ 6 files changed, 256 insertions(+), 73 deletions(-) create mode 100644 packages/ui/src/components/servers/server-header/WorldManageHeader.vue create mode 100644 packages/ui/src/stories/servers/WorldManageHeader.stories.ts diff --git a/apps/app-frontend/src/pages/instance/Index.vue b/apps/app-frontend/src/pages/instance/Index.vue index 0090900296..549d706196 100644 --- a/apps/app-frontend/src/pages/instance/Index.vue +++ b/apps/app-frontend/src/pages/instance/Index.vue @@ -28,17 +28,30 @@