249 lines
5.8 KiB
Vue
249 lines
5.8 KiB
Vue
<template>
|
|
<div class="flex flex-col gap-4 h-full">
|
|
<ConsolePageLayout />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {
|
|
ConsolePageLayout,
|
|
defineMessages,
|
|
injectNotificationManager,
|
|
provideConsoleManager,
|
|
useVIntl,
|
|
} from '@modrinth/ui'
|
|
import { computed, onUnmounted, ref, shallowRef, triggerRef, watch, watchEffect } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
|
|
import { useCrashAnalysis } from '@/composables/useCrashAnalysis'
|
|
import { useInstanceConsole } from '@/composables/useInstanceConsole'
|
|
import { log_listener, process_listener } from '@/helpers/events.js'
|
|
import {
|
|
delete_logs_by_filename,
|
|
export_crash_context,
|
|
get_output_by_filename,
|
|
} from '@/helpers/logs.js'
|
|
|
|
const { handleError } = injectNotificationManager()
|
|
const route = useRoute()
|
|
const { formatMessage } = useVIntl()
|
|
|
|
const messages = defineMessages({
|
|
liveLog: { id: 'instance.logs.source.live', defaultMessage: 'Live Log' },
|
|
unknownLog: { id: 'instance.logs.source.unknown', defaultMessage: 'Unknown' },
|
|
logName: { id: 'instance.logs.source.numbered', defaultMessage: 'Log {index}' },
|
|
cannotDeleteLatest: {
|
|
id: 'instance.logs.delete.latest-running',
|
|
defaultMessage: 'Cannot delete latest.log while the instance is running',
|
|
},
|
|
})
|
|
|
|
const props = defineProps({
|
|
instance: {
|
|
type: Object,
|
|
default() {
|
|
return {}
|
|
},
|
|
},
|
|
options: {
|
|
type: Object,
|
|
default() {
|
|
return {}
|
|
},
|
|
},
|
|
offline: {
|
|
type: Boolean,
|
|
default() {
|
|
return false
|
|
},
|
|
},
|
|
playing: {
|
|
type: Boolean,
|
|
default() {
|
|
return false
|
|
},
|
|
},
|
|
installed: {
|
|
type: Boolean,
|
|
default() {
|
|
return false
|
|
},
|
|
},
|
|
})
|
|
|
|
const instanceId = computed(() => route.params.id)
|
|
const {
|
|
analysis: localCrashAnalysis,
|
|
loading: crashAnalysisLoading,
|
|
refresh: refreshCrashAnalysis,
|
|
clear: clearCrashAnalysis,
|
|
} = useCrashAnalysis(instanceId.value)
|
|
const {
|
|
liveConsole,
|
|
historicalConsole,
|
|
hydrate,
|
|
getHistoricalLogs,
|
|
getHistoricalContent,
|
|
invalidate,
|
|
clearLive,
|
|
} = useInstanceConsole(instanceId.value)
|
|
|
|
await hydrate()
|
|
|
|
function buildLogList(rawLogs) {
|
|
return [
|
|
{ name: formatMessage(messages.liveLog), live: true },
|
|
...rawLogs
|
|
.filter(
|
|
(log) =>
|
|
log.filename !== 'latest_stdout.log' &&
|
|
log.filename !== 'latest_stdout' &&
|
|
log.filename !== 'launcher_log.txt' &&
|
|
(log.output == null || log.output !== '') &&
|
|
(log.filename.includes('.log') || log.filename.endsWith('.txt')),
|
|
)
|
|
.map((log) => ({
|
|
...log,
|
|
name: log.filename || formatMessage(messages.unknownLog),
|
|
})),
|
|
]
|
|
}
|
|
|
|
const logs = ref(buildLogList([]))
|
|
|
|
void getHistoricalLogs()
|
|
.then((allLogs) => {
|
|
logs.value = buildLogList(allLogs)
|
|
})
|
|
.catch(handleError)
|
|
|
|
const selectedLogIndex = ref(0)
|
|
const isLive = computed(() => selectedLogIndex.value === 0)
|
|
|
|
const filteredLogs = computed(() =>
|
|
props.playing ? logs.value.filter((l) => l.live || l.name !== 'latest.log') : logs.value,
|
|
)
|
|
|
|
const logSources = computed(() =>
|
|
filteredLogs.value.map((l, i) => ({
|
|
id: String(i),
|
|
name: l?.name ?? formatMessage(messages.logName, { index: i }),
|
|
live: l?.live ?? false,
|
|
})),
|
|
)
|
|
|
|
const activeConsole = computed(() => (isLive.value ? liveConsole : historicalConsole))
|
|
|
|
const logLines = shallowRef(activeConsole.value.output.value)
|
|
watchEffect(() => {
|
|
logLines.value = activeConsole.value.output.value
|
|
triggerRef(logLines)
|
|
})
|
|
|
|
async function analyseForCrash() {
|
|
await refreshCrashAnalysis().catch((error) => {
|
|
handleError(error)
|
|
})
|
|
}
|
|
|
|
async function exportCrashContext() {
|
|
await export_crash_context(props.instance.id, props.instance.name).catch(handleError)
|
|
}
|
|
|
|
const selectedLog = computed(() => filteredLogs.value[selectedLogIndex.value])
|
|
|
|
const deleteDisabled = computed(() => {
|
|
const log = selectedLog.value
|
|
if (!log || log.live) return true
|
|
return log.filename === 'latest.log' && props.playing
|
|
})
|
|
|
|
async function deleteSelectedLog() {
|
|
const log = selectedLog.value
|
|
if (!log || log.live) return
|
|
await delete_logs_by_filename(props.instance.id, log.log_type, log.filename)
|
|
invalidate()
|
|
const freshLogs = await getHistoricalLogs()
|
|
logs.value = buildLogList(freshLogs)
|
|
selectedLogIndex.value = 0
|
|
}
|
|
|
|
provideConsoleManager({
|
|
logLines,
|
|
logSources,
|
|
activeLogSourceIndex: selectedLogIndex,
|
|
showCommandInput: false,
|
|
loading: ref(false),
|
|
onClear: () => {
|
|
if (!isLive.value) return
|
|
void clearLive()
|
|
},
|
|
onDelete: deleteSelectedLog,
|
|
deleteDisabled,
|
|
deleteDisabledTooltip: computed(() => formatMessage(messages.cannotDeleteLatest)),
|
|
shareDisabled: computed(() => props.offline),
|
|
emptyStateType: 'instance',
|
|
localCrashAnalysis,
|
|
crashAnalysisLoading,
|
|
onExportCrashContext: exportCrashContext,
|
|
})
|
|
|
|
watch(selectedLogIndex, async (newIndex) => {
|
|
if (newIndex === 0) return
|
|
const log = filteredLogs.value[newIndex]
|
|
if (!log) return
|
|
|
|
const cached = getHistoricalContent(log.filename)
|
|
if (cached) {
|
|
historicalConsole.clear()
|
|
await historicalConsole.addLegacyLog(cached)
|
|
return
|
|
}
|
|
|
|
const output = await get_output_by_filename(props.instance.id, log.log_type, log.filename).catch(
|
|
handleError,
|
|
)
|
|
if (output) {
|
|
historicalConsole.clear()
|
|
await historicalConsole.addLegacyLog(output)
|
|
}
|
|
})
|
|
|
|
selectedLogIndex.value = 0
|
|
|
|
if (!props.playing) {
|
|
void analyseForCrash()
|
|
}
|
|
|
|
const unlistenLog = await log_listener((payload) => {
|
|
if (payload.instance_id !== instanceId.value) return
|
|
|
|
if (payload.type === 'log4j') {
|
|
liveConsole.addLog4jEvent(payload)
|
|
} else if (payload.type === 'legacy') {
|
|
void liveConsole.addLegacyLog(payload.message)
|
|
}
|
|
})
|
|
|
|
const unlistenProcesses = await process_listener(async (e) => {
|
|
if (e.instance_id !== instanceId.value) return
|
|
if (e.event === 'launched') {
|
|
liveConsole.clear()
|
|
clearCrashAnalysis()
|
|
invalidate()
|
|
selectedLogIndex.value = 0
|
|
}
|
|
if (e.event === 'finished') {
|
|
invalidate()
|
|
const freshLogs = await getHistoricalLogs()
|
|
logs.value = buildLogList(freshLogs)
|
|
void analyseForCrash()
|
|
}
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
unlistenLog()
|
|
unlistenProcesses()
|
|
})
|
|
</script>
|