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