mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 03:55:59 +00:00
feat: server settings split up + copy changes across panel warning modals
This commit is contained in:
@@ -101,10 +101,7 @@ export class ArchonServersV0Module extends AbstractModule {
|
||||
* Send a power action to a server (Start, Stop, Restart, Kill)
|
||||
* POST /modrinth/v0/servers/:id/power
|
||||
*/
|
||||
public async power(
|
||||
serverId: string,
|
||||
action: Archon.Servers.v0.PowerAction,
|
||||
): Promise<void> {
|
||||
public async power(serverId: string, action: Archon.Servers.v0.PowerAction): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/power`, {
|
||||
api: 'archon',
|
||||
method: 'POST',
|
||||
|
||||
+1
-1
@@ -285,7 +285,7 @@ const messages = defineMessages({
|
||||
},
|
||||
beforeResetServerBackupName: {
|
||||
id: 'creation-flow.modal.final-config.backup.before-reset-server.name',
|
||||
defaultMessage: 'Before reset server',
|
||||
defaultMessage: 'Before reset instance',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export const creationFlowMessages = defineMessages({
|
||||
},
|
||||
resetServerTitle: {
|
||||
id: 'creation-flow.title.reset-server',
|
||||
defaultMessage: 'Reset server',
|
||||
defaultMessage: 'Reset instance',
|
||||
},
|
||||
createInstanceTitle: {
|
||||
id: 'creation-flow.title.create-instance',
|
||||
|
||||
@@ -42,7 +42,7 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
|
||||
const label = isWorld
|
||||
? ctx.formatMessage(creationFlowMessages.createWorldButton)
|
||||
: isReset
|
||||
? ctx.formatMessage(commonMessages.resetServerButton)
|
||||
? ctx.formatMessage(creationFlowMessages.resetServerTitle)
|
||||
: isOnboarding
|
||||
? ctx.formatMessage(creationFlowMessages.setupServerButton)
|
||||
: ctx.formatMessage(commonMessages.continueButton)
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
<script setup lang="ts">
|
||||
import type { Archon } from '@modrinth/api-client'
|
||||
import { ChevronRightIcon } from '@modrinth/assets'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
|
||||
import type { TabbedModalTab } from '#ui/components'
|
||||
import { TabbedModal } from '#ui/components'
|
||||
import { defineMessage, defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
import {
|
||||
ServerInstanceSettingsAdvancedPage,
|
||||
ServerInstanceSettingsGeneralPage,
|
||||
serverInstanceSettingsTabDefinitions,
|
||||
type ServerInstanceSettingsTabId,
|
||||
ServerSettingsInstallationPage,
|
||||
ServerSettingsPropertiesPage,
|
||||
} from '#ui/layouts/shared/server-settings'
|
||||
import { provideServerSettings } from '#ui/layouts/shared/server-settings/providers/server-settings'
|
||||
import {
|
||||
injectModrinthClient,
|
||||
injectModrinthServerContext,
|
||||
injectNotificationManager,
|
||||
provideModrinthServerContext,
|
||||
} from '#ui/providers'
|
||||
import { commonMessages } from '#ui/utils/common-messages'
|
||||
|
||||
type ShowOptions = {
|
||||
serverId: string
|
||||
tabIndex?: number
|
||||
tabId?: ServerInstanceSettingsTabId
|
||||
worldId?: string | null
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
resolveViewer: () => Promise<{ userId: string | null; userRole: string | null }>
|
||||
browseModpacks?: (args: {
|
||||
serverId: string
|
||||
worldId: string | null
|
||||
from: 'reset-server'
|
||||
}) => void | Promise<void>
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const queryClient = useQueryClient()
|
||||
const client = injectModrinthClient()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
|
||||
const messages = defineMessages({
|
||||
failedToLoadServer: {
|
||||
id: 'app.server-instance-settings.failed-to-load-server',
|
||||
defaultMessage: 'Failed to load instance settings',
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref<InstanceType<typeof TabbedModal> | null>(null)
|
||||
|
||||
const baseServerContext = injectModrinthServerContext()
|
||||
const selectedWorldId = ref<string | null>(baseServerContext.worldId.value)
|
||||
const modalServerContext = {
|
||||
...baseServerContext,
|
||||
worldId: selectedWorldId,
|
||||
} satisfies ReturnType<typeof injectModrinthServerContext>
|
||||
provideModrinthServerContext(modalServerContext)
|
||||
|
||||
const { serverId: currentServerId, worldId, server } = modalServerContext
|
||||
|
||||
const currentUserId = ref<string | null>(null)
|
||||
const currentUserRole = ref<string | null>(null)
|
||||
const serverFull = ref<Archon.Servers.v1.ServerFull | null>(null)
|
||||
|
||||
const isApp = ref(true)
|
||||
|
||||
const serverInstanceSettingsTabComponentMap = {
|
||||
general: ServerInstanceSettingsGeneralPage,
|
||||
installation: ServerSettingsInstallationPage,
|
||||
properties: ServerSettingsPropertiesPage,
|
||||
advanced: ServerInstanceSettingsAdvancedPage,
|
||||
} as const
|
||||
|
||||
provideServerSettings({
|
||||
isApp,
|
||||
currentUserId,
|
||||
currentUserRole,
|
||||
browseModpacks: props.browseModpacks ?? (() => {}),
|
||||
closeModal: () => hide(),
|
||||
})
|
||||
|
||||
const ownerId = computed(() => server.value?.owner_id ?? 'Ghost')
|
||||
const isOwner = computed(() => currentUserId.value != null && currentUserId.value === ownerId.value)
|
||||
const isAdmin = computed(() => currentUserRole.value === 'admin')
|
||||
const currentInstanceName = computed(() => {
|
||||
const id = worldId.value
|
||||
if (!id) return null
|
||||
return serverFull.value?.worlds.find((world) => world.id === id)?.name ?? null
|
||||
})
|
||||
|
||||
const tabs = computed<TabbedModalTab[]>(() =>
|
||||
serverInstanceSettingsTabDefinitions.map((tab) => {
|
||||
const ctx = {
|
||||
serverId: currentServerId,
|
||||
ownerId: ownerId.value,
|
||||
serverStatus: server.value?.status,
|
||||
isOwner: isOwner.value,
|
||||
isAdmin: isAdmin.value,
|
||||
}
|
||||
const name = defineMessage({
|
||||
id: `server.instance-settings.tabs.${tab.id}`,
|
||||
defaultMessage: tab.label,
|
||||
})
|
||||
const shown = tab.shown ? tab.shown(ctx) : true
|
||||
|
||||
return {
|
||||
name,
|
||||
icon: tab.icon,
|
||||
content: serverInstanceSettingsTabComponentMap[tab.id],
|
||||
shown,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
async function fetchViewer() {
|
||||
currentUserId.value = null
|
||||
currentUserRole.value = null
|
||||
|
||||
const result = await props.resolveViewer()
|
||||
currentUserId.value = result.userId
|
||||
currentUserRole.value = result.userRole
|
||||
}
|
||||
|
||||
async function show({ serverId, tabIndex, tabId, worldId: requestedWorldId }: ShowOptions) {
|
||||
try {
|
||||
const targetServerId = currentServerId
|
||||
selectedWorldId.value = requestedWorldId ?? baseServerContext.worldId.value
|
||||
if (serverId !== targetServerId) {
|
||||
console.warn(
|
||||
`[ServerInstanceSettingsModal] Ignoring mismatched serverId "${serverId}" in favor of context "${targetServerId}"`,
|
||||
)
|
||||
}
|
||||
|
||||
const cachedServer = queryClient.getQueryData<Archon.Servers.v0.Server>([
|
||||
'servers',
|
||||
'detail',
|
||||
targetServerId,
|
||||
])
|
||||
const cachedFull = queryClient.getQueryData<Archon.Servers.v1.ServerFull>([
|
||||
'servers',
|
||||
'v1',
|
||||
'detail',
|
||||
targetServerId,
|
||||
])
|
||||
|
||||
serverFull.value = cachedFull ?? null
|
||||
modal.value?.show()
|
||||
const visibleTabs = tabs.value.filter((tab) => tab.shown !== false)
|
||||
let requestedTab = tabIndex ?? 0
|
||||
if (tabId) {
|
||||
const defIndex = serverInstanceSettingsTabDefinitions.findIndex((d) => d.id === tabId)
|
||||
if (defIndex >= 0) {
|
||||
const visibleIndex = visibleTabs.findIndex(
|
||||
(_, i) => tabs.value.indexOf(visibleTabs[i]) === defIndex,
|
||||
)
|
||||
if (visibleIndex >= 0) requestedTab = visibleIndex
|
||||
}
|
||||
}
|
||||
const clampedTab = Math.min(Math.max(requestedTab, 0), Math.max(visibleTabs.length - 1, 0))
|
||||
nextTick(() => modal.value?.setTab(clampedTab))
|
||||
|
||||
const fetchPromises: Promise<unknown>[] = [fetchViewer()]
|
||||
|
||||
if (!cachedServer) {
|
||||
fetchPromises.push(
|
||||
queryClient.fetchQuery({
|
||||
queryKey: ['servers', 'detail', targetServerId],
|
||||
queryFn: () => client.archon.servers_v0.get(targetServerId),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
if (!cachedFull) {
|
||||
fetchPromises.push(
|
||||
queryClient
|
||||
.fetchQuery({
|
||||
queryKey: ['servers', 'v1', 'detail', targetServerId],
|
||||
queryFn: () => client.archon.servers_v1.get(targetServerId),
|
||||
})
|
||||
.then((data) => {
|
||||
serverFull.value = data
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
await Promise.all(fetchPromises)
|
||||
|
||||
if (worldId.value) {
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['servers', 'properties', 'v1', targetServerId, worldId.value],
|
||||
queryFn: () => client.archon.properties_v1.getProperties(targetServerId, worldId.value!),
|
||||
})
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['content', 'list', 'v1', targetServerId, worldId.value],
|
||||
queryFn: () =>
|
||||
client.archon.content_v1.getAddons(targetServerId, worldId.value!, {
|
||||
from_modpack: false,
|
||||
}),
|
||||
})
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['servers', 'startup', 'v1', targetServerId, worldId.value],
|
||||
queryFn: () => client.archon.options_v1.getStartup(targetServerId, worldId.value!),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
addNotification({
|
||||
type: 'error',
|
||||
title: formatMessage(messages.failedToLoadServer),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
modal.value?.hide()
|
||||
}
|
||||
|
||||
defineExpose({ show, hide })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabbedModal
|
||||
ref="modal"
|
||||
:tabs="tabs"
|
||||
:max-width="'min(980px, calc(95vw - 2rem))'"
|
||||
:width="'min(980px, calc(95vw - 2rem))'"
|
||||
>
|
||||
<template #title>
|
||||
<span class="flex min-w-0 items-center gap-2 text-lg font-semibold text-primary">
|
||||
<span class="truncate">{{ server.name || 'Server' }}</span>
|
||||
<ChevronRightIcon class="shrink-0" />
|
||||
<span class="truncate">{{ currentInstanceName || 'Instance' }}</span>
|
||||
<ChevronRightIcon class="shrink-0" />
|
||||
<span class="shrink-0 font-extrabold text-contrast">{{
|
||||
formatMessage(commonMessages.settingsLabel)
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
</TabbedModal>
|
||||
</template>
|
||||
@@ -10,9 +10,7 @@ import { defineMessage, defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
import {
|
||||
ServerSettingsAdvancedPage,
|
||||
ServerSettingsGeneralPage,
|
||||
ServerSettingsInstallationPage,
|
||||
ServerSettingsNetworkPage,
|
||||
ServerSettingsPropertiesPage,
|
||||
serverSettingsTabDefinitions,
|
||||
type ServerSettingsTabId,
|
||||
} from '#ui/layouts/shared/server-settings'
|
||||
@@ -53,7 +51,7 @@ const messages = defineMessages({
|
||||
|
||||
const modal = ref<InstanceType<typeof TabbedModal> | null>(null)
|
||||
|
||||
const { serverId: currentServerId, worldId, server } = injectModrinthServerContext()
|
||||
const { serverId: currentServerId, server } = injectModrinthServerContext()
|
||||
|
||||
const currentUserId = ref<string | null>(null)
|
||||
const currentUserRole = ref<string | null>(null)
|
||||
@@ -72,9 +70,7 @@ useQuery({
|
||||
|
||||
const serverSettingsTabComponentMap = {
|
||||
general: ServerSettingsGeneralPage,
|
||||
installation: ServerSettingsInstallationPage,
|
||||
network: ServerSettingsNetworkPage,
|
||||
properties: ServerSettingsPropertiesPage,
|
||||
advanced: ServerSettingsAdvancedPage,
|
||||
} as const
|
||||
|
||||
@@ -146,12 +142,6 @@ async function show({ serverId, tabIndex, tabId }: ShowOptions) {
|
||||
'detail',
|
||||
targetServerId,
|
||||
])
|
||||
const cachedFull = queryClient.getQueryData<Archon.Servers.v1.ServerFull>([
|
||||
'servers',
|
||||
'v1',
|
||||
'detail',
|
||||
targetServerId,
|
||||
])
|
||||
|
||||
modal.value?.show()
|
||||
const visibleTabs = tabs.value.filter((tab) => tab.shown !== false)
|
||||
@@ -179,34 +169,8 @@ async function show({ serverId, tabIndex, tabId }: ShowOptions) {
|
||||
)
|
||||
}
|
||||
|
||||
if (!cachedFull) {
|
||||
fetchPromises.push(
|
||||
queryClient.fetchQuery({
|
||||
queryKey: ['servers', 'v1', 'detail', targetServerId],
|
||||
queryFn: () => client.archon.servers_v1.get(targetServerId),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
await Promise.all(fetchPromises)
|
||||
|
||||
if (worldId.value) {
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['servers', 'properties', 'v1', targetServerId, worldId.value],
|
||||
queryFn: () => client.archon.properties_v1.getProperties(targetServerId, worldId.value!),
|
||||
})
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['content', 'list', 'v1', targetServerId, worldId.value],
|
||||
queryFn: () =>
|
||||
client.archon.content_v1.getAddons(targetServerId, worldId.value!, {
|
||||
from_modpack: false,
|
||||
}),
|
||||
})
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['servers', 'startup', 'v1', targetServerId, worldId.value],
|
||||
queryFn: () => client.archon.options_v1.getStartup(targetServerId, worldId.value!),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
addNotification({
|
||||
|
||||
@@ -41,7 +41,7 @@ const { formatMessage } = useVIntl()
|
||||
const messages = defineMessages({
|
||||
rateLimitTitle: {
|
||||
id: 'servers.setup.rate-limit.title',
|
||||
defaultMessage: 'Cannot reinstall server',
|
||||
defaultMessage: 'Cannot reinstall instance',
|
||||
},
|
||||
rateLimitText: {
|
||||
id: 'servers.setup.rate-limit.text',
|
||||
|
||||
@@ -118,7 +118,7 @@ const messages = defineMessages({
|
||||
creatingBackupDescription: {
|
||||
id: 'servers.backups.admonition.creating-backup.description',
|
||||
defaultMessage:
|
||||
'Saving world data and server configuration for {backupName}. This can take a few minutes.',
|
||||
'Saving instance data and server configuration for {backupName}. This can take a few minutes.',
|
||||
},
|
||||
backupFailedTitle: {
|
||||
id: 'servers.backups.admonition.backup-failed.title',
|
||||
|
||||
@@ -11,6 +11,7 @@ export { default as ModrinthServersIcon } from './ModrinthServersIcon.vue'
|
||||
export { default as SaveBanner } from './SaveBanner.vue'
|
||||
export * from './server-header'
|
||||
export { default as ServerListEmpty } from './server-list-empty/ServerListEmpty.vue'
|
||||
export { default as ServerInstanceSettingsModal } from './ServerInstanceSettingsModal.vue'
|
||||
export { type PendingChange, default as ServerListing } from './ServerListing.vue'
|
||||
export { default as ServerSettingsModal } from './ServerSettingsModal.vue'
|
||||
export { default as ServerSetupModal } from './ServerSetupModal.vue'
|
||||
|
||||
@@ -146,7 +146,9 @@
|
||||
</span>
|
||||
</AutoLink>
|
||||
</div>
|
||||
<span v-else class="font-semibold text-contrast">{{ formatMessage(messages.noModpack) }}</span>
|
||||
<span v-else class="font-semibold text-contrast">{{
|
||||
formatMessage(messages.noModpack)
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
class="grid min-h-6 grid-cols-[minmax(0,1fr)_minmax(0,25%)] items-center gap-4 text-base text-secondary [&>*:last-child]:max-w-full [&>*:last-child]:justify-self-end"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</AutoLink>
|
||||
<div
|
||||
v-else
|
||||
v-tooltip="'Change server version'"
|
||||
v-tooltip="'Change instance version'"
|
||||
class="pointer-events-none flex min-w-0 flex-row items-center gap-1 truncate text-sm font-medium"
|
||||
>
|
||||
{{ game[0].toUpperCase() + game.slice(1) }}
|
||||
@@ -46,7 +46,7 @@ defineProps<{
|
||||
const settingsModal = injectServerSettingsModal(null)
|
||||
const settingsLinkTarget = computed(() => {
|
||||
if (settingsModal) {
|
||||
return () => settingsModal.openServerSettings({ tabId: 'installation' })
|
||||
return () => settingsModal.openServerInstanceSettings({ tabId: 'installation' })
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div v-else class="size-5 shrink-0 animate-pulse rounded-full bg-button-border"></div>
|
||||
<AutoLink
|
||||
v-if="isLink"
|
||||
v-tooltip="'Change server loader'"
|
||||
v-tooltip="'Change instance loader'"
|
||||
:to="settingsLinkTarget"
|
||||
class="flex min-w-0 items-center font-medium text-sm"
|
||||
:class="settingsLinkTarget ? 'hover:underline' : ''"
|
||||
@@ -54,7 +54,7 @@ defineProps<{
|
||||
const settingsModal = injectServerSettingsModal(null)
|
||||
const settingsLinkTarget = computed(() => {
|
||||
if (settingsModal) {
|
||||
return () => settingsModal.openServerSettings({ tabId: 'installation' })
|
||||
return () => settingsModal.openServerInstanceSettings({ tabId: 'installation' })
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
+2
@@ -15,6 +15,7 @@
|
||||
:backup-name="
|
||||
visibleBackupTip ? `Before bulk update (${visibleBackupTip})` : 'Before bulk update'
|
||||
"
|
||||
:target-type="props.targetType"
|
||||
:shift-click-hint-override="formatMessage(messages.shiftClickHint)"
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
@@ -88,6 +89,7 @@ const props = defineProps<{
|
||||
backupTip?: string
|
||||
actionDisabled?: boolean
|
||||
actionDisabledTooltip?: string
|
||||
targetType?: 'server' | 'instance'
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
+4
-1
@@ -17,6 +17,7 @@
|
||||
<InlineBackupCreator
|
||||
ref="backupCreator"
|
||||
:backup-name="props.backupTip ? `Before deletion (${props.backupTip})` : 'Before deletion'"
|
||||
:target-type="props.targetType"
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
</div>
|
||||
@@ -75,7 +76,7 @@ const messages = defineMessages({
|
||||
admonitionBody: {
|
||||
id: 'content.confirm-deletion.admonition-body',
|
||||
defaultMessage:
|
||||
'Deleting a mod can permanently affect your world and may cause missing content or unexpected issues when it loads again.',
|
||||
'Deleting a mod can permanently affect your instance and may cause missing content or unexpected issues when it starts again.',
|
||||
},
|
||||
deleteButton: {
|
||||
id: 'content.confirm-deletion.delete-button',
|
||||
@@ -91,12 +92,14 @@ const props = withDefaults(
|
||||
backupTip?: string
|
||||
actionDisabled?: boolean
|
||||
actionDisabledTooltip?: string
|
||||
targetType?: 'server' | 'instance'
|
||||
}>(),
|
||||
{
|
||||
variant: 'instance',
|
||||
backupTip: undefined,
|
||||
actionDisabled: false,
|
||||
actionDisabledTooltip: undefined,
|
||||
targetType: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
+2
@@ -22,6 +22,7 @@
|
||||
<InlineBackupCreator
|
||||
ref="backupCreator"
|
||||
:backup-name="backupName"
|
||||
:target-type="props.targetType"
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
</div>
|
||||
@@ -68,6 +69,7 @@ const props = defineProps<{
|
||||
backupTip?: string
|
||||
actionDisabled?: boolean
|
||||
actionDisabledTooltip?: string
|
||||
targetType?: 'server' | 'instance'
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<InlineBackupCreator
|
||||
ref="backupCreator"
|
||||
:backup-name="backupTip ? `Before reinstall (${backupTip})` : 'Before reinstall'"
|
||||
:target-type="targetType"
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
</div>
|
||||
@@ -75,6 +76,7 @@ const messages = defineMessages({
|
||||
defineProps<{
|
||||
server?: boolean
|
||||
backupTip?: string
|
||||
targetType?: 'server' | 'instance'
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<InlineBackupCreator
|
||||
ref="backupCreator"
|
||||
:backup-name="props.backupTip ? `Before unlink (${props.backupTip})` : 'Before unlink'"
|
||||
:target-type="props.targetType"
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
</div>
|
||||
@@ -58,6 +59,7 @@ const props = defineProps<{
|
||||
backupTip?: string
|
||||
actionDisabled?: boolean
|
||||
actionDisabledTooltip?: string
|
||||
targetType?: 'server' | 'instance'
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
+6
-16
@@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3">
|
||||
<span class="text-primary">
|
||||
{{
|
||||
formatMessage(messages.warningBody, {
|
||||
type: formatMessage(backup.isServer ? messages.worldLabel : messages.instanceLabel),
|
||||
})
|
||||
}}
|
||||
{{ formatMessage(messages.warningBody, { type: backupTargetType }) }}
|
||||
</span>
|
||||
|
||||
<div v-if="backup.available" class="flex items-center gap-2">
|
||||
@@ -47,7 +43,7 @@
|
||||
|
||||
<TriangleAlertIcon
|
||||
v-if="backup.isServer"
|
||||
v-tooltip="formatMessage(messages.backupTakesAWhile)"
|
||||
v-tooltip="formatMessage(messages.backupTakesAWhile, { type: backupTargetType })"
|
||||
class="size-5 shrink-0 text-brand-orange hover:brightness-110"
|
||||
/>
|
||||
</div>
|
||||
@@ -71,6 +67,7 @@ import { useInlineBackup } from '../../composables/use-inline-backup'
|
||||
|
||||
const props = defineProps<{
|
||||
backupName: string
|
||||
targetType?: 'server' | 'instance'
|
||||
hideShiftClickHint?: boolean
|
||||
shiftClickHintOverride?: string
|
||||
}>()
|
||||
@@ -87,6 +84,7 @@ const canManageBackups = computed(
|
||||
const permissionDeniedMessage = computed(() => formatMessage(commonMessages.noPermissionAction))
|
||||
|
||||
const backup = useInlineBackup(() => props.backupName)
|
||||
const backupTargetType = computed(() => props.targetType ?? 'instance')
|
||||
|
||||
function startBackup() {
|
||||
if (
|
||||
@@ -115,15 +113,7 @@ const messages = defineMessages({
|
||||
warningBody: {
|
||||
id: 'content.inline-backup.warning-body',
|
||||
defaultMessage:
|
||||
'We recommend creating a backup before proceeding so you can restore your {type} if anything breaks.',
|
||||
},
|
||||
worldLabel: {
|
||||
id: 'content.inline-backup.world-label',
|
||||
defaultMessage: 'world',
|
||||
},
|
||||
instanceLabel: {
|
||||
id: 'content.inline-backup.instance-label',
|
||||
defaultMessage: 'instance',
|
||||
'We recommend creating a backup before proceeding so you can restore your {type, select, server {server} other {instance}} if anything breaks.',
|
||||
},
|
||||
createBackup: {
|
||||
id: 'content.inline-backup.create-backup',
|
||||
@@ -144,7 +134,7 @@ const messages = defineMessages({
|
||||
backupTakesAWhile: {
|
||||
id: 'content.inline-backup.backup-takes-a-while',
|
||||
defaultMessage:
|
||||
'Creating a backup may take several minutes depending on the size of your server.',
|
||||
'Creating a backup may take several minutes depending on the size of your {type, select, server {server} other {instance}}.',
|
||||
},
|
||||
backupInProgress: {
|
||||
id: 'content.inline-backup.backup-in-progress',
|
||||
|
||||
+10
-10
@@ -207,9 +207,10 @@
|
||||
formatMessage(
|
||||
incompatibilityWarningMode
|
||||
? messages.incompatibilityWarning
|
||||
: isApp
|
||||
? messages.updateWarningApp
|
||||
: messages.updateWarningWeb,
|
||||
: messages.updateWarning,
|
||||
{
|
||||
type: updateWarningTargetType,
|
||||
},
|
||||
)
|
||||
}}</span>
|
||||
</div>
|
||||
@@ -352,14 +353,10 @@ const messages = defineMessages({
|
||||
id: 'instances.updater-modal.select-version',
|
||||
defaultMessage: 'Select a version to view its changelog',
|
||||
},
|
||||
updateWarningApp: {
|
||||
id: 'instances.updater-modal.warning-app',
|
||||
updateWarning: {
|
||||
id: 'instances.updater-modal.warning',
|
||||
defaultMessage:
|
||||
'Updating can break your instance. Review version changelogs and back up first.',
|
||||
},
|
||||
updateWarningWeb: {
|
||||
id: 'instances.updater-modal.warning-web',
|
||||
defaultMessage: 'Updating can break your world. Review version changelogs and back up first.',
|
||||
'Updating can break your {type, select, server {server} other {instance}}. Review version changelogs and back up first.',
|
||||
},
|
||||
incompatibilityWarning: {
|
||||
id: 'instances.updater-modal.incompatibility-warning',
|
||||
@@ -424,6 +421,7 @@ const props = withDefaults(
|
||||
currentLoader: string
|
||||
currentVersionId: string
|
||||
isApp: boolean
|
||||
targetType?: 'server' | 'instance'
|
||||
/** The project type (e.g. mod, shader, resourcepack, datapack, modpack). */
|
||||
projectType?: string
|
||||
projectIconUrl?: string
|
||||
@@ -440,6 +438,7 @@ const props = withDefaults(
|
||||
actionDisabledTooltip?: string
|
||||
}>(),
|
||||
{
|
||||
targetType: undefined,
|
||||
projectType: undefined,
|
||||
projectIconUrl: undefined,
|
||||
projectName: undefined,
|
||||
@@ -469,6 +468,7 @@ const defaultHeader = computed(() => {
|
||||
: messages.updateVersionHeader,
|
||||
)
|
||||
})
|
||||
const updateWarningTargetType = computed(() => props.targetType ?? 'instance')
|
||||
|
||||
const emit = defineEmits<{
|
||||
update: [version: Labrinth.Versions.v2.Version, event: MouseEvent]
|
||||
|
||||
@@ -1039,6 +1039,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
:backup-tip="pendingDeletionItems.map((i) => i.project?.title ?? i.file_name).join(', ')"
|
||||
:action-disabled="ctx.isBusy.value"
|
||||
:action-disabled-tooltip="ctx.busyMessage?.value ?? undefined"
|
||||
:target-type="ctx.deletionContext ?? 'instance'"
|
||||
@delete="confirmDelete"
|
||||
/>
|
||||
<ContentDependencyWarningModal
|
||||
@@ -1059,6 +1060,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
:server="ctx.deletionContext === 'server'"
|
||||
:action-disabled="ctx.isBusy.value"
|
||||
:action-disabled-tooltip="ctx.busyMessage?.value ?? undefined"
|
||||
:target-type="ctx.deletionContext ?? 'instance'"
|
||||
@update="confirmBulkUpdate"
|
||||
/>
|
||||
<ConfirmUnlinkModal
|
||||
@@ -1068,6 +1070,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
:backup-tip="ctx.modpack.value?.project.title"
|
||||
:action-disabled="ctx.isBusy.value"
|
||||
:action-disabled-tooltip="ctx.busyMessage?.value ?? undefined"
|
||||
:target-type="ctx.deletionContext ?? 'instance'"
|
||||
@unlink="ctx.unlinkModpack!()"
|
||||
/>
|
||||
|
||||
|
||||
+10
-2
@@ -5,7 +5,13 @@
|
||||
<Admonition :type="hasUnknownContent ? 'warning' : 'info'" :header="admonitionHeader">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span>{{ description }}</span>
|
||||
<span v-if="hasUnknownContent">{{ formatMessage(messages.unknownContentBody) }}</span>
|
||||
<span v-if="hasUnknownContent">
|
||||
{{
|
||||
formatMessage(messages.unknownContentBody, {
|
||||
type: targetType ?? 'server',
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</Admonition>
|
||||
|
||||
@@ -73,6 +79,7 @@
|
||||
<InlineBackupCreator
|
||||
ref="backupCreator"
|
||||
backup-name="Before version change"
|
||||
:target-type="targetType"
|
||||
hide-shift-click-hint
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
@@ -131,6 +138,7 @@ const props = defineProps<{
|
||||
confirmIcon?: Component
|
||||
showReportButton?: boolean
|
||||
showBackupCreator?: boolean
|
||||
targetType?: 'server' | 'instance'
|
||||
removedLabel?: string
|
||||
disableClose?: boolean
|
||||
}>()
|
||||
@@ -192,7 +200,7 @@ const messages = defineMessages({
|
||||
unknownContentBody: {
|
||||
id: 'content.diff-modal.unknown-content-body',
|
||||
defaultMessage:
|
||||
'Some content on your server could not be analyzed and may be affected by this change.',
|
||||
'Some content on your {type, select, server {server} other {instance}} could not be analyzed and may be affected by this change.',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
+15
-3
@@ -13,7 +13,9 @@
|
||||
<span>
|
||||
{{
|
||||
variant === 'loader-change'
|
||||
? formatMessage(messages.loaderChangeBody)
|
||||
? formatMessage(messages.loaderChangeBody, {
|
||||
type: server ? 'server' : 'instance',
|
||||
})
|
||||
: formatMessage(messages.gameVersionWarningBody)
|
||||
}}
|
||||
</span>
|
||||
@@ -21,7 +23,11 @@
|
||||
<ButtonStyled color="red">
|
||||
<button :disabled="loading" @click="handleResetServer">
|
||||
<TrashIcon class="size-5" />
|
||||
{{ formatMessage(commonMessages.resetServerButton) }}
|
||||
{{
|
||||
formatMessage(messages.resetButton, {
|
||||
type: server ? 'server' : 'instance',
|
||||
})
|
||||
}}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -33,6 +39,7 @@
|
||||
:backup-name="
|
||||
variant === 'loader-change' ? 'Before loader change' : 'Before version change'
|
||||
"
|
||||
:target-type="server ? 'server' : 'instance'"
|
||||
hide-shift-click-hint
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
@@ -104,6 +111,7 @@ import InlineBackupCreator from '../../content-tab/components/modals/InlineBacku
|
||||
defineProps<{
|
||||
variant: 'loader-change' | 'game-version-change'
|
||||
loading?: boolean
|
||||
server?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -165,7 +173,7 @@ const messages = defineMessages({
|
||||
loaderChangeBody: {
|
||||
id: 'installation-settings.incompatible-content.loader-change-body',
|
||||
defaultMessage:
|
||||
'When changing the loader, all installed content will be disabled. We recommend resetting your server instead.',
|
||||
'When changing the loader, all installed content will be disabled. We recommend resetting your {type, select, server {server} other {instance}} instead.',
|
||||
},
|
||||
gameVersionWarningTitle: {
|
||||
id: 'installation-settings.incompatible-content.game-version-warning-title',
|
||||
@@ -188,6 +196,10 @@ const messages = defineMessages({
|
||||
id: 'installation-settings.incompatible-content.disable-conflicts-button',
|
||||
defaultMessage: 'Disable conflicts',
|
||||
},
|
||||
resetButton: {
|
||||
id: 'installation-settings.incompatible-content.reset-button',
|
||||
defaultMessage: 'Reset {type, select, server {server} other {instance}}',
|
||||
},
|
||||
})
|
||||
|
||||
defineExpose({ show, hide })
|
||||
|
||||
@@ -189,7 +189,9 @@ const platformDisabledItems = computed(() =>
|
||||
const platformDisabledTooltip = computed(() =>
|
||||
ctx.isBusy.value
|
||||
? (ctx.busyMessage?.value ?? undefined)
|
||||
: formatMessage(messages.platformLockTooltip),
|
||||
: formatMessage(messages.platformLockTooltip, {
|
||||
type: ctx.isServer ? 'server' : 'instance',
|
||||
}),
|
||||
)
|
||||
|
||||
const showModpackVersionActions = computed(() => {
|
||||
@@ -206,6 +208,19 @@ const isLocalFile = computed(() => {
|
||||
|
||||
const isLinkedModpack = computed(() => showModpackVersionActions.value || isLocalFile.value)
|
||||
|
||||
const showBackupCreator = computed(() => {
|
||||
const val = ctx.showBackupCreator
|
||||
if (val == null) return ctx.isServer
|
||||
return typeof val === 'boolean' ? val : val.value
|
||||
})
|
||||
|
||||
const repairDescriptionMessage = computed(() => {
|
||||
const kind = ctx.repairDescriptionKind ?? (ctx.isServer ? 'server' : 'app-instance')
|
||||
if (kind === 'server') return messages.repairServerDescription
|
||||
if (kind === 'server-instance') return messages.repairServerInstanceDescription
|
||||
return messages.repairInstanceDescription
|
||||
})
|
||||
|
||||
function handleModpackUpdateRequest(version: Labrinth.Versions.v2.Version, event?: MouseEvent) {
|
||||
debug('handleModpackUpdateRequest: start', {
|
||||
versionId: version.id,
|
||||
@@ -468,6 +483,11 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
'Reinstalls the loader and Minecraft dependencies without deleting your content. This may resolve issues if your server is not starting correctly.',
|
||||
},
|
||||
repairServerInstanceDescription: {
|
||||
id: 'installation-settings.repair.server-instance-description',
|
||||
defaultMessage:
|
||||
'Reinstalls the loader and Minecraft dependencies without deleting your content. This may resolve issues if this instance is not starting correctly.',
|
||||
},
|
||||
editWarningInstance: {
|
||||
id: 'installation-settings.edit.warning-instance',
|
||||
defaultMessage:
|
||||
@@ -540,7 +560,8 @@ const messages = defineMessages({
|
||||
},
|
||||
platformLockTooltip: {
|
||||
id: 'installation-settings.platform-lock-tooltip',
|
||||
defaultMessage: 'You will need to reset your server to switch loader.',
|
||||
defaultMessage:
|
||||
'You will need to reset your {type, select, server {server} other {instance}} to switch loader.',
|
||||
},
|
||||
confirmVersionChangeHeader: {
|
||||
id: 'installation-settings.confirm-version-change-header',
|
||||
@@ -552,7 +573,8 @@ const messages = defineMessages({
|
||||
},
|
||||
confirmVersionChangeDescription: {
|
||||
id: 'installation-settings.confirm-version-change-description',
|
||||
defaultMessage: 'Changing to {gameVersion} will modify the following content on your server.',
|
||||
defaultMessage:
|
||||
'Changing to {gameVersion} will modify the following content on your {type, select, server {server} other {instance}}.',
|
||||
},
|
||||
removedIncompatible: {
|
||||
id: 'installation-settings.removed-incompatible',
|
||||
@@ -771,13 +793,7 @@ const messages = defineMessages({
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<span class="text-primary">
|
||||
{{
|
||||
formatMessage(
|
||||
ctx.isServer
|
||||
? messages.repairServerDescription
|
||||
: messages.repairInstanceDescription,
|
||||
)
|
||||
}}
|
||||
{{ formatMessage(repairDescriptionMessage) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1009,13 +1025,7 @@ const messages = defineMessages({
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<span class="text-primary">
|
||||
{{
|
||||
formatMessage(
|
||||
ctx.isServer
|
||||
? messages.repairServerDescription
|
||||
: messages.repairInstanceDescription,
|
||||
)
|
||||
}}
|
||||
{{ formatMessage(repairDescriptionMessage) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1073,6 +1083,7 @@ const messages = defineMessages({
|
||||
ref="incompatibleContentModal"
|
||||
:variant="form.incompatibleContentVariant.value"
|
||||
:loading="form.isVerifying.value || form.isSaving.value"
|
||||
:server="ctx.isServer"
|
||||
@confirm-loader-change="form.confirmLoaderChange()"
|
||||
@auto-fix="form.confirmAutoFix()"
|
||||
@disable-conflicts="form.confirmDisableConflicts()"
|
||||
@@ -1087,6 +1098,7 @@ const messages = defineMessages({
|
||||
:description="
|
||||
formatMessage(messages.confirmVersionChangeDescription, {
|
||||
gameVersion: form.pendingPreview.value.newGameVersion,
|
||||
type: ctx.isServer ? 'server' : 'instance',
|
||||
})
|
||||
"
|
||||
:admonition-header="formatMessage(messages.confirmVersionChangeHeader)"
|
||||
@@ -1094,8 +1106,9 @@ const messages = defineMessages({
|
||||
:has-unknown-content="form.pendingPreview.value.hasUnknownContent"
|
||||
:confirm-label="formatMessage(messages.confirmVersionChange)"
|
||||
:confirm-icon="SaveIcon"
|
||||
:target-type="ctx.isServer ? 'server' : 'instance'"
|
||||
:removed-label="formatMessage(messages.removedIncompatible)"
|
||||
:show-backup-creator="ctx.isServer"
|
||||
:show-backup-creator="showBackupCreator"
|
||||
@confirm="form.confirmSave()"
|
||||
@cancel="form.cancelPreview()"
|
||||
/>
|
||||
|
||||
+6
@@ -63,6 +63,12 @@ export interface InstallationSettingsContext {
|
||||
/** True when the linked modpack was uploaded as a local file rather than from Modrinth */
|
||||
isLocalFile?: boolean | ComputedRef<boolean>
|
||||
|
||||
/** Controls whether destructive installation changes offer inline backups. Defaults to isServer. */
|
||||
showBackupCreator?: boolean | ComputedRef<boolean>
|
||||
|
||||
/** Controls repair description copy separately from the title target. */
|
||||
repairDescriptionKind?: 'server' | 'server-instance' | 'app-instance'
|
||||
|
||||
repairing?: Ref<boolean>
|
||||
reinstalling?: Ref<boolean>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<div class="relative h-full w-full">
|
||||
<div class="flex h-full w-full flex-col gap-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
<!-- SFTP section -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col items-center justify-between gap-0.5 sm:flex-row">
|
||||
<span class="text-lg font-semibold text-contrast">SFTP</span>
|
||||
@@ -89,7 +88,6 @@
|
||||
:disabled="!canWriteFiles"
|
||||
@click.stop="togglePasswordVisibility"
|
||||
>
|
||||
<!-- look into doing stop propagation here -->
|
||||
<EyeIcon v-if="showPassword" class="h-5 w-5" />
|
||||
<EyeOffIcon v-else class="h-5 w-5" />
|
||||
</button>
|
||||
@@ -100,156 +98,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Startup command section -->
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="flex h-10 flex-col items-end justify-between gap-4 sm:flex-row">
|
||||
<label for="startup-command-field" class="mb-0.5 flex flex-col gap-2">
|
||||
<span class="text-lg font-semibold text-contrast">Startup command</span>
|
||||
</label>
|
||||
<ButtonStyled v-if="startupCommand !== defaultStartupCommand" type="transparent">
|
||||
<button
|
||||
v-tooltip="advancedActionTooltip"
|
||||
:disabled="
|
||||
isStartupLoading ||
|
||||
startupCommand === defaultStartupCommand ||
|
||||
!canUseAdvancedSettings
|
||||
"
|
||||
class="relative !w-full sm:!w-auto"
|
||||
@click="resetToDefault"
|
||||
>
|
||||
<UpdatedIcon class="h-5 w-5" />
|
||||
Default
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<StyledInput
|
||||
id="startup-command-field"
|
||||
v-model="startupCommand"
|
||||
v-tooltip="advancedActionTooltip"
|
||||
multiline
|
||||
resize="vertical"
|
||||
input-class="font-mono field-sizing-content"
|
||||
:disabled="isStartupLoading || !canUseAdvancedSettings"
|
||||
/>
|
||||
<div
|
||||
v-if="isStartupLoading"
|
||||
class="bg-bg/50 absolute inset-0 flex items-center justify-center rounded-xl"
|
||||
>
|
||||
<SpinnerIcon class="h-6 w-6 animate-spin text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
<span> The command that runs when your server is started. </span>
|
||||
</div>
|
||||
|
||||
<!-- Java version section -->
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-lg font-semibold text-contrast">Java version</span>
|
||||
</div>
|
||||
<div class="relative max-w-xs">
|
||||
<Combobox
|
||||
:id="'java-version-field'"
|
||||
v-model="javaVersion"
|
||||
v-tooltip="advancedActionTooltip"
|
||||
name="java-version"
|
||||
:options="displayedJavaVersions"
|
||||
:display-value="javaVersionLabel ?? 'Java Version'"
|
||||
:disabled="isStartupLoading || !canUseAdvancedSettings"
|
||||
>
|
||||
<template #dropdown-footer>
|
||||
<button
|
||||
class="flex w-full cursor-pointer items-center justify-center gap-1.5 border-0 border-t border-solid border-surface-5 bg-transparent py-3 text-center text-sm font-semibold text-secondary transition-colors hover:text-contrast"
|
||||
@mousedown.prevent
|
||||
@click="showAllVersions = !showAllVersions"
|
||||
>
|
||||
<EyeOffIcon v-if="showAllVersions" class="size-4" />
|
||||
<EyeIcon v-else class="size-4" />
|
||||
{{ showAllVersions ? 'Hide extra versions' : 'Show all versions' }}
|
||||
</button>
|
||||
</template>
|
||||
</Combobox>
|
||||
<div
|
||||
v-if="isStartupLoading"
|
||||
class="bg-bg/50 absolute inset-0 flex items-center justify-center rounded-xl"
|
||||
>
|
||||
<SpinnerIcon class="h-5 w-5 animate-spin text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
<span> The Java version your server runs on. </span>
|
||||
</div>
|
||||
|
||||
<!-- Java runtime section -->
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-lg font-semibold text-contrast">Java runtime</span>
|
||||
</div>
|
||||
<div class="relative max-w-xs">
|
||||
<Combobox
|
||||
:id="'runtime-field'"
|
||||
v-model="jreVendor"
|
||||
v-tooltip="advancedActionTooltip"
|
||||
name="runtime"
|
||||
:options="JRE_VENDORS"
|
||||
:display-value="jreVendorLabel ?? 'Runtime'"
|
||||
:disabled="isStartupLoading || !canUseAdvancedSettings"
|
||||
/>
|
||||
<div
|
||||
v-if="isStartupLoading"
|
||||
class="bg-bg/50 absolute inset-0 flex items-center justify-center rounded-xl"
|
||||
>
|
||||
<SpinnerIcon class="h-5 w-5 animate-spin text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
<span> The Java runtime your server will use. </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SaveBanner
|
||||
:is-visible="!!hasUnsavedChanges || isPending"
|
||||
:server-id="serverId"
|
||||
:is-updating="isPending"
|
||||
:save="saveStartup"
|
||||
:reset="resetStartup"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Archon } from '@modrinth/api-client'
|
||||
import {
|
||||
CopyIcon,
|
||||
ExternalIcon,
|
||||
EyeIcon,
|
||||
EyeOffIcon,
|
||||
SpinnerIcon,
|
||||
UpdatedIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { CopyIcon, ExternalIcon, EyeIcon, EyeOffIcon } from '@modrinth/assets'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { ButtonStyled, Combobox, StyledInput } from '#ui/components'
|
||||
import SaveBanner from '#ui/components/servers/SaveBanner.vue'
|
||||
import { ButtonStyled } from '#ui/components'
|
||||
import { useServerPermissions } from '#ui/composables/server-permissions'
|
||||
import {
|
||||
injectModrinthClient,
|
||||
injectModrinthServerContext,
|
||||
injectNotificationManager,
|
||||
} from '#ui/providers'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { server, serverId, worldId } = injectModrinthServerContext()
|
||||
const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
const { canUseAdvancedSettings, canWriteFiles, permissionDeniedMessage } = useServerPermissions()
|
||||
const { server } = injectModrinthServerContext()
|
||||
const { canWriteFiles, permissionDeniedMessage } = useServerPermissions()
|
||||
|
||||
// SFTP state
|
||||
const showPassword = ref(false)
|
||||
const sftpUrl = computed(() => `sftp://${server.value?.sftp_username}@${server.value?.sftp_host}`)
|
||||
const advancedActionTooltip = computed(() =>
|
||||
canUseAdvancedSettings.value ? undefined : permissionDeniedMessage.value,
|
||||
)
|
||||
const sftpActionTooltip = computed(() =>
|
||||
canWriteFiles.value
|
||||
? 'This button only works with compatible SFTP clients (e.g. WinSCP)'
|
||||
@@ -272,160 +142,10 @@ const copyToClipboard = (name: string, textToCopy?: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
// Startup state
|
||||
const startupQueryKey = computed(() => ['servers', 'startup', 'v1', serverId, worldId.value])
|
||||
|
||||
const { data: startupData, isLoading: isStartupLoading } = useQuery({
|
||||
queryKey: startupQueryKey,
|
||||
queryFn: () => client.archon.options_v1.getStartup(serverId, worldId.value!),
|
||||
enabled: computed(() => worldId.value !== null),
|
||||
})
|
||||
|
||||
function togglePasswordVisibility() {
|
||||
if (!canWriteFiles.value) return
|
||||
showPassword.value = !showPassword.value
|
||||
}
|
||||
|
||||
const JAVA_VERSIONS = [
|
||||
{ value: 8, label: 'Java 8' },
|
||||
{ value: 11, label: 'Java 11' },
|
||||
{ value: 17, label: 'Java 17' },
|
||||
{ value: 21, label: 'Java 21' },
|
||||
{ value: 25, label: 'Java 25' },
|
||||
]
|
||||
|
||||
const showAllVersions = ref(false)
|
||||
|
||||
type MinecraftReleaseVersion = {
|
||||
major: number
|
||||
minor: number
|
||||
}
|
||||
|
||||
function parseMinecraftReleaseVersion(version: string): MinecraftReleaseVersion | null {
|
||||
const [majorPart, minorPart] = version.split('.')
|
||||
|
||||
if (!majorPart || !minorPart) return null
|
||||
|
||||
const major = Number(majorPart)
|
||||
const minor = Number(minorPart)
|
||||
|
||||
if (!Number.isInteger(major) || !Number.isInteger(minor)) return null
|
||||
|
||||
return { major, minor }
|
||||
}
|
||||
|
||||
function filterJavaVersions(compatibleVersions: number[]) {
|
||||
return JAVA_VERSIONS.filter((version) => compatibleVersions.includes(version.value))
|
||||
}
|
||||
|
||||
const displayedJavaVersions = computed(() => {
|
||||
if (showAllVersions.value) return JAVA_VERSIONS
|
||||
|
||||
const mcVersion = server.value?.mc_version ?? ''
|
||||
if (!mcVersion) return JAVA_VERSIONS
|
||||
|
||||
const releaseVersion = parseMinecraftReleaseVersion(mcVersion)
|
||||
if (!releaseVersion) return JAVA_VERSIONS
|
||||
|
||||
if (releaseVersion.major > 1) {
|
||||
if (releaseVersion.major >= 26) {
|
||||
return filterJavaVersions([25])
|
||||
}
|
||||
|
||||
return JAVA_VERSIONS
|
||||
}
|
||||
|
||||
if (releaseVersion.minor >= 20) return filterJavaVersions([21])
|
||||
if (releaseVersion.minor >= 17) return filterJavaVersions([17, 21])
|
||||
if (releaseVersion.minor >= 12) return filterJavaVersions([8, 11, 17, 21])
|
||||
if (releaseVersion.minor >= 6) return filterJavaVersions([8, 11])
|
||||
return filterJavaVersions([8])
|
||||
})
|
||||
|
||||
const JRE_VENDORS: { value: Archon.Content.v1.JreVendor; label: string }[] = [
|
||||
{ value: 'corretto', label: 'Corretto' },
|
||||
{ value: 'temurin', label: 'Temurin' },
|
||||
{ value: 'graal', label: 'GraalVM' },
|
||||
]
|
||||
|
||||
const savedStartupCommand = computed(() => startupData.value?.startup_command ?? '')
|
||||
const savedJavaVersion = computed(() => startupData.value?.java_version ?? undefined)
|
||||
const savedJreVendor = computed(() => startupData.value?.jre_vendor ?? undefined)
|
||||
const defaultStartupCommand = computed(
|
||||
() => startupData.value?.original_invocation ?? savedStartupCommand.value,
|
||||
)
|
||||
|
||||
const startupCommand = ref('')
|
||||
const javaVersion = ref<number>()
|
||||
const jreVendor = ref<Archon.Content.v1.JreVendor>()
|
||||
|
||||
const javaVersionLabel = computed(
|
||||
() => JAVA_VERSIONS.find((v) => v.value === javaVersion.value)?.label,
|
||||
)
|
||||
const jreVendorLabel = computed(() => JRE_VENDORS.find((v) => v.value === jreVendor.value)?.label)
|
||||
|
||||
function syncFormFromData() {
|
||||
startupCommand.value = savedStartupCommand.value
|
||||
javaVersion.value = savedJavaVersion.value
|
||||
jreVendor.value = savedJreVendor.value
|
||||
}
|
||||
|
||||
watch(
|
||||
startupData,
|
||||
(newData, oldData) => {
|
||||
if (newData && !oldData) {
|
||||
syncFormFromData()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const hasUnsavedChanges = computed(
|
||||
() =>
|
||||
startupCommand.value !== savedStartupCommand.value ||
|
||||
javaVersion.value !== savedJavaVersion.value ||
|
||||
jreVendor.value !== savedJreVendor.value,
|
||||
)
|
||||
|
||||
const { mutate: saveStartupMutation, isPending } = useMutation({
|
||||
mutationFn: () =>
|
||||
client.archon.options_v1.patchStartup(serverId, worldId.value!, {
|
||||
startup_command: startupCommand.value || null,
|
||||
java_version: javaVersion.value ?? null,
|
||||
jre_vendor: jreVendor.value ?? null,
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: startupQueryKey.value })
|
||||
syncFormFromData()
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: 'Server settings updated',
|
||||
text: 'Your server settings were successfully changed.',
|
||||
})
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error)
|
||||
addNotification({
|
||||
type: 'error',
|
||||
title: 'Failed to update server arguments',
|
||||
text: 'Please try again later.',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
function saveStartup() {
|
||||
if (!canUseAdvancedSettings.value) return
|
||||
saveStartupMutation()
|
||||
}
|
||||
|
||||
function resetStartup() {
|
||||
syncFormFromData()
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
if (!canUseAdvancedSettings.value) return
|
||||
startupCommand.value = defaultStartupCommand.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export { default as ServerSettingsAdvancedPage } from './advanced.vue'
|
||||
export { default as ServerSettingsGeneralPage } from './general.vue'
|
||||
export { default as ServerSettingsInstallationPage } from './installation.vue'
|
||||
export { default as ServerInstanceSettingsAdvancedPage } from './instance-advanced.vue'
|
||||
export { default as ServerInstanceSettingsGeneralPage } from './instance-general.vue'
|
||||
export { default as ServerSettingsNetworkPage } from './network.vue'
|
||||
export { default as ServerSettingsPropertiesPage } from './properties.vue'
|
||||
|
||||
@@ -16,30 +16,6 @@
|
||||
ref="installationSettingsLayout"
|
||||
@reset-server="showResetServerModal"
|
||||
>
|
||||
<template #extra>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<span class="text-lg font-semibold text-contrast">{{
|
||||
formatMessage(messages.resetServerTitle)
|
||||
}}</span>
|
||||
<div>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
v-tooltip="resetServerDisabledTooltip"
|
||||
class="!shadow-none"
|
||||
:disabled="resetServerDisabled"
|
||||
@click="showResetServerModal"
|
||||
>
|
||||
<RotateCounterClockwiseIcon class="size-5" />
|
||||
{{ formatMessage(commonMessages.resetServerButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<span class="text-primary">
|
||||
{{ formatMessage(messages.resetServerDescription) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #extra-modals>
|
||||
<Teleport to="body">
|
||||
<div class="relative z-[100]">
|
||||
@@ -121,15 +97,6 @@ const uploadProgressModal =
|
||||
useTemplateRef<InstanceType<typeof UploadProgressModal>>('uploadProgressModal')
|
||||
|
||||
const messages = defineMessages({
|
||||
resetServerTitle: {
|
||||
id: 'hosting.loader.reset-server',
|
||||
defaultMessage: 'Reset server',
|
||||
},
|
||||
resetServerDescription: {
|
||||
id: 'hosting.loader.reset-server-description',
|
||||
defaultMessage:
|
||||
'Removes all data on your server, including your worlds, mods, and configuration files. Backups will remain and can be restored.',
|
||||
},
|
||||
loaderVersionLabel: {
|
||||
id: 'hosting.loader.loader-version',
|
||||
defaultMessage: '{loader, select, null {Loader} other {{loader}}} version',
|
||||
@@ -152,11 +119,11 @@ const messages = defineMessages({
|
||||
},
|
||||
repairStartedText: {
|
||||
id: 'hosting.loader.repair-started-text',
|
||||
defaultMessage: 'Your server installation has been repaired.',
|
||||
defaultMessage: 'Your instance installation has been repaired.',
|
||||
},
|
||||
failedToRepair: {
|
||||
id: 'hosting.loader.failed-to-repair',
|
||||
defaultMessage: 'Failed to repair server',
|
||||
defaultMessage: 'Failed to repair instance',
|
||||
},
|
||||
failedToReinstall: {
|
||||
id: 'hosting.loader.failed-to-reinstall',
|
||||
@@ -181,19 +148,19 @@ const messages = defineMessages({
|
||||
resetToOnboardingModalDescription: {
|
||||
id: 'hosting.loader.reset-to-onboarding-modal-description',
|
||||
defaultMessage:
|
||||
'This will send the server back into onboarding so setup can be completed again. Are you sure you want to continue?',
|
||||
'This will send the instance back into onboarding so setup can be completed again. Are you sure you want to continue?',
|
||||
},
|
||||
resetToOnboardingSuccessTitle: {
|
||||
id: 'hosting.loader.reset-to-onboarding-success-title',
|
||||
defaultMessage: 'Server reset to onboarding',
|
||||
defaultMessage: 'Instance reset to onboarding',
|
||||
},
|
||||
resetToOnboardingSuccessDescription: {
|
||||
id: 'hosting.loader.reset-to-onboarding-success-description',
|
||||
defaultMessage: 'The server has been returned to the onboarding flow.',
|
||||
defaultMessage: 'The instance has been returned to the onboarding flow.',
|
||||
},
|
||||
failedToResetToOnboarding: {
|
||||
id: 'hosting.loader.failed-to-reset-to-onboarding',
|
||||
defaultMessage: 'Failed to reset server to onboarding',
|
||||
defaultMessage: 'Failed to reset instance to onboarding',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -221,10 +188,6 @@ const setupActionDisabledMessage = computed(() => {
|
||||
return busyReasons.value.length > 0 ? formatMessage(busyReasons.value[0].reason) : null
|
||||
})
|
||||
const resetServerDisabled = computed(() => !canResetServer.value || isInstalling.value)
|
||||
const resetServerDisabledTooltip = computed(() => {
|
||||
if (!canResetServer.value) return permissionDeniedMessage.value
|
||||
return busyReasons.value.length > 0 ? formatMessage(busyReasons.value[0].reason) : undefined
|
||||
})
|
||||
const installationSettingsLayout = ref<InstanceType<typeof InstallationSettingsLayout>>()
|
||||
const setupModal = ref<InstanceType<typeof ServerSetupModal>>()
|
||||
const contentListQueryKey = computed(() => ['content', 'list', 'v1', serverId, worldId.value])
|
||||
@@ -268,8 +231,24 @@ const modpackVersionsQuery = useQuery({
|
||||
|
||||
const isSiteAdmin = computed(() => serverSettings.currentUserRole.value === 'admin')
|
||||
|
||||
const editingPlatform = ref(server.value?.loader?.toLowerCase() ?? 'vanilla')
|
||||
const editingGameVersion = ref(server.value?.mc_version ?? '')
|
||||
function normalizeLoader(loader?: string | null) {
|
||||
const normalized = loader?.toLowerCase()
|
||||
if (!normalized) return 'vanilla'
|
||||
if (normalized === 'neo_forge') return 'neoforge'
|
||||
return normalized
|
||||
}
|
||||
|
||||
const currentPlatform = computed(() =>
|
||||
normalizeLoader(addonsQuery.data.value?.modloader ?? server.value?.loader),
|
||||
)
|
||||
const currentGameVersion = computed(
|
||||
() => addonsQuery.data.value?.game_version ?? server.value?.mc_version ?? '',
|
||||
)
|
||||
const currentLoaderVersion = computed(
|
||||
() => addonsQuery.data.value?.modloader_version ?? server.value?.loader_version ?? '',
|
||||
)
|
||||
const editingPlatform = ref(currentPlatform.value)
|
||||
const editingGameVersion = ref(currentGameVersion.value)
|
||||
const resetToOnboardingModal = ref<InstanceType<typeof ConfirmModal>>()
|
||||
const isResettingToOnboarding = ref(false)
|
||||
const supportResetToOnboardingDisabled = computed(
|
||||
@@ -456,9 +435,9 @@ provideInstallationSettings({
|
||||
: undefined,
|
||||
}
|
||||
}),
|
||||
currentPlatform: computed(() => server.value?.loader?.toLowerCase() ?? 'vanilla'),
|
||||
currentGameVersion: computed(() => server.value?.mc_version ?? ''),
|
||||
currentLoaderVersion: computed(() => server.value?.loader_version ?? ''),
|
||||
currentPlatform,
|
||||
currentGameVersion,
|
||||
currentLoaderVersion,
|
||||
availablePlatforms: ['vanilla', 'fabric', 'neoforge', 'forge', 'quilt', 'paper', 'purpur'],
|
||||
|
||||
editingPlatformRef: editingPlatform,
|
||||
@@ -540,11 +519,10 @@ provideInstallationSettings({
|
||||
async save(platform, gameVersion, loaderVersionId) {
|
||||
if (setupActionDisabled.value) return
|
||||
debug('save: called with', { platform, gameVersion, loaderVersionId })
|
||||
const currentPlatform = server.value?.loader?.toLowerCase() ?? 'vanilla'
|
||||
const platformChanged = platform !== currentPlatform
|
||||
const gameVersionChanged = gameVersion !== (server.value?.mc_version ?? '')
|
||||
const platformChanged = platform !== currentPlatform.value
|
||||
const gameVersionChanged = gameVersion !== currentGameVersion.value
|
||||
const loaderVersionChanged =
|
||||
loaderVersionId !== null && loaderVersionId !== (server.value?.loader_version ?? '')
|
||||
loaderVersionId !== null && loaderVersionId !== currentLoaderVersion.value
|
||||
|
||||
let resolvedLoaderVersion = loaderVersionId
|
||||
if (!resolvedLoaderVersion && platform !== 'vanilla') {
|
||||
@@ -776,10 +754,12 @@ provideInstallationSettings({
|
||||
currentLoader: addonsQuery.data.value?.modloader ?? server.value?.loader ?? '',
|
||||
})),
|
||||
|
||||
isServer: true,
|
||||
isServer: false,
|
||||
isApp: serverSettings.isApp.value,
|
||||
showModpackVersionActions: computed(() => modpack.value?.spec.platform === 'modrinth'),
|
||||
isLocalFile: computed(() => modpack.value?.spec.platform === 'local_file'),
|
||||
showBackupCreator: true,
|
||||
repairDescriptionKind: 'server-instance',
|
||||
|
||||
lockPlatform: false,
|
||||
hideLoaderVersion: false,
|
||||
@@ -896,8 +876,21 @@ watch(
|
||||
})
|
||||
if (oldStatus === 'installing' && newStatus === 'available') {
|
||||
debug('status installing->available, resetting editing refs')
|
||||
editingPlatform.value = server.value?.loader?.toLowerCase() ?? 'vanilla'
|
||||
editingGameVersion.value = server.value?.mc_version ?? ''
|
||||
editingPlatform.value = currentPlatform.value
|
||||
editingGameVersion.value = currentGameVersion.value
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
[worldId, currentPlatform, currentGameVersion],
|
||||
([, newPlatform, newGameVersion], [, oldPlatform, oldGameVersion]) => {
|
||||
if (
|
||||
editingPlatform.value === oldPlatform &&
|
||||
editingGameVersion.value === oldGameVersion
|
||||
) {
|
||||
editingPlatform.value = newPlatform
|
||||
editingGameVersion.value = newGameVersion
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<div class="relative h-full w-full">
|
||||
<div class="flex h-full w-full flex-col gap-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="flex h-10 flex-col items-end justify-between gap-4 sm:flex-row">
|
||||
<label for="startup-command-field" class="mb-0.5 flex flex-col gap-2">
|
||||
<span class="text-lg font-semibold text-contrast">Startup command</span>
|
||||
</label>
|
||||
<ButtonStyled v-if="startupCommand !== defaultStartupCommand" type="transparent">
|
||||
<button
|
||||
:disabled="isStartupLoading || startupCommand === defaultStartupCommand"
|
||||
class="relative !w-full sm:!w-auto"
|
||||
@click="resetToDefault"
|
||||
>
|
||||
<UpdatedIcon class="h-5 w-5" />
|
||||
Default
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<StyledInput
|
||||
id="startup-command-field"
|
||||
v-model="startupCommand"
|
||||
multiline
|
||||
resize="vertical"
|
||||
input-class="font-mono field-sizing-content"
|
||||
:disabled="isStartupLoading"
|
||||
/>
|
||||
<div
|
||||
v-if="isStartupLoading"
|
||||
class="bg-bg/50 absolute inset-0 flex items-center justify-center rounded-xl"
|
||||
>
|
||||
<SpinnerIcon class="h-6 w-6 animate-spin text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
<span>The command that runs when your instance is started.</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-lg font-semibold text-contrast">Java version</span>
|
||||
</div>
|
||||
<div class="relative max-w-xs">
|
||||
<Combobox
|
||||
:id="'java-version-field'"
|
||||
v-model="javaVersion"
|
||||
name="java-version"
|
||||
:options="displayedJavaVersions"
|
||||
:display-value="javaVersionLabel ?? 'Java Version'"
|
||||
:disabled="isStartupLoading"
|
||||
>
|
||||
<template #dropdown-footer>
|
||||
<button
|
||||
class="flex w-full cursor-pointer items-center justify-center gap-1.5 border-0 border-t border-solid border-surface-5 bg-transparent py-3 text-center text-sm font-semibold text-secondary transition-colors hover:text-contrast"
|
||||
@mousedown.prevent
|
||||
@click="showAllVersions = !showAllVersions"
|
||||
>
|
||||
<EyeOffIcon v-if="showAllVersions" class="size-4" />
|
||||
<EyeIcon v-else class="size-4" />
|
||||
{{ showAllVersions ? 'Hide extra versions' : 'Show all versions' }}
|
||||
</button>
|
||||
</template>
|
||||
</Combobox>
|
||||
<div
|
||||
v-if="isStartupLoading"
|
||||
class="bg-bg/50 absolute inset-0 flex items-center justify-center rounded-xl"
|
||||
>
|
||||
<SpinnerIcon class="h-5 w-5 animate-spin text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
The Java version your instance runs on. By default, only versions compatible with
|
||||
your Minecraft version are shown.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-lg font-semibold text-contrast">Java runtime</span>
|
||||
</div>
|
||||
<div class="relative max-w-xs">
|
||||
<Combobox
|
||||
:id="'runtime-field'"
|
||||
v-model="jreVendor"
|
||||
name="runtime"
|
||||
:options="JRE_VENDORS"
|
||||
:display-value="jreVendorLabel ?? 'Runtime'"
|
||||
:disabled="isStartupLoading"
|
||||
/>
|
||||
<div
|
||||
v-if="isStartupLoading"
|
||||
class="bg-bg/50 absolute inset-0 flex items-center justify-center rounded-xl"
|
||||
>
|
||||
<SpinnerIcon class="h-5 w-5 animate-spin text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
<span>The Java runtime your instance will use.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SaveBanner
|
||||
:is-visible="!!hasUnsavedChanges || isPending"
|
||||
:server-id="serverId"
|
||||
:is-updating="isPending"
|
||||
:save="() => saveStartup()"
|
||||
:reset="resetStartup"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Archon } from '@modrinth/api-client'
|
||||
import { EyeIcon, EyeOffIcon, SpinnerIcon, UpdatedIcon } from '@modrinth/assets'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { ButtonStyled, Combobox, StyledInput } from '#ui/components'
|
||||
import SaveBanner from '#ui/components/servers/SaveBanner.vue'
|
||||
import {
|
||||
injectModrinthClient,
|
||||
injectModrinthServerContext,
|
||||
injectNotificationManager,
|
||||
} from '#ui/providers'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { server, serverId, worldId } = injectModrinthServerContext()
|
||||
const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const startupQueryKey = computed(() => ['servers', 'startup', 'v1', serverId, worldId.value])
|
||||
|
||||
const { data: startupData, isLoading: isStartupLoading } = useQuery({
|
||||
queryKey: startupQueryKey,
|
||||
queryFn: () => client.archon.options_v1.getStartup(serverId, worldId.value!),
|
||||
enabled: computed(() => worldId.value !== null),
|
||||
})
|
||||
|
||||
const JAVA_VERSIONS = [
|
||||
{ value: 8, label: 'Java 8' },
|
||||
{ value: 11, label: 'Java 11' },
|
||||
{ value: 17, label: 'Java 17' },
|
||||
{ value: 21, label: 'Java 21' },
|
||||
{ value: 25, label: 'Java 25' },
|
||||
]
|
||||
|
||||
const showAllVersions = ref(false)
|
||||
|
||||
type MinecraftReleaseVersion = {
|
||||
major: number
|
||||
minor: number
|
||||
}
|
||||
|
||||
function parseMinecraftReleaseVersion(version: string): MinecraftReleaseVersion | null {
|
||||
const [majorPart, minorPart] = version.split('.')
|
||||
|
||||
if (!majorPart || !minorPart) return null
|
||||
|
||||
const major = Number(majorPart)
|
||||
const minor = Number(minorPart)
|
||||
|
||||
if (!Number.isInteger(major) || !Number.isInteger(minor)) return null
|
||||
|
||||
return { major, minor }
|
||||
}
|
||||
|
||||
function filterJavaVersions(compatibleVersions: number[]) {
|
||||
return JAVA_VERSIONS.filter((version) => compatibleVersions.includes(version.value))
|
||||
}
|
||||
|
||||
const displayedJavaVersions = computed(() => {
|
||||
if (showAllVersions.value) return JAVA_VERSIONS
|
||||
|
||||
// TODO: Use the selected instance's content Minecraft version instead of the server fallback.
|
||||
const mcVersion = server.value?.mc_version ?? ''
|
||||
if (!mcVersion) return JAVA_VERSIONS
|
||||
|
||||
const releaseVersion = parseMinecraftReleaseVersion(mcVersion)
|
||||
if (!releaseVersion) return JAVA_VERSIONS
|
||||
|
||||
if (releaseVersion.major > 1) {
|
||||
if (releaseVersion.major >= 26) {
|
||||
return filterJavaVersions([25])
|
||||
}
|
||||
|
||||
return JAVA_VERSIONS
|
||||
}
|
||||
|
||||
if (releaseVersion.minor >= 20) return filterJavaVersions([21])
|
||||
if (releaseVersion.minor >= 17) return filterJavaVersions([17, 21])
|
||||
if (releaseVersion.minor >= 12) return filterJavaVersions([8, 11, 17, 21])
|
||||
if (releaseVersion.minor >= 6) return filterJavaVersions([8, 11])
|
||||
return filterJavaVersions([8])
|
||||
})
|
||||
|
||||
const JRE_VENDORS: { value: Archon.Content.v1.JreVendor; label: string }[] = [
|
||||
{ value: 'corretto', label: 'Corretto' },
|
||||
{ value: 'temurin', label: 'Temurin' },
|
||||
{ value: 'graal', label: 'GraalVM' },
|
||||
]
|
||||
|
||||
const savedStartupCommand = computed(() => startupData.value?.startup_command ?? '')
|
||||
const savedJavaVersion = computed(() => startupData.value?.java_version ?? undefined)
|
||||
const savedJreVendor = computed(() => startupData.value?.jre_vendor ?? undefined)
|
||||
const defaultStartupCommand = computed(
|
||||
() => startupData.value?.original_invocation ?? savedStartupCommand.value,
|
||||
)
|
||||
|
||||
const startupCommand = ref('')
|
||||
const javaVersion = ref<number>()
|
||||
const jreVendor = ref<Archon.Content.v1.JreVendor>()
|
||||
|
||||
const javaVersionLabel = computed(
|
||||
() => JAVA_VERSIONS.find((v) => v.value === javaVersion.value)?.label,
|
||||
)
|
||||
const jreVendorLabel = computed(() => JRE_VENDORS.find((v) => v.value === jreVendor.value)?.label)
|
||||
|
||||
function syncFormFromData() {
|
||||
startupCommand.value = savedStartupCommand.value
|
||||
javaVersion.value = savedJavaVersion.value
|
||||
jreVendor.value = savedJreVendor.value
|
||||
}
|
||||
|
||||
watch(
|
||||
startupData,
|
||||
(newData, oldData) => {
|
||||
if (newData && !oldData) {
|
||||
syncFormFromData()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const hasUnsavedChanges = computed(
|
||||
() =>
|
||||
startupCommand.value !== savedStartupCommand.value ||
|
||||
javaVersion.value !== savedJavaVersion.value ||
|
||||
jreVendor.value !== savedJreVendor.value,
|
||||
)
|
||||
|
||||
const { mutate: saveStartup, isPending } = useMutation({
|
||||
mutationFn: () =>
|
||||
client.archon.options_v1.patchStartup(serverId, worldId.value!, {
|
||||
startup_command: startupCommand.value || null,
|
||||
java_version: javaVersion.value ?? null,
|
||||
jre_vendor: jreVendor.value ?? null,
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: startupQueryKey.value })
|
||||
syncFormFromData()
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: 'Instance settings updated',
|
||||
text: 'Your instance settings were successfully changed.',
|
||||
})
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error)
|
||||
addNotification({
|
||||
type: 'error',
|
||||
title: 'Failed to update instance arguments',
|
||||
text: 'Please try again later.',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
function resetStartup() {
|
||||
syncFormFromData()
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
startupCommand.value = defaultStartupCommand.value
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="flex h-full w-full flex-col gap-6">
|
||||
<Teleport to="body">
|
||||
<div class="relative z-[100]">
|
||||
<ServerSetupModal
|
||||
ref="setupModal"
|
||||
@reinstall="onResetEverything"
|
||||
@browse-modpacks="onBrowseModpacks"
|
||||
/>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<div class="flex max-w-[496px] flex-col gap-2.5">
|
||||
<label for="instance-name-field" class="font-semibold text-contrast">
|
||||
{{ formatMessage(messages.instanceNameLabel) }}
|
||||
</label>
|
||||
<div v-tooltip="formatMessage(messages.notImplemented)" class="max-w-[400px]">
|
||||
<StyledInput
|
||||
id="instance-name-field"
|
||||
:model-value="instanceName"
|
||||
disabled
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-primary">{{ formatMessage(messages.instanceNameDescription) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ formatMessage(messages.dangerZoneLabel) }}
|
||||
</span>
|
||||
<div class="flex flex-col gap-4 rounded-2xl border border-solid border-surface-5 p-4">
|
||||
<div class="flex flex-col items-start gap-2.5">
|
||||
<div v-tooltip="formatMessage(messages.notImplemented)">
|
||||
<ButtonStyled color="red">
|
||||
<button class="!shadow-none" disabled>
|
||||
<RotateCounterClockwiseIcon class="size-5" />
|
||||
{{ formatMessage(messages.resetWorldFilesButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<span class="text-primary">
|
||||
{{ formatMessage(messages.resetWorldFilesDescription) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-start gap-2.5">
|
||||
<ButtonStyled color="red">
|
||||
<button class="!shadow-none" :disabled="isResetDisabled" @click="setupModal?.show()">
|
||||
<TrashIcon class="size-5" />
|
||||
{{ formatMessage(messages.resetEverythingButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<span class="text-primary">
|
||||
{{ formatMessage(messages.resetEverythingDescription) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RotateCounterClockwiseIcon, TrashIcon } from '@modrinth/assets'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { ButtonStyled, StyledInput } from '#ui/components'
|
||||
import ServerSetupModal from '#ui/components/servers/ServerSetupModal.vue'
|
||||
import { useModrinthServersConsole } from '#ui/composables'
|
||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
import { injectServerSettings } from '#ui/layouts/shared/server-settings'
|
||||
import { injectModrinthClient, injectModrinthServerContext } from '#ui/providers'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const client = injectModrinthClient()
|
||||
const { server, serverId, worldId, isSyncingContent, busyReasons } = injectModrinthServerContext()
|
||||
const serverSettings = injectServerSettings()
|
||||
const queryClient = useQueryClient()
|
||||
const modrinthServersConsole = useModrinthServersConsole()
|
||||
|
||||
const setupModal = ref<InstanceType<typeof ServerSetupModal>>()
|
||||
|
||||
const messages = defineMessages({
|
||||
instanceNameLabel: {
|
||||
id: 'server.instance-settings.general.instance-name.label',
|
||||
defaultMessage: 'Instance name',
|
||||
},
|
||||
instanceNameDescription: {
|
||||
id: 'server.instance-settings.general.instance-name.description',
|
||||
defaultMessage: 'This name is only visible on Modrinth.',
|
||||
},
|
||||
instanceFallbackName: {
|
||||
id: 'server.instance-settings.general.instance-name.fallback',
|
||||
defaultMessage: 'Instance',
|
||||
},
|
||||
dangerZoneLabel: {
|
||||
id: 'server.instance-settings.general.danger-zone.label',
|
||||
defaultMessage: 'Danger zone',
|
||||
},
|
||||
resetWorldFilesButton: {
|
||||
id: 'server.instance-settings.general.reset-world-files.button',
|
||||
defaultMessage: 'Reset world data',
|
||||
},
|
||||
resetWorldFilesDescription: {
|
||||
id: 'server.instance-settings.general.reset-world-files.description',
|
||||
defaultMessage:
|
||||
'Delete the current world data and generate a new one. Your content and files will stay the same.',
|
||||
},
|
||||
resetEverythingButton: {
|
||||
id: 'server.instance-settings.general.reset-everything.button',
|
||||
defaultMessage: 'Reset everything',
|
||||
},
|
||||
resetEverythingDescription: {
|
||||
id: 'server.instance-settings.general.reset-everything.description',
|
||||
defaultMessage:
|
||||
'Reset your instance completely. This removes world data, content, and any configuration. A backup of the previous instance will remain available.',
|
||||
},
|
||||
notImplemented: {
|
||||
id: 'server.instance-settings.general.not-implemented',
|
||||
defaultMessage: 'Not yet implemented',
|
||||
},
|
||||
})
|
||||
|
||||
const serverFullQuery = useQuery({
|
||||
queryKey: ['servers', 'v1', 'detail', serverId],
|
||||
queryFn: () => client.archon.servers_v1.get(serverId),
|
||||
staleTime: 30_000,
|
||||
})
|
||||
|
||||
const currentWorld = computed(() => {
|
||||
const id = worldId.value
|
||||
if (!id) return null
|
||||
return serverFullQuery.data.value?.worlds.find((world) => world.id === id) ?? null
|
||||
})
|
||||
|
||||
const instanceName = computed(
|
||||
() => currentWorld.value?.name ?? formatMessage(messages.instanceFallbackName),
|
||||
)
|
||||
|
||||
const isResetDisabled = computed(
|
||||
() =>
|
||||
!worldId.value ||
|
||||
server.value?.status === 'installing' ||
|
||||
isSyncingContent.value ||
|
||||
busyReasons.value.length > 0,
|
||||
)
|
||||
|
||||
function onResetEverything() {
|
||||
modrinthServersConsole.clear()
|
||||
queryClient.removeQueries({ queryKey: ['servers', 'ws-state', serverId] })
|
||||
void Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ['servers', 'detail', serverId] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['servers', 'v1', 'detail', serverId] }),
|
||||
worldId.value
|
||||
? queryClient.invalidateQueries({
|
||||
queryKey: ['content', 'list', 'v1', serverId, worldId.value],
|
||||
})
|
||||
: Promise.resolve(),
|
||||
])
|
||||
serverSettings.closeModal?.()
|
||||
}
|
||||
|
||||
function onBrowseModpacks() {
|
||||
serverSettings.browseModpacks({
|
||||
serverId,
|
||||
worldId: worldId.value,
|
||||
from: 'reset-server',
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -10,14 +10,11 @@ import {
|
||||
} from '@modrinth/assets'
|
||||
import type { Component } from 'vue'
|
||||
|
||||
export type ServerSettingsTabId =
|
||||
| 'general'
|
||||
| 'installation'
|
||||
| 'network'
|
||||
| 'properties'
|
||||
| 'advanced'
|
||||
| 'billing'
|
||||
| 'admin-billing'
|
||||
export type ServerSettingsTabId = 'general' | 'network' | 'advanced' | 'billing' | 'admin-billing'
|
||||
|
||||
export type ServerInstanceSettingsTabId = 'general' | 'installation' | 'properties' | 'advanced'
|
||||
|
||||
export type AnyServerSettingsTabId = ServerSettingsTabId | ServerInstanceSettingsTabId
|
||||
|
||||
export interface ServerSettingsTabContext {
|
||||
serverId: string
|
||||
@@ -36,28 +33,24 @@ export interface ServerSettingsTabDefinition {
|
||||
shown?: (ctx: ServerSettingsTabContext) => boolean
|
||||
}
|
||||
|
||||
export interface ServerInstanceSettingsTabDefinition {
|
||||
id: ServerInstanceSettingsTabId
|
||||
label: string
|
||||
icon: Component
|
||||
shown?: (ctx: ServerSettingsTabContext) => boolean
|
||||
}
|
||||
|
||||
export const serverSettingsTabDefinitions: ServerSettingsTabDefinition[] = [
|
||||
{
|
||||
id: 'general',
|
||||
label: 'General',
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
id: 'installation',
|
||||
label: 'Installation',
|
||||
icon: WrenchIcon,
|
||||
},
|
||||
{
|
||||
id: 'network',
|
||||
label: 'Network',
|
||||
icon: VersionIcon,
|
||||
},
|
||||
{
|
||||
id: 'properties',
|
||||
label: 'Properties',
|
||||
icon: ListIcon,
|
||||
shown: ({ serverStatus }) => serverStatus !== 'installing',
|
||||
},
|
||||
{
|
||||
id: 'advanced',
|
||||
label: 'Advanced',
|
||||
@@ -80,3 +73,27 @@ export const serverSettingsTabDefinitions: ServerSettingsTabDefinition[] = [
|
||||
shown: ({ isAdmin }) => isAdmin,
|
||||
},
|
||||
]
|
||||
|
||||
export const serverInstanceSettingsTabDefinitions: ServerInstanceSettingsTabDefinition[] = [
|
||||
{
|
||||
id: 'general',
|
||||
label: 'General',
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
id: 'installation',
|
||||
label: 'Installation',
|
||||
icon: WrenchIcon,
|
||||
},
|
||||
{
|
||||
id: 'properties',
|
||||
label: 'Properties',
|
||||
icon: ListIcon,
|
||||
shown: ({ serverStatus }) => serverStatus !== 'installing',
|
||||
},
|
||||
{
|
||||
id: 'advanced',
|
||||
label: 'Advanced',
|
||||
icon: TextQuoteIcon,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -221,7 +221,7 @@
|
||||
<ButtonStyled color="red" type="standard">
|
||||
<button
|
||||
class="whitespace-pre"
|
||||
@click="openServerSettingsModal('installation')"
|
||||
@click="openServerInstanceSettingsModal('installation')"
|
||||
>
|
||||
<RightArrowIcon />
|
||||
{{ formatMessage(messages.changeLoader) }}
|
||||
@@ -289,6 +289,13 @@
|
||||
:browse-modpacks="handleBrowseModpacks"
|
||||
/>
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<ServerInstanceSettingsModal
|
||||
ref="serverInstanceSettingsModal"
|
||||
:resolve-viewer="resolveViewer"
|
||||
:browse-modpacks="handleBrowseModpacks"
|
||||
/>
|
||||
</Suspense>
|
||||
<ConfirmLeaveModal
|
||||
ref="confirmLeaveModal"
|
||||
:header="formatMessage(leaveMessages.uploadInProgress)"
|
||||
@@ -328,6 +335,7 @@ 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 { ServerManageHeader } from '#ui/components/servers/server-header'
|
||||
import ServerInstanceSettingsModal from '#ui/components/servers/ServerInstanceSettingsModal.vue'
|
||||
import ServerSettingsModal from '#ui/components/servers/ServerSettingsModal.vue'
|
||||
import {
|
||||
hasServerPermission,
|
||||
@@ -343,7 +351,10 @@ import { useServerPanelSync } from '#ui/composables/server-panel-sync'
|
||||
import { useServerBackupsQueue } from '#ui/composables/servers/server-backups-queue.ts'
|
||||
import { useServerManageCoreRuntime } from '#ui/composables/servers/server-manage-core-runtime.ts'
|
||||
import type { LogLine } from '#ui/layouts/shared/console'
|
||||
import type { ServerSettingsTabId } from '#ui/layouts/shared/server-settings'
|
||||
import type {
|
||||
ServerInstanceSettingsTabId,
|
||||
ServerSettingsTabId,
|
||||
} from '#ui/layouts/shared/server-settings'
|
||||
import {
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
@@ -714,6 +725,9 @@ function dismissInstancesHint() {
|
||||
}
|
||||
|
||||
const serverSettingsModal = ref<InstanceType<typeof ServerSettingsModal> | null>(null)
|
||||
const serverInstanceSettingsModal = ref<InstanceType<typeof ServerInstanceSettingsModal> | null>(
|
||||
null,
|
||||
)
|
||||
const confirmLeaveModal = ref<InstanceType<typeof ConfirmLeaveModal>>()
|
||||
|
||||
const {
|
||||
@@ -1490,6 +1504,18 @@ function openServerSettingsModal(tabId?: ServerSettingsTabId) {
|
||||
serverSettingsModal.value?.show({ serverId: props.serverId, tabId })
|
||||
}
|
||||
|
||||
function openServerInstanceSettingsModal(
|
||||
tabId?: ServerInstanceSettingsTabId,
|
||||
targetWorldId?: string | null,
|
||||
) {
|
||||
if (!props.serverId) return
|
||||
serverInstanceSettingsModal.value?.show({
|
||||
serverId: props.serverId,
|
||||
tabId,
|
||||
worldId: targetWorldId,
|
||||
})
|
||||
}
|
||||
|
||||
function handleBrowseModpacks(args: {
|
||||
serverId: string
|
||||
worldId: string | null
|
||||
@@ -1508,6 +1534,8 @@ function handleBrowseContent(args: {
|
||||
|
||||
provideServerSettingsModal({
|
||||
openServerSettings: (options) => openServerSettingsModal(options?.tabId),
|
||||
openServerInstanceSettings: (options) =>
|
||||
openServerInstanceSettingsModal(options?.tabId, options?.worldId),
|
||||
browseServerContent: (args) => handleBrowseContent(args),
|
||||
})
|
||||
|
||||
@@ -1675,12 +1703,23 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
if (route.query.openSettings) {
|
||||
const tabId = route.query.openSettings as ServerSettingsTabId
|
||||
const tabId =
|
||||
typeof route.query.openSettings === 'string' ? route.query.openSettings : undefined
|
||||
router.replace({ query: { ...route.query, openSettings: undefined } })
|
||||
queryClient.invalidateQueries({ queryKey: ['servers', 'detail', props.serverId] })
|
||||
queryClient.invalidateQueries({ queryKey: ['content', 'list', 'v1', props.serverId] })
|
||||
queryClient.invalidateQueries({ queryKey: ['servers', 'startup', 'v1', props.serverId] })
|
||||
nextTick(() => openServerSettingsModal(tabId))
|
||||
if (tabId === 'installation' || tabId === 'properties') {
|
||||
nextTick(() => openServerInstanceSettingsModal(tabId))
|
||||
} else if (
|
||||
tabId === 'general' ||
|
||||
tabId === 'network' ||
|
||||
tabId === 'advanced' ||
|
||||
tabId === 'billing' ||
|
||||
tabId === 'admin-billing'
|
||||
) {
|
||||
nextTick(() => openServerSettingsModal(tabId))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
+1
-1
@@ -284,9 +284,9 @@ import BackupDeleteModal from '#ui/components/servers/backups/BackupDeleteModal.
|
||||
import BackupItem from '#ui/components/servers/backups/BackupItem.vue'
|
||||
import BackupRenameModal from '#ui/components/servers/backups/BackupRenameModal.vue'
|
||||
import BackupRestoreModal from '#ui/components/servers/backups/BackupRestoreModal.vue'
|
||||
import { useBackupsSelection } from '#ui/composables/servers/backups-selection'
|
||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
import { useServerPermissions } from '#ui/composables/server-permissions'
|
||||
import { useBackupsSelection } from '#ui/composables/servers/backups-selection'
|
||||
import { useServerBackupsQueue } from '#ui/composables/servers/server-backups-queue.ts'
|
||||
import { useBulkOperation } from '#ui/layouts/shared/content-tab/composables/bulk-operations'
|
||||
import {
|
||||
|
||||
+6
-5
@@ -124,7 +124,7 @@ const contentUploadSession = useUploadSessionUpload({
|
||||
cancelUpload,
|
||||
})
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { openServerSettings, browseServerContent } = injectServerSettingsModal()
|
||||
const { openServerInstanceSettings, browseServerContent } = injectServerSettingsModal()
|
||||
const { canSetup, permissionDeniedMessage } = useServerPermissions()
|
||||
const router = useRouter()
|
||||
const queryClient = useQueryClient()
|
||||
@@ -1345,14 +1345,14 @@ provideContentManager({
|
||||
},
|
||||
browse: handleBrowseContent,
|
||||
uploadFiles: handleUploadFiles,
|
||||
deletionContext: 'server',
|
||||
deletionContext: 'instance',
|
||||
hasUpdateSupport: true,
|
||||
updateItem: handleUpdateItem,
|
||||
bulkUpdateItems: handleBulkUpdate,
|
||||
updateModpack: handleModpackUpdate,
|
||||
viewModpackContent: handleViewModpackContent,
|
||||
unlinkModpack: handleModpackUnlink,
|
||||
openSettings: () => openServerSettings({ tabId: 'installation' }),
|
||||
openSettings: () => openServerInstanceSettings({ tabId: 'installation' }),
|
||||
switchVersion: handleSwitchVersion,
|
||||
getOverflowOptions,
|
||||
getItemId: getContentItemId,
|
||||
@@ -1386,9 +1386,9 @@ provideContentManager({
|
||||
<template #modals>
|
||||
<ConfirmUnlinkModal
|
||||
ref="modpackUnlinkModal"
|
||||
server
|
||||
:action-disabled="setupActionDisabled"
|
||||
:action-disabled-tooltip="setupActionBusyMessage ?? undefined"
|
||||
target-type="instance"
|
||||
@unlink="handleModpackUnlinkConfirm"
|
||||
/>
|
||||
<ModpackContentModal
|
||||
@@ -1430,6 +1430,7 @@ provideContentManager({
|
||||
:loading-changelog="loadingChangelog"
|
||||
:action-disabled="setupActionDisabled"
|
||||
:action-disabled-tooltip="setupActionBusyMessage ?? undefined"
|
||||
target-type="instance"
|
||||
@update="handleModalUpdate"
|
||||
@cancel="resetUpdateState"
|
||||
@version-select="handleVersionSelect"
|
||||
@@ -1446,9 +1447,9 @@ provideContentManager({
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
"
|
||||
server
|
||||
:action-disabled="setupActionDisabled"
|
||||
:action-disabled-tooltip="setupActionBusyMessage ?? undefined"
|
||||
target-type="instance"
|
||||
@confirm="handleModpackUpdateConfirm"
|
||||
@cancel="handleModpackUpdateCancel"
|
||||
/>
|
||||
|
||||
+2
-2
@@ -88,7 +88,7 @@ const messages = defineMessages({
|
||||
|
||||
const client = injectModrinthClient()
|
||||
const { serverId, server, worldId, isServerRunning } = injectModrinthServerContext()
|
||||
const { openServerSettings } = injectServerSettingsModal()
|
||||
const { openServerInstanceSettings } = injectServerSettingsModal()
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const router = useRouter()
|
||||
@@ -253,7 +253,7 @@ const headerActions = computed(() => [
|
||||
icon: Settings2Icon,
|
||||
labelHidden: true,
|
||||
tooltip: formatMessage(messages.instanceSettings),
|
||||
onClick: () => openServerSettings({ tabId: 'installation' }),
|
||||
onClick: () => openServerInstanceSettings({ tabId: 'general' }),
|
||||
},
|
||||
])
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ const INSTANCE_INFO_ADMONITION_KEY = 'server-instances-info-admonition-dismissed
|
||||
|
||||
const client = injectModrinthClient()
|
||||
const { serverId, server, isServerRunning } = injectModrinthServerContext()
|
||||
const { openServerSettings } = injectServerSettingsModal()
|
||||
const { openServerInstanceSettings } = injectServerSettingsModal()
|
||||
const { formatMessage } = useVIntl()
|
||||
const router = useRouter()
|
||||
const instanceInfoAdmonitionDismissed = useStorage(INSTANCE_INFO_ADMONITION_KEY, false)
|
||||
@@ -322,12 +322,12 @@ function handleEditWorld(worldId: string) {
|
||||
)
|
||||
}
|
||||
|
||||
function handleWorldSettings() {
|
||||
openServerSettings({ tabId: 'installation' })
|
||||
function handleWorldSettings(worldId: string) {
|
||||
openServerInstanceSettings({ tabId: 'general', worldId })
|
||||
}
|
||||
|
||||
function handleCreateWorld() {
|
||||
openServerSettings({ tabId: 'installation' })
|
||||
openServerInstanceSettings({ tabId: 'installation' })
|
||||
}
|
||||
|
||||
function dismissInstanceInfoAdmonition() {
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
"affiliate.viewAnalytics": {
|
||||
"defaultMessage": "View analytics"
|
||||
},
|
||||
"app.server-instance-settings.failed-to-load-server": {
|
||||
"defaultMessage": "Failed to load instance settings"
|
||||
},
|
||||
"app.server-settings.failed-to-load-server": {
|
||||
"defaultMessage": "Failed to load server settings"
|
||||
},
|
||||
@@ -345,7 +348,7 @@
|
||||
"defaultMessage": "Update {count, plural, one {# project} other {# projects}}"
|
||||
},
|
||||
"content.confirm-deletion.admonition-body": {
|
||||
"defaultMessage": "Deleting a mod can permanently affect your world and may cause missing content or unexpected issues when it loads again."
|
||||
"defaultMessage": "Deleting a mod can permanently affect your instance and may cause missing content or unexpected issues when it starts again."
|
||||
},
|
||||
"content.confirm-deletion.admonition-header": {
|
||||
"defaultMessage": "Deletion warning"
|
||||
@@ -438,7 +441,7 @@
|
||||
"defaultMessage": "{count} removed"
|
||||
},
|
||||
"content.diff-modal.unknown-content-body": {
|
||||
"defaultMessage": "Some content on your server could not be analyzed and may be affected by this change."
|
||||
"defaultMessage": "Some content on your {type, select, server {server} other {instance}} could not be analyzed and may be affected by this change."
|
||||
},
|
||||
"content.diff-modal.updated-count": {
|
||||
"defaultMessage": "{count} updated"
|
||||
@@ -468,22 +471,16 @@
|
||||
"defaultMessage": "A backup is in progress, it's recommended to wait for it to finish before performing this action."
|
||||
},
|
||||
"content.inline-backup.backup-takes-a-while": {
|
||||
"defaultMessage": "Creating a backup may take several minutes depending on the size of your server."
|
||||
"defaultMessage": "Creating a backup may take several minutes depending on the size of your {type, select, server {server} other {instance}}."
|
||||
},
|
||||
"content.inline-backup.create-backup": {
|
||||
"defaultMessage": "Create backup"
|
||||
},
|
||||
"content.inline-backup.instance-label": {
|
||||
"defaultMessage": "instance"
|
||||
},
|
||||
"content.inline-backup.shift-click-hint": {
|
||||
"defaultMessage": "Hold Shift while clicking to skip confirmation."
|
||||
},
|
||||
"content.inline-backup.warning-body": {
|
||||
"defaultMessage": "We recommend creating a backup before proceeding so you can restore your {type} if anything breaks."
|
||||
},
|
||||
"content.inline-backup.world-label": {
|
||||
"defaultMessage": "world"
|
||||
"defaultMessage": "We recommend creating a backup before proceeding so you can restore your {type, select, server {server} other {instance}} if anything breaks."
|
||||
},
|
||||
"content.modpack-card.content-hint-description": {
|
||||
"defaultMessage": "Your modpack's content can now be found here!"
|
||||
@@ -687,7 +684,7 @@
|
||||
"defaultMessage": "Additional settings"
|
||||
},
|
||||
"creation-flow.modal.final-config.backup.before-reset-server.name": {
|
||||
"defaultMessage": "Before reset server"
|
||||
"defaultMessage": "Before reset instance"
|
||||
},
|
||||
"creation-flow.modal.final-config.difficulty.easy": {
|
||||
"defaultMessage": "Easy"
|
||||
@@ -876,7 +873,7 @@
|
||||
"defaultMessage": "Import instance"
|
||||
},
|
||||
"creation-flow.title.reset-server": {
|
||||
"defaultMessage": "Reset server"
|
||||
"defaultMessage": "Reset instance"
|
||||
},
|
||||
"creation-flow.title.set-up-server": {
|
||||
"defaultMessage": "Set up server"
|
||||
@@ -1692,10 +1689,10 @@
|
||||
"defaultMessage": "Failed to reinstall modpack"
|
||||
},
|
||||
"hosting.loader.failed-to-repair": {
|
||||
"defaultMessage": "Failed to repair server"
|
||||
"defaultMessage": "Failed to repair instance"
|
||||
},
|
||||
"hosting.loader.failed-to-reset-to-onboarding": {
|
||||
"defaultMessage": "Failed to reset server to onboarding"
|
||||
"defaultMessage": "Failed to reset instance to onboarding"
|
||||
},
|
||||
"hosting.loader.failed-to-save-settings": {
|
||||
"defaultMessage": "Failed to save installation settings"
|
||||
@@ -1707,31 +1704,25 @@
|
||||
"defaultMessage": "{loader, select, null {Loader} other {{loader}}} version"
|
||||
},
|
||||
"hosting.loader.repair-started-text": {
|
||||
"defaultMessage": "Your server installation has been repaired."
|
||||
"defaultMessage": "Your instance installation has been repaired."
|
||||
},
|
||||
"hosting.loader.repair-started-title": {
|
||||
"defaultMessage": "Repair completed"
|
||||
},
|
||||
"hosting.loader.reset-server": {
|
||||
"defaultMessage": "Reset server"
|
||||
},
|
||||
"hosting.loader.reset-server-description": {
|
||||
"defaultMessage": "Removes all data on your server, including your worlds, mods, and configuration files. Backups will remain and can be restored."
|
||||
},
|
||||
"hosting.loader.reset-to-onboarding-button": {
|
||||
"defaultMessage": "Reset to onboarding"
|
||||
},
|
||||
"hosting.loader.reset-to-onboarding-modal-description": {
|
||||
"defaultMessage": "This will send the server back into onboarding so setup can be completed again. Are you sure you want to continue?"
|
||||
"defaultMessage": "This will send the instance back into onboarding so setup can be completed again. Are you sure you want to continue?"
|
||||
},
|
||||
"hosting.loader.reset-to-onboarding-modal-title": {
|
||||
"defaultMessage": "Reset to onboarding"
|
||||
},
|
||||
"hosting.loader.reset-to-onboarding-success-description": {
|
||||
"defaultMessage": "The server has been returned to the onboarding flow."
|
||||
"defaultMessage": "The instance has been returned to the onboarding flow."
|
||||
},
|
||||
"hosting.loader.reset-to-onboarding-success-title": {
|
||||
"defaultMessage": "Server reset to onboarding"
|
||||
"defaultMessage": "Instance reset to onboarding"
|
||||
},
|
||||
"hosting.loader.support-options-title": {
|
||||
"defaultMessage": "Support options"
|
||||
@@ -1794,7 +1785,7 @@
|
||||
"defaultMessage": "Confirm"
|
||||
},
|
||||
"installation-settings.confirm-version-change-description": {
|
||||
"defaultMessage": "Changing to {gameVersion} will modify the following content on your server."
|
||||
"defaultMessage": "Changing to {gameVersion} will modify the following content on your {type, select, server {server} other {instance}}."
|
||||
},
|
||||
"installation-settings.confirm-version-change-header": {
|
||||
"defaultMessage": "Review content changes"
|
||||
@@ -1827,11 +1818,14 @@
|
||||
"defaultMessage": "Incompatible content installed"
|
||||
},
|
||||
"installation-settings.incompatible-content.loader-change-body": {
|
||||
"defaultMessage": "When changing the loader, all installed content will be disabled. We recommend resetting your server instead."
|
||||
"defaultMessage": "When changing the loader, all installed content will be disabled. We recommend resetting your {type, select, server {server} other {instance}} instead."
|
||||
},
|
||||
"installation-settings.incompatible-content.loader-change-title": {
|
||||
"defaultMessage": "Changing loaders is destructive"
|
||||
},
|
||||
"installation-settings.incompatible-content.reset-button": {
|
||||
"defaultMessage": "Reset {type, select, server {server} other {instance}}"
|
||||
},
|
||||
"installation-settings.linked-instance.title": {
|
||||
"defaultMessage": "Linked {projectType}"
|
||||
},
|
||||
@@ -1845,7 +1839,7 @@
|
||||
"defaultMessage": "{loader} version"
|
||||
},
|
||||
"installation-settings.platform-lock-tooltip": {
|
||||
"defaultMessage": "You will need to reset your server to switch loader."
|
||||
"defaultMessage": "You will need to reset your {type, select, server {server} other {instance}} to switch loader."
|
||||
},
|
||||
"installation-settings.reinstall-modpack.description": {
|
||||
"defaultMessage": "Re-installing the modpack resets the {type} content to its original state, removing any mods or content you have added."
|
||||
@@ -1868,6 +1862,9 @@
|
||||
"installation-settings.repair.server-description": {
|
||||
"defaultMessage": "Reinstalls the loader and Minecraft dependencies without deleting your content. This may resolve issues if your server is not starting correctly."
|
||||
},
|
||||
"installation-settings.repair.server-instance-description": {
|
||||
"defaultMessage": "Reinstalls the loader and Minecraft dependencies without deleting your content. This may resolve issues if this instance is not starting correctly."
|
||||
},
|
||||
"installation-settings.repair.server-title": {
|
||||
"defaultMessage": "Repair server"
|
||||
},
|
||||
@@ -2075,11 +2072,8 @@
|
||||
"instances.updater-modal.update-to": {
|
||||
"defaultMessage": "Update to {version}"
|
||||
},
|
||||
"instances.updater-modal.warning-app": {
|
||||
"defaultMessage": "Updating can break your instance. Review version changelogs and back up first."
|
||||
},
|
||||
"instances.updater-modal.warning-web": {
|
||||
"defaultMessage": "Updating can break your world. Review version changelogs and back up first."
|
||||
"instances.updater-modal.warning": {
|
||||
"defaultMessage": "Updating can break your {type, select, server {server} other {instance}}. Review version changelogs and back up first."
|
||||
},
|
||||
"label.actions": {
|
||||
"defaultMessage": "Actions"
|
||||
@@ -3896,6 +3890,33 @@
|
||||
"servers.access-table.user-avatar-alt": {
|
||||
"defaultMessage": "{username}'s avatar"
|
||||
},
|
||||
"server.instance-settings.general.danger-zone.label": {
|
||||
"defaultMessage": "Danger zone"
|
||||
},
|
||||
"server.instance-settings.general.instance-name.description": {
|
||||
"defaultMessage": "This name is only visible on Modrinth."
|
||||
},
|
||||
"server.instance-settings.general.instance-name.fallback": {
|
||||
"defaultMessage": "Instance"
|
||||
},
|
||||
"server.instance-settings.general.instance-name.label": {
|
||||
"defaultMessage": "Instance name"
|
||||
},
|
||||
"server.instance-settings.general.not-implemented": {
|
||||
"defaultMessage": "Not yet implemented"
|
||||
},
|
||||
"server.instance-settings.general.reset-everything.button": {
|
||||
"defaultMessage": "Reset everything"
|
||||
},
|
||||
"server.instance-settings.general.reset-everything.description": {
|
||||
"defaultMessage": "Reset your instance completely. This removes world data, content, and any configuration. A backup of the previous instance will remain available."
|
||||
},
|
||||
"server.instance-settings.general.reset-world-files.button": {
|
||||
"defaultMessage": "Reset world data"
|
||||
},
|
||||
"server.instance-settings.general.reset-world-files.description": {
|
||||
"defaultMessage": "Delete the current world data and generate a new one. Your content and files will stay the same."
|
||||
},
|
||||
"servers.admonitions.background-task-running": {
|
||||
"defaultMessage": "Background task running"
|
||||
},
|
||||
@@ -4179,7 +4200,7 @@
|
||||
"defaultMessage": "Backup timed out"
|
||||
},
|
||||
"servers.backups.admonition.creating-backup.description": {
|
||||
"defaultMessage": "Saving world data and server configuration for {backupName}. This can take a few minutes."
|
||||
"defaultMessage": "Saving instance data and server configuration for {backupName}. This can take a few minutes."
|
||||
},
|
||||
"servers.backups.admonition.creating-backup.title": {
|
||||
"defaultMessage": "Creating backup"
|
||||
@@ -4700,8 +4721,8 @@
|
||||
"servers.manage.instances.card.last-active": {
|
||||
"defaultMessage": "Last active"
|
||||
},
|
||||
"servers.manage.instances.card.none": {
|
||||
"defaultMessage": "None"
|
||||
"servers.manage.instances.card.no-modpack": {
|
||||
"defaultMessage": "—"
|
||||
},
|
||||
"servers.manage.instances.card.not-tracked-yet": {
|
||||
"defaultMessage": "Not tracked yet"
|
||||
@@ -4709,17 +4730,14 @@
|
||||
"servers.manage.instances.card.settings": {
|
||||
"defaultMessage": "Instance settings"
|
||||
},
|
||||
"servers.manage.instances.info.definition": {
|
||||
"defaultMessage": "An instance is a separate server setup."
|
||||
"servers.manage.instances.info.body": {
|
||||
"defaultMessage": "An instance is a separate setup of your server with its own content, files, worlds, and settings. You can switch which instance your server runs at any time."
|
||||
},
|
||||
"servers.manage.instances.info.files": {
|
||||
"defaultMessage": "Each instance has its own server files, worlds, installed content, and settings."
|
||||
"servers.manage.instances.info.dismiss": {
|
||||
"defaultMessage": "Don't show this again"
|
||||
},
|
||||
"servers.manage.instances.info.header": {
|
||||
"defaultMessage": "What is an instance?"
|
||||
},
|
||||
"servers.manage.instances.info.switching": {
|
||||
"defaultMessage": "You can switch which instance your server runs."
|
||||
"defaultMessage": "What is a server instance?"
|
||||
},
|
||||
"servers.manage.instances.slot-name": {
|
||||
"defaultMessage": "Instance #{index}"
|
||||
@@ -5091,7 +5109,7 @@
|
||||
"defaultMessage": "You are being rate limited. Please try again later."
|
||||
},
|
||||
"servers.setup.rate-limit.title": {
|
||||
"defaultMessage": "Cannot reinstall server"
|
||||
"defaultMessage": "Cannot reinstall instance"
|
||||
},
|
||||
"servers.setup.reinstall-failed.text": {
|
||||
"defaultMessage": "An unexpected error occurred while reinstalling. Please try again later."
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { ServerSettingsTabId } from '#ui/layouts/shared/server-settings'
|
||||
import type {
|
||||
ServerInstanceSettingsTabId,
|
||||
ServerSettingsTabId,
|
||||
} from '#ui/layouts/shared/server-settings'
|
||||
|
||||
import { createContext } from './create-context'
|
||||
|
||||
@@ -10,6 +13,10 @@ export interface BrowseServerContentArgs {
|
||||
|
||||
export interface ServerSettingsModalContext {
|
||||
openServerSettings: (options?: { tabId?: ServerSettingsTabId }) => void
|
||||
openServerInstanceSettings: (options?: {
|
||||
tabId?: ServerInstanceSettingsTabId
|
||||
worldId?: string | null
|
||||
}) => void
|
||||
browseServerContent?: (args: BrowseServerContentArgs) => void | Promise<void>
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user