Files
pages/apps/frontend/src/composables/servers/modules/general.ts
Calum H. 099011a177 feat: modrinth hosting - files tab refactor (#4912)
* feat: api-client module for content v0

* feat: delete unused components + modules + setting

* feat: xhr uploading

* feat: fs module -> api-client

* feat: migrate files.vue to use tanstack

* fix: mem leak + other issues

* fix: build

* feat: switch to monaco

* fix: go back to using ace, but improve preloading + theme

* fix: styling + dead attrs

* feat: match figma

* fix: padding

* feat: files-new for ui page structure

* feat: finalize files.vue

* fix: lint

* fix: qa

* fix: dep

* fix: lint

* fix: lockfile merge

* feat: icons on navtab

* fix: surface alternating on table

* fix: hover surface color

---------

Signed-off-by: Calum H. <contact@cal.engineer>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-01-06 00:35:51 +00:00

202 lines
5.5 KiB
TypeScript

import type { JWTAuth, PowerAction, Project, ServerGeneral } from '@modrinth/utils'
import { $fetch } from 'ofetch'
import { useServersFetch } from '../servers-fetch.ts'
import { ServerModule } from './base.ts'
export class GeneralModule extends ServerModule implements ServerGeneral {
server_id!: string
name!: string
owner_id!: string
net!: { ip: string; port: number; domain: string }
game!: string
backup_quota!: number
used_backup_quota!: number
status!: string
suspension_reason!: string
loader!: string
loader_version!: string
mc_version!: string
upstream!: {
kind: 'modpack' | 'mod' | 'resourcepack'
version_id: string
project_id: string
} | null
motd?: string
image?: string
project?: Project
sftp_username!: string
sftp_password!: string
sftp_host!: string
datacenter?: string
notices?: any[]
node!: { token: string; instance: string }
flows?: { intro?: boolean }
is_medal?: boolean
async fetch(): Promise<void> {
const data = await useServersFetch<ServerGeneral>(`servers/${this.serverId}`, {}, 'general')
if (data.upstream?.project_id) {
const project = await $fetch(
`https://api.modrinth.com/v2/project/${data.upstream.project_id}`,
)
data.project = project as Project
}
if (import.meta.client) {
data.image = (await this.server.processImage(data.project?.icon_url)) ?? undefined
}
// Copy data to this module
Object.assign(this, data)
}
async updateName(newName: string): Promise<void> {
await useServersFetch(`servers/${this.serverId}/name`, {
method: 'POST',
body: { name: newName },
})
}
async power(action: PowerAction): Promise<void> {
await useServersFetch(`servers/${this.serverId}/power`, {
method: 'POST',
body: { action },
})
await new Promise((resolve) => setTimeout(resolve, 1000))
await this.fetch() // Refresh this module
}
async reinstall(
loader: boolean,
projectId: string,
versionId?: string,
loaderVersionId?: string,
hardReset: boolean = false,
): Promise<void> {
const hardResetParam = hardReset ? 'true' : 'false'
if (loader) {
if (projectId.toLowerCase() === 'neoforge') {
projectId = 'NeoForge'
}
await useServersFetch(`servers/${this.serverId}/reinstall?hard=${hardResetParam}`, {
method: 'POST',
body: {
loader: projectId,
loader_version: loaderVersionId,
game_version: versionId,
},
})
} else {
await useServersFetch(`servers/${this.serverId}/reinstall?hard=${hardResetParam}`, {
method: 'POST',
body: { project_id: projectId, version_id: versionId },
})
}
}
reinstallFromMrpack(
mrpack: File,
hardReset: boolean = false,
): {
promise: Promise<void>
onProgress: (cb: (p: { loaded: number; total: number; progress: number }) => void) => void
} {
const hardResetParam = hardReset ? 'true' : 'false'
const progressSubject = new EventTarget()
const uploadPromise = (async () => {
try {
const auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/reinstallFromMrpack`)
await new Promise<void>((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
progressSubject.dispatchEvent(
new CustomEvent('progress', {
detail: {
loaded: e.loaded,
total: e.total,
progress: (e.loaded / e.total) * 100,
},
}),
)
}
})
xhr.onload = () =>
xhr.status >= 200 && xhr.status < 300
? resolve()
: reject(new Error(`[pyroservers] XHR error status: ${xhr.status}`))
xhr.onerror = () => reject(new Error('[pyroservers] .mrpack upload failed'))
xhr.onabort = () => reject(new Error('[pyroservers] .mrpack upload cancelled'))
xhr.ontimeout = () => reject(new Error('[pyroservers] .mrpack upload timed out'))
xhr.timeout = 30 * 60 * 1000
xhr.open('POST', `https://${auth.url}/reinstallMrpackMultiparted?hard=${hardResetParam}`)
xhr.setRequestHeader('Authorization', `Bearer ${auth.token}`)
const formData = new FormData()
formData.append('file', mrpack)
xhr.send(formData)
})
} catch (err) {
console.error('Error reinstalling from mrpack:', err)
throw err
}
})()
return {
promise: uploadPromise,
onProgress: (cb: (p: { loaded: number; total: number; progress: number }) => void) =>
progressSubject.addEventListener('progress', ((e: CustomEvent) =>
cb(e.detail)) as EventListener),
}
}
async suspend(status: boolean): Promise<void> {
await useServersFetch(`servers/${this.serverId}/suspend`, {
method: 'POST',
body: { suspended: status },
})
}
async endIntro(): Promise<void> {
await useServersFetch(`servers/${this.serverId}/flows/intro`, {
method: 'DELETE',
version: 1,
})
await this.fetch() // Refresh this module
}
async setMotd(motd: string): Promise<void> {
try {
const props = (await this.server.fetchConfigFile('ServerProperties')) as any
if (props) {
props.motd = motd
const newProps = this.server.constructServerProperties(props)
const octetStream = new Blob([newProps], { type: 'application/octet-stream' })
const auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/fs`)
await useServersFetch(`/update?path=/server.properties`, {
method: 'PUT',
contentType: 'application/octet-stream',
body: octetStream,
override: auth,
})
}
} catch {
console.error(
'[Modrinth Hosting] [General] Failed to set MOTD due to lack of server properties file.',
)
}
}
}