mirror of
https://github.com/modrinth/code.git
synced 2026-09-03 13:36:48 +00:00
feat: impl world scoped power endpoint
This commit is contained in:
@@ -97,19 +97,6 @@ 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> {
|
|
||||||
await this.client.request(`/servers/${serverId}/power`, {
|
|
||||||
api: 'archon',
|
|
||||||
method: 'POST',
|
|
||||||
version: 'modrinth/v0',
|
|
||||||
body: { action },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reinstall a server with a new loader or modpack
|
* Reinstall a server with a new loader or modpack
|
||||||
* POST /modrinth/v0/servers/:id/reinstall
|
* POST /modrinth/v0/servers/:id/reinstall
|
||||||
|
|||||||
@@ -91,6 +91,23 @@ export class ArchonServersV1Module extends AbstractModule {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a power action for a specific world
|
||||||
|
* POST /v1/servers/:id/worlds/:wid/power
|
||||||
|
*/
|
||||||
|
public async powerWorld(
|
||||||
|
serverId: string,
|
||||||
|
worldId: string,
|
||||||
|
request: Archon.Servers.v1.WorldPowerActionRequest,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.client.request(`/servers/${serverId}/worlds/${worldId}/power`, {
|
||||||
|
api: 'archon',
|
||||||
|
version: 1,
|
||||||
|
method: 'POST',
|
||||||
|
body: request,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset a world to onboarding
|
* Reset a world to onboarding
|
||||||
* POST /v1/servers/:id/worlds/:wid/onboard
|
* POST /v1/servers/:id/worlds/:wid/onboard
|
||||||
|
|||||||
@@ -681,8 +681,6 @@ export namespace Archon {
|
|||||||
token: string // JWT token for filesystem access
|
token: string // JWT token for filesystem access
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PowerAction = 'Start' | 'Stop' | 'Restart' | 'Kill'
|
|
||||||
|
|
||||||
export type ReinstallLoaderRequest = {
|
export type ReinstallLoaderRequest = {
|
||||||
loader: string
|
loader: string
|
||||||
loader_version?: string
|
loader_version?: string
|
||||||
@@ -715,6 +713,13 @@ export namespace Archon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace v1 {
|
export namespace v1 {
|
||||||
|
export type WorldPowerAction = 'start' | 'stop' | 'restart' | 'kill'
|
||||||
|
|
||||||
|
export type WorldPowerActionRequest = {
|
||||||
|
action: WorldPowerAction
|
||||||
|
shutdown_strategy?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export type ServerFull = {
|
export type ServerFull = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled v-if="props.restart" color="brand">
|
<ButtonStyled v-if="props.restart" color="brand">
|
||||||
<button :disabled="props.isUpdating || isTransitioning" @click="saveAndPower">
|
<button :disabled="props.isUpdating || isTransitioning || !worldId" @click="saveAndPower">
|
||||||
<SpinnerIcon v-if="props.isUpdating || isTransitioning" class="animate-spin" />
|
<SpinnerIcon v-if="props.isUpdating || isTransitioning" class="animate-spin" />
|
||||||
{{ powerButtonLabel }}
|
{{ powerButtonLabel }}
|
||||||
</button>
|
</button>
|
||||||
@@ -43,7 +43,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const client = injectModrinthClient()
|
const client = injectModrinthClient()
|
||||||
|
|
||||||
const { powerState } = injectModrinthServerContext()
|
const { powerState, worldId } = injectModrinthServerContext()
|
||||||
|
|
||||||
const isStopped = computed(() => powerState.value === 'stopped' || powerState.value === 'crashed')
|
const isStopped = computed(() => powerState.value === 'stopped' || powerState.value === 'crashed')
|
||||||
|
|
||||||
@@ -63,6 +63,9 @@ const saveAndPower = async () => {
|
|||||||
} catch {
|
} catch {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await client.archon.servers_v0.power(props.serverId, isStopped.value ? 'Start' : 'Restart')
|
if (!worldId.value) return
|
||||||
|
await client.archon.servers_v1.powerWorld(props.serverId, worldId.value, {
|
||||||
|
action: isStopped.value ? 'start' : 'restart',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ const props = withDefaults(
|
|||||||
loader: null,
|
loader: null,
|
||||||
loaderVersion: null,
|
loaderVersion: null,
|
||||||
lastActive: null,
|
lastActive: null,
|
||||||
fallbackName: 'World',
|
fallbackName: 'Instance',
|
||||||
headerClass: '',
|
headerClass: '',
|
||||||
actions: () => [],
|
actions: () => [],
|
||||||
},
|
},
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
export { default as PanelServerActionButton } from './PanelServerActionButton.vue'
|
export { default as PanelServerActionButton } from './PanelServerActionButton.vue'
|
||||||
export { default as ServerManageHeader } from './ServerManageHeader.vue'
|
export { default as ServerManageHeader } from './ServerManageHeader.vue'
|
||||||
export { default as WorldManageHeader } from './WorldManageHeader.vue'
|
export { default as ServerInstanceManageHeader } from './ServerInstanceManageHeader.vue'
|
||||||
|
|||||||
@@ -9,12 +9,19 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
} from '#ui/providers'
|
} from '#ui/providers'
|
||||||
|
|
||||||
export type PowerAction = Archon.Servers.v0.PowerAction
|
export type PowerAction = 'Start' | 'Stop' | 'Restart' | 'Kill'
|
||||||
|
|
||||||
|
const powerActionMap = {
|
||||||
|
Start: 'start',
|
||||||
|
Stop: 'stop',
|
||||||
|
Restart: 'restart',
|
||||||
|
Kill: 'kill',
|
||||||
|
} as const satisfies Record<PowerAction, Archon.Servers.v1.WorldPowerAction>
|
||||||
|
|
||||||
export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
|
export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const client = injectModrinthClient()
|
const client = injectModrinthClient()
|
||||||
const { serverId, server, powerState, isSyncingContent, busyReasons } =
|
const { serverId, worldId, server, powerState, isSyncingContent, busyReasons } =
|
||||||
injectModrinthServerContext()
|
injectModrinthServerContext()
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const { canUsePowerActions, permissionDeniedMessage } = useServerPermissions()
|
const { canUsePowerActions, permissionDeniedMessage } = useServerPermissions()
|
||||||
@@ -46,16 +53,18 @@ export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
|
|||||||
|
|
||||||
const busyTooltip = computed(() => {
|
const busyTooltip = computed(() => {
|
||||||
if (!canUsePowerActions.value) return permissionDeniedMessage.value
|
if (!canUsePowerActions.value) return permissionDeniedMessage.value
|
||||||
|
if (!worldId.value) return 'Your server instance is loading'
|
||||||
if (isStarting.value) return 'Your server is starting'
|
if (isStarting.value) return 'Your server is starting'
|
||||||
return busyReasons.value.length > 0 ? formatMessage(busyReasons.value[0].reason) : undefined
|
return busyReasons.value.length > 0 ? formatMessage(busyReasons.value[0].reason) : undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
const canTakeAction = computed(
|
const canTakeAction = computed(
|
||||||
() => !isTransitioning.value && !isBlockedByPropsBusyOrPermission.value,
|
() => !!worldId.value && !isTransitioning.value && !isBlockedByPropsBusyOrPermission.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
const canKill = computed(
|
const canKill = computed(
|
||||||
() =>
|
() =>
|
||||||
|
!!worldId.value &&
|
||||||
!isBlockedByPropsBusyOrPermission.value &&
|
!isBlockedByPropsBusyOrPermission.value &&
|
||||||
(isStopping.value || isRunning.value || isStarting.value),
|
(isStopping.value || isRunning.value || isStarting.value),
|
||||||
)
|
)
|
||||||
@@ -72,9 +81,13 @@ export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function sendPowerAction(action: PowerAction) {
|
async function sendPowerAction(action: PowerAction, targetWorldId = worldId.value) {
|
||||||
|
if (!targetWorldId) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.archon.servers_v0.power(serverId, action)
|
await client.archon.servers_v1.powerWorld(serverId, targetWorldId, {
|
||||||
|
action: powerActionMap[action],
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error performing ${action} on server:`, error)
|
console.error(`Error performing ${action} on server:`, error)
|
||||||
addNotification({
|
addNotification({
|
||||||
@@ -85,13 +98,13 @@ export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initiateAction(action: PowerAction) {
|
function initiateAction(action: PowerAction, targetWorldId = worldId.value) {
|
||||||
if (action === 'Kill') {
|
if (action === 'Kill') {
|
||||||
if (!canKill.value) return
|
if (!canKill.value) return
|
||||||
} else {
|
} else {
|
||||||
if (!canTakeAction.value) return
|
if (!canTakeAction.value) return
|
||||||
}
|
}
|
||||||
void sendPowerAction(action)
|
void sendPowerAction(action, targetWorldId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePrimaryAction() {
|
function handlePrimaryAction() {
|
||||||
|
|||||||
+2
-2
@@ -389,8 +389,8 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// Restart
|
// Restart
|
||||||
async function restartServer() {
|
async function restartServer() {
|
||||||
if (!canUsePowerActions.value) return
|
if (!canUsePowerActions.value || !worldId.value) return
|
||||||
await client.archon.servers_v0.power(serverId, 'Restart')
|
await client.archon.servers_v1.powerWorld(serverId, worldId.value, { action: 'restart' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSessionUploadFilename(fileName: string) {
|
function getSessionUploadFilename(fileName: string) {
|
||||||
|
|||||||
+3
-4
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex min-h-[36rem] flex-col gap-4 text-primary">
|
<div class="flex min-h-[36rem] flex-col gap-4 text-primary">
|
||||||
<WorldManageHeader
|
<ServerInstanceManageHeader
|
||||||
:name="worldName"
|
:name="worldName"
|
||||||
:game-version="gameVersion"
|
:game-version="gameVersion"
|
||||||
:loader="loader"
|
:loader="loader"
|
||||||
@@ -38,7 +38,7 @@ import { useRouter } from 'vue-router'
|
|||||||
|
|
||||||
import type { JoinedButtonAction } from '#ui/components/base/JoinedButtons.vue'
|
import type { JoinedButtonAction } from '#ui/components/base/JoinedButtons.vue'
|
||||||
import NavTabs from '#ui/components/base/NavTabs.vue'
|
import NavTabs from '#ui/components/base/NavTabs.vue'
|
||||||
import { WorldManageHeader } from '#ui/components/servers/server-header'
|
import { ServerInstanceManageHeader } from '#ui/components/servers/server-header'
|
||||||
import { useServerPowerAction } from '#ui/components/servers/server-header/use-server-power-action'
|
import { useServerPowerAction } from '#ui/components/servers/server-header/use-server-power-action'
|
||||||
import { useRelativeTime } from '#ui/composables'
|
import { useRelativeTime } from '#ui/composables'
|
||||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||||
@@ -179,12 +179,11 @@ const restartSplitActions = computed<JoinedButtonAction[]>(() => [
|
|||||||
icon: UpdatedIcon,
|
icon: UpdatedIcon,
|
||||||
action: () => initiateAction('Restart'),
|
action: () => initiateAction('Restart'),
|
||||||
},
|
},
|
||||||
// TODO: Implement world scoping when Archon/Kyros support target worlds in power requests.
|
|
||||||
...restartableWorlds.value.map((world) => ({
|
...restartableWorlds.value.map((world) => ({
|
||||||
id: `restart-${world.id}`,
|
id: `restart-${world.id}`,
|
||||||
label: `Restart with ${world.name}`,
|
label: `Restart with ${world.name}`,
|
||||||
icon: GlobeIcon,
|
icon: GlobeIcon,
|
||||||
action: () => initiateAction('Restart'),
|
action: () => initiateAction('Restart', world.id),
|
||||||
})),
|
})),
|
||||||
])
|
])
|
||||||
const powerActions = computed(() => {
|
const powerActions = computed(() => {
|
||||||
|
|||||||
+4
-4
@@ -1,11 +1,11 @@
|
|||||||
import { PlayIcon, SettingsIcon } from '@modrinth/assets'
|
import { PlayIcon, SettingsIcon } from '@modrinth/assets'
|
||||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||||
|
|
||||||
import WorldManageHeader from '../../components/servers/server-header/WorldManageHeader.vue'
|
import ServerInstanceManageHeader from '../../components/servers/server-header/ServerInstanceManageHeader.vue'
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Servers/WorldManageHeader',
|
title: 'Servers/ServerInstanceManageHeader',
|
||||||
component: WorldManageHeader,
|
component: ServerInstanceManageHeader,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: 'padded',
|
layout: 'padded',
|
||||||
},
|
},
|
||||||
@@ -15,7 +15,7 @@ const meta = {
|
|||||||
template: '<div style="max-width: 920px;"><story /></div>',
|
template: '<div style="max-width: 920px;"><story /></div>',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
} satisfies Meta<typeof WorldManageHeader>
|
} satisfies Meta<typeof ServerInstanceManageHeader>
|
||||||
|
|
||||||
export default meta
|
export default meta
|
||||||
type Story = StoryObj<typeof meta>
|
type Story = StoryObj<typeof meta>
|
||||||
Reference in New Issue
Block a user