Skip to content

Commit 1aed557

Browse files
committed
feat: implement wrapped overview page with shared composable and hook it up
1 parent 6333cca commit 1aed557

File tree

15 files changed

+945
-862
lines changed

15 files changed

+945
-862
lines changed

apps/app-frontend/src/pages/hosting/manage/Index.vue

Lines changed: 56 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
:show-uptime="false"
1616
>
1717
<template #actions>
18-
<div class="flex gap-2" v-if="isConnected && !server.flows?.intro">
18+
<div v-if="isConnected && !server.flows?.intro" class="flex gap-2">
1919
<PanelServerActionButton />
2020
<ButtonStyled circular size="large">
2121
<button v-tooltip="'Server settings'" @click="openServerSettingsModal">
@@ -35,10 +35,13 @@
3535
<Suspense>
3636
<ServerSettingsModal ref="serverSettingsModal" />
3737
</Suspense>
38-
<RouterView v-slot="{ Component }">
38+
<RouterView v-slot="{ Component, route: childRoute }">
3939
<template v-if="Component">
4040
<Suspense>
41-
<component :is="Component" />
41+
<component
42+
:is="Component"
43+
v-bind="childRoute.name === 'ServerManageOverview' ? overviewChildProps : undefined"
44+
/>
4245
<template #fallback>
4346
<LoadingIndicator />
4447
</template>
@@ -51,22 +54,26 @@
5154
</template>
5255

