fix: prerelease fixes (#5793)
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import { injectModrinthServerContext, ServersManageOverviewPage } from '@modrinth/ui'
|
import { injectModrinthServerContext, ServersManageOverviewPage } from '@modrinth/ui'
|
||||||
|
|
||||||
const { server } = injectModrinthServerContext()
|
const { server } = injectModrinthServerContext()
|
||||||
|
const flags = useFeatureFlags()
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: computed(() => `Overview - ${server.value?.name ?? 'Server'} - Modrinth`),
|
title: computed(() => `Overview - ${server.value?.name ?? 'Server'} - Modrinth`),
|
||||||
@@ -9,5 +10,5 @@ useHead({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ServersManageOverviewPage />
|
<ServersManageOverviewPage :show-advanced-debug-info="flags.advancedDebugInfo" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,54 +1,71 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<Teleport to="body">
|
||||||
v-if="open"
|
|
||||||
:style="`${mouseX !== -1 ? `--_mouse-x: ${mouseX};` : ''} ${mouseY !== -1 ? `--_mouse-y: ${mouseY};` : ''}`"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
:class="{ shown: visible }"
|
v-if="open"
|
||||||
class="tauri-overlay"
|
:style="`${mouseX !== -1 ? `--_mouse-x: ${mouseX};` : ''} ${mouseY !== -1 ? `--_mouse-y: ${mouseY};` : ''}`"
|
||||||
data-tauri-drag-region
|
|
||||||
@click="() => (closeOnClickOutside && closable ? hide() : {})"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'modal-overlay',
|
|
||||||
{
|
|
||||||
shown: visible,
|
|
||||||
noblur: effectiveNoblur,
|
|
||||||
},
|
|
||||||
computedFade,
|
|
||||||
]"
|
|
||||||
@click="() => (closeOnClickOutside && closable ? hide() : {})"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="modal-container experimental-styles-within"
|
|
||||||
:class="{ shown: visible }"
|
|
||||||
:style="{
|
|
||||||
'--_max-width': maxWidth,
|
|
||||||
'--_width': width,
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="modalBodyRef"
|
:class="{ shown: visible }"
|
||||||
role="dialog"
|
class="tauri-overlay"
|
||||||
aria-modal="true"
|
data-tauri-drag-region
|
||||||
:aria-labelledby="headerId"
|
@click="() => (closeOnClickOutside && closable ? hide() : {})"
|
||||||
class="modal-body flex flex-col bg-bg-raised rounded-2xl border border-solid border-surface-5"
|
/>
|
||||||
@keydown="handleKeyDown"
|
<div
|
||||||
|
:class="[
|
||||||
|
'modal-overlay',
|
||||||
|
{
|
||||||
|
shown: visible,
|
||||||
|
noblur: effectiveNoblur,
|
||||||
|
},
|
||||||
|
computedFade,
|
||||||
|
]"
|
||||||
|
@click="() => (closeOnClickOutside && closable ? hide() : {})"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="modal-container experimental-styles-within"
|
||||||
|
:class="{ shown: visible }"
|
||||||
|
:style="{
|
||||||
|
'--_max-width': maxWidth,
|
||||||
|
'--_width': width,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="!hideHeader"
|
ref="modalBodyRef"
|
||||||
data-tauri-drag-region
|
role="dialog"
|
||||||
class="grid grid-cols-[auto_min-content] items-center gap-4 p-6 border-solid border-0 border-b-[1px] border-surface-5 max-w-full"
|
aria-modal="true"
|
||||||
|
:aria-labelledby="headerId"
|
||||||
|
class="modal-body flex flex-col bg-bg-raised rounded-2xl border border-solid border-surface-5"
|
||||||
|
@keydown="handleKeyDown"
|
||||||
>
|
>
|
||||||
<div class="flex text-wrap break-words items-center gap-3 min-w-0">
|
<div
|
||||||
<slot name="title">
|
v-if="!hideHeader"
|
||||||
<span v-if="header" :id="headerId" class="text-2xl font-semibold text-contrast">
|
data-tauri-drag-region
|
||||||
{{ header }}
|
class="grid grid-cols-[auto_min-content] items-center gap-4 p-6 border-solid border-0 border-b-[1px] border-surface-5 max-w-full"
|
||||||
</span>
|
>
|
||||||
</slot>
|
<div class="flex text-wrap break-words items-center gap-3 min-w-0">
|
||||||
|
<slot name="title">
|
||||||
|
<span v-if="header" :id="headerId" class="text-2xl font-semibold text-contrast">
|
||||||
|
{{ header }}
|
||||||
|
</span>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<ButtonStyled v-if="closable" circular>
|
||||||
|
<button
|
||||||
|
v-tooltip="closeLabel"
|
||||||
|
:aria-label="closeLabel"
|
||||||
|
:disabled="disableClose"
|
||||||
|
@click="hide"
|
||||||
|
>
|
||||||
|
<XIcon aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
<ButtonStyled v-if="closable" circular>
|
|
||||||
|
<ButtonStyled
|
||||||
|
v-if="props.mergeHeader && closable"
|
||||||
|
class="absolute top-4 right-4 z-10"
|
||||||
|
circular
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
v-tooltip="closeLabel"
|
v-tooltip="closeLabel"
|
||||||
:aria-label="closeLabel"
|
:aria-label="closeLabel"
|
||||||
@@ -58,82 +75,67 @@
|
|||||||
<XIcon aria-hidden="true" />
|
<XIcon aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
|
||||||
|
|
||||||
<ButtonStyled
|
<div v-if="scrollable" class="relative flex-1 min-h-0 flex flex-col">
|
||||||
v-if="props.mergeHeader && closable"
|
<Transition
|
||||||
class="absolute top-4 right-4 z-10"
|
enter-active-class="transition-all duration-200 ease-out"
|
||||||
circular
|
enter-from-class="opacity-0 max-h-0"
|
||||||
>
|
enter-to-class="opacity-100 max-h-6"
|
||||||
<button
|
leave-active-class="transition-all duration-200 ease-in"
|
||||||
v-tooltip="closeLabel"
|
leave-from-class="opacity-100 max-h-6"
|
||||||
:aria-label="closeLabel"
|
leave-to-class="opacity-0 max-h-0"
|
||||||
:disabled="disableClose"
|
>
|
||||||
@click="hide"
|
<div
|
||||||
>
|
v-if="showTopFade"
|
||||||
<XIcon aria-hidden="true" />
|
class="pointer-events-none absolute left-0 right-0 top-0 z-10 h-6 bg-gradient-to-b from-bg-raised to-transparent"
|
||||||
</button>
|
/>
|
||||||
</ButtonStyled>
|
</Transition>
|
||||||
|
|
||||||
<div v-if="scrollable" class="relative flex-1 min-h-0 flex flex-col">
|
|
||||||
<Transition
|
|
||||||
enter-active-class="transition-all duration-200 ease-out"
|
|
||||||
enter-from-class="opacity-0 max-h-0"
|
|
||||||
enter-to-class="opacity-100 max-h-6"
|
|
||||||
leave-active-class="transition-all duration-200 ease-in"
|
|
||||||
leave-from-class="opacity-100 max-h-6"
|
|
||||||
leave-to-class="opacity-0 max-h-0"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
v-if="showTopFade"
|
ref="scrollContainer"
|
||||||
class="pointer-events-none absolute left-0 right-0 top-0 z-10 h-6 bg-gradient-to-b from-bg-raised to-transparent"
|
:class="[
|
||||||
/>
|
'flex-1 min-h-0',
|
||||||
</Transition>
|
props.noPadding ? '' : 'overflow-y-auto p-6 !pb-1 sm:pb-6',
|
||||||
|
{ 'pt-12': props.mergeHeader && closable && !props.noPadding },
|
||||||
|
]"
|
||||||
|
:style="props.noPadding ? {} : { maxHeight: maxContentHeight }"
|
||||||
|
@scroll="checkScrollState"
|
||||||
|
>
|
||||||
|
<slot> You just lost the game.</slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition-all duration-200 ease-out"
|
||||||
|
enter-from-class="opacity-0 max-h-0"
|
||||||
|
enter-to-class="opacity-100 max-h-6"
|
||||||
|
leave-active-class="transition-all duration-200 ease-in"
|
||||||
|
leave-from-class="opacity-100 max-h-6"
|
||||||
|
leave-to-class="opacity-0 max-h-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="showBottomFade"
|
||||||
|
class="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-6 bg-gradient-to-t from-bg-raised to-transparent"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref="scrollContainer"
|
v-else
|
||||||
:class="[
|
:class="[
|
||||||
'flex-1 min-h-0',
|
props.noPadding ? '' : 'overflow-y-auto p-6',
|
||||||
props.noPadding ? '' : 'overflow-y-auto p-6 !pb-1 sm:pb-6',
|
|
||||||
{ 'pt-12': props.mergeHeader && closable && !props.noPadding },
|
{ 'pt-12': props.mergeHeader && closable && !props.noPadding },
|
||||||
]"
|
]"
|
||||||
:style="props.noPadding ? {} : { maxHeight: maxContentHeight }"
|
|
||||||
@scroll="checkScrollState"
|
|
||||||
>
|
>
|
||||||
<slot> You just lost the game.</slot>
|
<slot> You just lost the game.</slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Transition
|
<div v-if="$slots.actions" class="p-4 pt-0">
|
||||||
enter-active-class="transition-all duration-200 ease-out"
|
<slot name="actions" />
|
||||||
enter-from-class="opacity-0 max-h-0"
|
</div>
|
||||||
enter-to-class="opacity-100 max-h-6"
|
|
||||||
leave-active-class="transition-all duration-200 ease-in"
|
|
||||||
leave-from-class="opacity-100 max-h-6"
|
|
||||||
leave-to-class="opacity-0 max-h-0"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="showBottomFade"
|
|
||||||
class="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-6 bg-gradient-to-t from-bg-raised to-transparent"
|
|
||||||
/>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
:class="[
|
|
||||||
props.noPadding ? '' : 'overflow-y-auto p-6',
|
|
||||||
{ 'pt-12': props.mergeHeader && closable && !props.noPadding },
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<slot> You just lost the game.</slot>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="$slots.actions" class="p-4 pt-0">
|
|
||||||
<slot name="actions" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
|
|||||||
const showStopButton = computed(() => isRunning.value || isStarting.value)
|
const showStopButton = computed(() => isRunning.value || isStarting.value)
|
||||||
|
|
||||||
const busyTooltip = computed(() => {
|
const busyTooltip = computed(() => {
|
||||||
if (isStopping.value) return 'Server is currently stopping'
|
|
||||||
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
|
||||||
})
|
})
|
||||||
@@ -37,6 +36,8 @@ export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
|
|||||||
case 'running':
|
case 'running':
|
||||||
case 'starting':
|
case 'starting':
|
||||||
return 'Restart'
|
return 'Restart'
|
||||||
|
case 'stopping':
|
||||||
|
return 'Stopping'
|
||||||
default:
|
default:
|
||||||
return 'Start'
|
return 'Start'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,16 +194,16 @@ function formatLog4jLines(event: Log4jEvent): LogLine[] {
|
|||||||
const message = event.message?.trim() ?? ''
|
const message = event.message?.trim() ?? ''
|
||||||
|
|
||||||
const prefix = time ? `${time} [${thread}/${levelStr}]: ` : `[${thread}/${levelStr}]: `
|
const prefix = time ? `${time} [${thread}/${levelStr}]: ` : `[${thread}/${levelStr}]: `
|
||||||
const messageLines = message.split(/\r?\n/)
|
const messageLines = message.split(/[\r\n]+/)
|
||||||
const lines: LogLine[] = [{ text: prefix + messageLines[0], level }]
|
const lines: LogLine[] = [{ text: prefix + messageLines[0], level }]
|
||||||
for (let i = 1; i < messageLines.length; i++) {
|
for (let i = 1; i < messageLines.length; i++) {
|
||||||
if (!messageLines[i].trim()) continue
|
if (!messageLines[i]) continue
|
||||||
lines.push({ text: messageLines[i], level })
|
lines.push({ text: messageLines[i], level })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.throwable) {
|
if (event.throwable) {
|
||||||
for (const line of event.throwable.split(/\r?\n/)) {
|
for (const line of event.throwable.split(/[\r\n]+/)) {
|
||||||
if (!line.trim()) continue
|
if (!line) continue
|
||||||
lines.push({ text: line, level: 'error' })
|
lines.push({ text: line, level: 'error' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,6 +218,9 @@ function textToLogLine(text: string): LogLine {
|
|||||||
export function createConsoleState() {
|
export function createConsoleState() {
|
||||||
const archive = new ColumnarRingBuffer(ARCHIVE_CAPACITY)
|
const archive = new ColumnarRingBuffer(ARCHIVE_CAPACITY)
|
||||||
const output: Ref<LogLine[]> = shallowRef<LogLine[]>([])
|
const output: Ref<LogLine[]> = shallowRef<LogLine[]>([])
|
||||||
|
const WS_EVENT_HISTORY_MAX = 25000
|
||||||
|
const wsEventHistory: unknown[] = []
|
||||||
|
let wsEventCaptureEnabled = false
|
||||||
|
|
||||||
let lineBuffer: LogLine[] = []
|
let lineBuffer: LogLine[] = []
|
||||||
let batchTimer: NodeJS.Timeout | null = null
|
let batchTimer: NodeJS.Timeout | null = null
|
||||||
@@ -283,10 +286,25 @@ export function createConsoleState() {
|
|||||||
addLines(formatLog4jLines(event))
|
addLines(formatLog4jLines(event))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const recordWsEvent = (event: unknown): void => {
|
||||||
|
if (!wsEventCaptureEnabled) return
|
||||||
|
wsEventHistory.push(event)
|
||||||
|
if (wsEventHistory.length > WS_EVENT_HISTORY_MAX) {
|
||||||
|
wsEventHistory.splice(0, wsEventHistory.length - WS_EVENT_HISTORY_MAX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWsEventHistory = (): unknown[] => wsEventHistory.slice()
|
||||||
|
|
||||||
|
const setWsEventCaptureEnabled = (enabled: boolean): void => {
|
||||||
|
wsEventCaptureEnabled = enabled
|
||||||
|
if (!enabled) wsEventHistory.length = 0
|
||||||
|
}
|
||||||
|
|
||||||
const addLegacyLog = (message: string): void => {
|
const addLegacyLog = (message: string): void => {
|
||||||
const logLines = message
|
const logLines = message
|
||||||
.split('\n')
|
.split(/[\r\n]+/)
|
||||||
.filter((l) => l.trim())
|
.filter((l) => l)
|
||||||
.map(textToLogLine)
|
.map(textToLogLine)
|
||||||
|
|
||||||
let parentLevel: LogLevel | null = null
|
let parentLevel: LogLevel | null = null
|
||||||
@@ -306,6 +324,7 @@ export function createConsoleState() {
|
|||||||
archive.clear()
|
archive.clear()
|
||||||
output.value = []
|
output.value = []
|
||||||
lineBuffer = []
|
lineBuffer = []
|
||||||
|
wsEventHistory.length = 0
|
||||||
wrapCount = 0
|
wrapCount = 0
|
||||||
if (batchTimer) {
|
if (batchTimer) {
|
||||||
clearTimeout(batchTimer)
|
clearTimeout(batchTimer)
|
||||||
@@ -343,6 +362,9 @@ export function createConsoleState() {
|
|||||||
addLines,
|
addLines,
|
||||||
addLog4jEvent,
|
addLog4jEvent,
|
||||||
addLegacyLog,
|
addLegacyLog,
|
||||||
|
recordWsEvent,
|
||||||
|
getWsEventHistory,
|
||||||
|
setWsEventCaptureEnabled,
|
||||||
clear,
|
clear,
|
||||||
__debugStats,
|
__debugStats,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,11 +198,13 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
|
|||||||
|
|
||||||
const handleLog = (data: Archon.Websocket.v0.WSLogEvent) => {
|
const handleLog = (data: Archon.Websocket.v0.WSLogEvent) => {
|
||||||
if (!shouldProcessEvent()) return
|
if (!shouldProcessEvent()) return
|
||||||
|
modrinthServersConsole.recordWsEvent({ event: 'log', ...data })
|
||||||
modrinthServersConsole.addLegacyLog(data.message)
|
modrinthServersConsole.addLegacyLog(data.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLog4j = (data: Archon.Websocket.v0.WSLog4jEvent) => {
|
const handleLog4j = (data: Archon.Websocket.v0.WSLog4jEvent) => {
|
||||||
if (!shouldProcessEvent()) return
|
if (!shouldProcessEvent()) return
|
||||||
|
modrinthServersConsole.recordWsEvent({ event: 'log4j', ...data })
|
||||||
modrinthServersConsole.addLog4jEvent(data)
|
modrinthServersConsole.addLog4jEvent(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,14 @@
|
|||||||
page. (WebSocket Authentication Failed)
|
page. (WebSocket Authentication Failed)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="showAdvancedDebugInfo"
|
||||||
|
class="self-start rounded-lg bg-surface-3 px-3 py-1 text-sm text-contrast hover:brightness-125"
|
||||||
|
@click="downloadLog4jDebug"
|
||||||
|
>
|
||||||
|
Download WS debug JSON
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -37,6 +45,15 @@ import { injectModrinthClient, injectModrinthServerContext } from '#ui/providers
|
|||||||
|
|
||||||
import ServerManageStats from './components/ServerManageStats.vue'
|
import ServerManageStats from './components/ServerManageStats.vue'
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
showAdvancedDebugInfo?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
showAdvancedDebugInfo: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const client = injectModrinthClient()
|
const client = injectModrinthClient()
|
||||||
const {
|
const {
|
||||||
server: _serverData,
|
server: _serverData,
|
||||||
@@ -49,6 +66,14 @@ const {
|
|||||||
} = injectModrinthServerContext()
|
} = injectModrinthServerContext()
|
||||||
const modrinthServersConsole = useModrinthServersConsole()
|
const modrinthServersConsole = useModrinthServersConsole()
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.showAdvancedDebugInfo,
|
||||||
|
(enabled) => {
|
||||||
|
modrinthServersConsole.setWsEventCaptureEnabled(enabled)
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
const crashAnalysis = ref<Mclogs.Insights.v1.InsightsResponse | null>(null)
|
const crashAnalysis = ref<Mclogs.Insights.v1.InsightsResponse | null>(null)
|
||||||
const DISMISS_DURATION_MS = 30 * 60 * 1000
|
const DISMISS_DURATION_MS = 30 * 60 * 1000
|
||||||
const dismissedUntil = useStorage(`modrinth-crash-dismissed-${serverId}`, 0)
|
const dismissedUntil = useStorage(`modrinth-crash-dismissed-${serverId}`, 0)
|
||||||
@@ -120,4 +145,17 @@ watch(
|
|||||||
if (serverPowerState.value === 'crashed') {
|
if (serverPowerState.value === 'crashed') {
|
||||||
void inspectError()
|
void inspectError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const downloadLog4jDebug = () => {
|
||||||
|
const events = modrinthServersConsole.getWsEventHistory()
|
||||||
|
const blob = new Blob([JSON.stringify(events, null, 2)], { type: 'application/json' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = `ws-debug-${serverId}-${Date.now()}.json`
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user