5356
<script setup lang="ts">
54-
import { type Archon, clearNodeAuthState, setNodeAuthState } from '@modrinth/api-client'
55-
import { BoxesIcon, DatabaseBackupIcon, FolderOpenIcon, SettingsIcon } from '@modrinth/assets'
57+
import type { Archon } from '@modrinth/api-client'
58+
import {
59+
BoxesIcon,
60+
DatabaseBackupIcon,
61+
FolderOpenIcon,
62+
LayoutTemplateIcon,
63+
SettingsIcon,
64+
} from '@modrinth/assets'
5665
import {
57-
type BusyReason,
5866
ButtonStyled,
59-
defineMessage,
6067
injectModrinthClient,
6168
LoadingIndicator,
6269
NavTabs,
6370
PanelServerActionButton,
6471
PanelServerOverflowMenu,
65-
provideModrinthServerContext,
6672
ServerManageHeader,
73+
useServerManageCoreRuntime,
6774
} from '@modrinth/ui'
68-
import { useQuery, useQueryClient } from '@tanstack/vue-query'
69-
import { computed, onUnmounted, reactive, ref, watch } from 'vue'
75+
import { useQuery } from '@tanstack/vue-query'
76+
import { computed, onUnmounted, ref, watch } from 'vue'
7077
import { useRoute } from 'vue-router'
7178
7279
import ServerSettingsModal from '@/components/ui/modal/ServerSettingsModal.vue'
@@ -75,7 +82,6 @@ import { useTheming } from '@/store/theme'
7582
const route = useRoute()
7683
const themeStore = useTheming()
7784
const client = injectModrinthClient()
78-
const queryClient = useQueryClient()
7985
8086
const serverId = computed(() => {
8187
const rawId = route.params.id
@@ -137,39 +143,28 @@ const serverProjectLink = computed(() => {
137143
return `/project/${serverProject.value.slug ?? serverProject.value.id}`
138144
})
139145
140-
const isConnected = ref(false)
141-
const powerState = ref<Archon.Websocket.v0.PowerState>('stopped')
142-
const isServerRunning = computed(() => powerState.value === 'running')
143-
const backupsState = reactive(new Map())
144146
const isSyncingContent = ref(false)
145-
const socketUnsubscribers = ref<(() => void)[]>([])
146-
const connectedSocketServerId = ref<string | null>(null)
147-
148-
const busyReasons = computed<BusyReason[]>(() => {
149-
const reasons: BusyReason[] = []
150-
if (server.value?.status === 'installing') {
151-
reasons.push({
152-
reason: defineMessage({
153-
id: 'servers.busy.installing',
154-
defaultMessage: 'Server is installing',
155-
}),
156-
})
157-
}
158-
if (isSyncingContent.value) {
159-
reasons.push({
160-
reason: defineMessage({
161-
id: 'servers.busy.syncing-content',
162-
defaultMessage: 'Content sync in progress',
163-
}),
164-
})
165-
}
166-
return reasons
147+
const {
148+
cleanupCoreRuntime,
149+
connectSocket,
150+
connectedSocketServerId,
151+
disconnectSocket,
152+
fsAuth,
153+
isConnected,
154+
isServerRunning,
155+
isWsAuthIncorrect,
156+
powerStateDetails,
157+
refreshFsAuth,
158+
serverPowerState,
159+
stats,
160+
} = useServerManageCoreRuntime({
161+
serverId,
162+
worldId,
163+
server,
164+
isSyncingContent,
165+
setDisconnectedOnAuthIncorrect: true,
167166
})
168167
169-
const fsAuth = ref<{ url: string; token: string } | null>(null)
170-
const fsOps = ref<Archon.Websocket.v0.FilesystemOperation[]>([])
171-
const fsQueuedOps = ref<Archon.Websocket.v0.QueuedFilesystemOp[]>([])
172-
173168
async function processImageBlob(blob: Blob, size: number): Promise<string> {
174169
return new Promise((resolve) => {
175170
const canvas = document.createElement('canvas')
@@ -205,85 +200,11 @@ const { data: serverImage } = useQuery({
205200
enabled: computed(() => !!serverId.value && server.value.status === 'available'),
206201
})
207202
208-
async function refreshFsAuth() {
209-
if (!serverId.value) {
210-
fsAuth.value = null
211-
return
212-
}
213-
fsAuth.value = await queryClient.fetchQuery({
214-
queryKey: ['servers', 'filesystem-auth', serverId.value],
215-
queryFn: () => client.archon.servers_v0.getFilesystemAuth(serverId.value),
216-
})
217-
}
218-
219-
function markBackupCancelled(backupId: string) {
220-
backupsState.delete(backupId)
221-
}
222-
223203
function openServerSettingsModal() {
224204
if (!serverId.value) return
225205
serverSettingsModal.value?.show({ serverId: serverId.value })
226206
}
227207
228-
function setPowerState(state: Archon.Websocket.v0.PowerState) {
229-
powerState.value = state
230-
}
231-
232-
function handlePowerState(data: Archon.Websocket.v0.WSPowerStateEvent) {
233-
setPowerState(data.state)
234-
}
235-
236-
function handleState(data: Archon.Websocket.v0.WSStateEvent) {
237-
const powerMap: Record<Archon.Websocket.v0.FlattenedPowerState, Archon.Websocket.v0.PowerState> =
238-
{
239-
not_ready: 'stopped',
240-
starting: 'starting',
241-
running: 'running',
242-
stopping: 'stopping',
243-
idle:
244-
data.was_oom || (data.exit_code != null && data.exit_code !== 0) ? 'crashed' : 'stopped',
245-
}
246-
setPowerState(powerMap[data.power_variant])
247-
}
248-
249-
function disconnectSocket(targetServerId?: string) {
250-
for (const unsub of socketUnsubscribers.value) unsub()
251-
socketUnsubscribers.value = []
252-
253-
if (targetServerId) {
254-
client.archon.sockets.disconnect(targetServerId)
255-
}
256-
connectedSocketServerId.value = null
257-
isConnected.value = false
258-
setPowerState('stopped')
259-
}
260-
261-
async function connectSocket(targetServerId: string) {
262-
if (connectedSocketServerId.value === targetServerId && isConnected.value) {
263-
return
264-
}
265-
disconnectSocket(targetServerId)
266-
267-
try {
268-
await client.archon.sockets.safeConnect(targetServerId, { force: true })
269-
connectedSocketServerId.value = targetServerId
270-
isConnected.value = true
271-
socketUnsubscribers.value = [
272-
client.archon.sockets.on(targetServerId, 'state', handleState),
273-
client.archon.sockets.on(targetServerId, 'power-state', handlePowerState),
274-
client.archon.sockets.on(targetServerId, 'auth-incorrect', () => {
275-
isConnected.value = false
276-
}),
277-
client.archon.sockets.on(targetServerId, 'auth-ok', () => {
278-
isConnected.value = true
279-
}),
280-
]
281-
} catch (error) {
282-
console.error('[hosting/manage] Failed to connect server socket:', error)
283-
isConnected.value = false
284-
}
285-
}
286-
287208
watch(
288209
() => serverId.value,
289210
(newServerId, oldServerId) => {
@@ -304,41 +225,36 @@ watch(
304225
disconnectSocket(currentServerId)
305226
return
306227
}
307-
if (connectedSocketServerId.value === currentServerId && isConnected.value) {
228+
if (
229+
connectedSocketServerId.value === currentServerId &&
230+
(isConnected.value || isWsAuthIncorrect.value)
231+
) {
308232
return
309233
}
310-
void connectSocket(currentServerId)
234+
void connectSocket(currentServerId, { force: true })
311235
},
312236
{ immediate: true },
313237
)
314238
315-
provideModrinthServerContext({
316-
get serverId() {
317-
return serverId.value
318-
},
319-
worldId,
320-
server,
321-
isConnected,
322-
powerState,
323-
isServerRunning,
324-
backupsState,
325-
markBackupCancelled,
326-
isSyncingContent,
327-
busyReasons,
328-
fsAuth,
329-
fsOps,
330-
fsQueuedOps,
331-
refreshFsAuth,
332-
})
333-
334-
setNodeAuthState(() => fsAuth.value, refreshFsAuth)
335-
336239
onUnmounted(() => {
337-
disconnectSocket(serverId.value || undefined)
338-
clearNodeAuthState()
240+
cleanupCoreRuntime(serverId.value || undefined)
339241
})
340242
243+
const overviewChildProps = computed(() => ({
244+
isConnected: isConnected.value,
245+
isWsAuthIncorrect: isWsAuthIncorrect.value,
246+
isServerRunning: isServerRunning.value,
247+
stats: stats.value,
248+
serverPowerState: serverPowerState.value,
249+
powerStateDetails: powerStateDetails.value,
250+
}))
251+
341252
const tabs = computed(() => [
253+
{
254+
label: 'Overview',
255+
href: basePath.value,
256+
icon: LayoutTemplateIcon,
257+
},
342258
{
343259
label: 'Content',
344260
href: `${basePath.value}/content`,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import { ServersManageOverviewPage } from '@modrinth/ui'
3+
import type { ServerState, Stats } from '@modrinth/utils'
4+
5+
type ServerProps = {
6+
isConnected: boolean
7+
isWsAuthIncorrect: boolean
8+
stats: Stats
9+
serverPowerState: ServerState
10+
powerStateDetails?: {
11+
oom_killed?: boolean
12+
exit_code?: number
13+
}
14+
isServerRunning: boolean
15+
}
16+
17+
const props = defineProps<ServerProps>()
18+
</script>
19+
20+
<template>
21+
<ServersManageOverviewPage v-bind="props" />
22+
</template>

apps/app-frontend/src/pages/hosting/manage/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ import Backups from './Backups.vue'
22
import Content from './Content.vue'
33
import Files from './Files.vue'
44
import Index from './Index.vue'
5+
import Overview from './Overview.vue'
56

6-
export { Backups, Content, Files, Index }
7+
export { Backups, Content, Files, Index, Overview }

apps/app-frontend/src/routes.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,8 @@ export default new createRouter({
4343
children: [
4444
{
4545
path: '',
46-
redirect: (to) => {
47-
const rawId = Array.isArray(to.params.id) ? to.params.id[0] : to.params.id
48-
if (!rawId) return '/hosting/manage'
49-
return `/hosting/manage/${encodeURIComponent(rawId)}/content`
50-
},
46+
name: 'ServerManageOverview',
47+
component: Hosting.Overview,
5148
},
5249
{
5350
path: 'content',

apps/frontend/src/locales/en-US/index.json

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2777,18 +2777,6 @@
27772777
"search.filter.locked.server.sync": {
27782778
"message": "Sync with server"
27792779
},
2780-
"servers.busy.backup-creating": {
2781-
"message": "Backup creation in progress"
2782-
},
2783-
"servers.busy.backup-restoring": {
2784-
"message": "Backup restore in progress"
2785-
},
2786-
"servers.busy.installing": {
2787-
"message": "Server is installing"
2788-
},
2789-
"servers.busy.syncing-content": {
2790-
"message": "Content sync in progress"
2791-
},
27922780
"servers.notice.actions": {
27932781
"message": "Actions"
27942782
},

0 commit comments

Comments
 (0)