1 parent
c54acd2e36
commit
79addd4aba
14 files changed
+831
-2
No files matched your search
@@ -0,0 +1,192 @@
|
||||
<script setup lang="ts">
|
||||
import { DownloadIcon, FolderOpenIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Combobox,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
injectPopupNotificationManager,
|
||||
NewModal,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { exportLauncherLogs, highlightInFolder } from '@/helpers/utils'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { handleError } = injectNotificationManager()
|
||||
const popupNotificationManager = injectPopupNotificationManager()
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>()
|
||||
const exporting = ref(false)
|
||||
const selectedRange = ref<'last30Minutes' | 'last2Hours' | 'all'>('last2Hours')
|
||||
const selectedLevel = ref<'all' | 'debug' | 'info'>('all')
|
||||
const includeSystemInfo = ref(true)
|
||||
const includeInstanceLogs = ref(false)
|
||||
const includeCrashAnalysis = ref(false)
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'app.settings.logs.export.modal.title', defaultMessage: 'Export logs' },
|
||||
description: {
|
||||
id: 'app.settings.logs.export.modal.description',
|
||||
defaultMessage:
|
||||
'Choose how much detail to include. Exported logs have access tokens, Minecraft tokens, and IP addresses replaced with placeholders.',
|
||||
},
|
||||
rangeLabel: { id: 'app.settings.logs.export.modal.range', defaultMessage: 'Time range' },
|
||||
rangeLast30Minutes: {
|
||||
id: 'app.settings.logs.export.modal.range.30-minutes',
|
||||
defaultMessage: 'Last 30 minutes',
|
||||
},
|
||||
rangeLast2Hours: {
|
||||
id: 'app.settings.logs.export.modal.range.2-hours',
|
||||
defaultMessage: 'Last 2 hours',
|
||||
},
|
||||
rangeAll: { id: 'app.settings.logs.export.modal.range.all', defaultMessage: 'All sessions' },
|
||||
levelLabel: { id: 'app.settings.logs.export.modal.level', defaultMessage: 'Log level' },
|
||||
levelAll: { id: 'app.settings.logs.export.modal.level.all', defaultMessage: 'All levels' },
|
||||
levelDebug: {
|
||||
id: 'app.settings.logs.export.modal.level.debug',
|
||||
defaultMessage: 'Debug and above',
|
||||
},
|
||||
levelInfo: { id: 'app.settings.logs.export.modal.level.info', defaultMessage: 'Info and above' },
|
||||
extrasLabel: {
|
||||
id: 'app.settings.logs.export.modal.extras',
|
||||
defaultMessage: 'Additional information',
|
||||
},
|
||||
systemInfo: {
|
||||
id: 'app.settings.logs.export.modal.system-info',
|
||||
defaultMessage: 'System and environment summary',
|
||||
},
|
||||
instanceLogs: {
|
||||
id: 'app.settings.logs.export.modal.instance-logs',
|
||||
defaultMessage: 'Most recent instance log',
|
||||
},
|
||||
crashAnalysis: {
|
||||
id: 'app.settings.logs.export.modal.crash-analysis',
|
||||
defaultMessage: 'Crash analysis (when a crash is detected)',
|
||||
},
|
||||
cancel: { id: 'app.settings.logs.export.modal.cancel', defaultMessage: 'Cancel' },
|
||||
confirm: { id: 'app.settings.logs.export.modal.confirm', defaultMessage: 'Export' },
|
||||
exportedTitle: {
|
||||
id: 'app.settings.logs.export.notification-title',
|
||||
defaultMessage: 'Logs exported',
|
||||
},
|
||||
exportedText: {
|
||||
id: 'app.settings.logs.export.notification-text',
|
||||
defaultMessage: 'The log archive was saved to {path}',
|
||||
},
|
||||
})
|
||||
|
||||
const rangeOptions = computed(() => [
|
||||
{ value: 'last30Minutes', label: formatMessage(messages.rangeLast30Minutes) },
|
||||
{ value: 'last2Hours', label: formatMessage(messages.rangeLast2Hours) },
|
||||
{ value: 'all', label: formatMessage(messages.rangeAll) },
|
||||
])
|
||||
|
||||
const levelOptions = computed(() => [
|
||||
{ value: 'all', label: formatMessage(messages.levelAll) },
|
||||
{ value: 'debug', label: formatMessage(messages.levelDebug) },
|
||||
{ value: 'info', label: formatMessage(messages.levelInfo) },
|
||||
])
|
||||
|
||||
async function exportLogs() {
|
||||
exporting.value = true
|
||||
try {
|
||||
const path = await exportLauncherLogs({
|
||||
range: selectedRange.value,
|
||||
level: selectedLevel.value,
|
||||
includeSystemInfo: includeSystemInfo.value,
|
||||
includeInstanceLogs: includeInstanceLogs.value,
|
||||
includeCrashAnalysis: includeCrashAnalysis.value,
|
||||
})
|
||||
|
||||
if (path) {
|
||||
modal.value?.hide()
|
||||
popupNotificationManager.addPopupNotification({
|
||||
title: formatMessage(messages.exportedTitle),
|
||||
text: formatMessage(messages.exportedText, { path }),
|
||||
type: 'success',
|
||||
buttons: [
|
||||
{
|
||||
label: formatMessage(commonMessages.openInFolderButton),
|
||||
icon: FolderOpenIcon,
|
||||
action: () => highlightInFolder(path).catch(handleError),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
} finally {
|
||||
exporting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show: () => modal.value?.show(),
|
||||
hide: () => modal.value?.hide(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NewModal
|
||||
ref="modal"
|
||||
:header="formatMessage(messages.title)"
|
||||
:closable="true"
|
||||
:close-on-click-outside="true"
|
||||
width="40rem"
|
||||
scrollable
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<p class="m-0">{{ formatMessage(messages.description) }}</p>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-[var(--color-text-primary)]">{{
|
||||
formatMessage(messages.rangeLabel)
|
||||
}}</span>
|
||||
<Combobox
|
||||
id="log-export-range"
|
||||
v-model="selectedRange"
|
||||
:name="formatMessage(messages.rangeLabel)"
|
||||
:options="rangeOptions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-[var(--color-text-primary)]">{{
|
||||
formatMessage(messages.levelLabel)
|
||||
}}</span>
|
||||
<Combobox
|
||||
id="log-export-level"
|
||||
v-model="selectedLevel"
|
||||
:name="formatMessage(messages.levelLabel)"
|
||||
:options="levelOptions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-[var(--color-text-primary)]">{{
|
||||
formatMessage(messages.extrasLabel)
|
||||
}}</span>
|
||||
<Checkbox v-model="includeSystemInfo" :label="formatMessage(messages.systemInfo)" />
|
||||
<Checkbox v-model="includeInstanceLogs" :label="formatMessage(messages.instanceLogs)" />
|
||||
<Checkbox v-model="includeCrashAnalysis" :label="formatMessage(messages.crashAnalysis)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #actions>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button type="outlined" :disabled="exporting" @click="modal?.hide()"
|
||||
><XIcon />
|
||||
{{ formatMessage(messages.cancel) }}
|
||||
</Button>
|
||||
<Button type="colored" color="brand" :disabled="exporting" @click="exportLogs"
|
||||
><DownloadIcon />
|
||||
{{ formatMessage(messages.confirm) }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</NewModal>
|
||||
</template>
|
||||
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { FolderOpenIcon, UploadIcon } from '@modrinth/assets'
|
||||
import { Button, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { showLauncherLogsFolder } from '@/helpers/utils'
|
||||
|
||||
import LogExportModal from './LogExportModal.vue'
|
||||
import SettingsRow from './SettingsRow.vue'
|
||||
import SettingsSection from './SettingsSection.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const exportModal = ref<InstanceType<typeof LogExportModal>>()
|
||||
|
||||
const messages = defineMessages({
|
||||
exportTitle: { id: 'app.settings.logs.export.title', defaultMessage: 'Export logs' },
|
||||
exportDescription: {
|
||||
id: 'app.settings.logs.export.description',
|
||||
defaultMessage:
|
||||
'Package launcher logs with an optional environment summary, instance log, and crash analysis.',
|
||||
},
|
||||
exportButton: { id: 'app.settings.logs.export.button', defaultMessage: 'Export logs…' },
|
||||
openFolder: { id: 'app.settings.logs.open-folder', defaultMessage: 'Open logs folder' },
|
||||
})
|
||||
|
||||
async function openLogsFolder() {
|
||||
try {
|
||||
await showLauncherLogsFolder()
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-6">
|
||||
<SettingsSection
|
||||
:title="formatMessage(messages.exportTitle)"
|
||||
:description="formatMessage(messages.exportDescription)"
|
||||
title-id="settings-target-logs-export"
|
||||
>
|
||||
<SettingsRow stacked>
|
||||
<template #label>
|
||||
<span>{{ formatMessage(messages.exportTitle) }}</span>
|
||||
</template>
|
||||
<template #description>{{ formatMessage(messages.exportDescription) }}</template>
|
||||
<template #control>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Button type="colored" color="brand" @click="exportModal?.show()"
|
||||
><UploadIcon />
|
||||
{{ formatMessage(messages.exportButton) }}
|
||||
</Button>
|
||||
<Button @click="openLogsFolder"
|
||||
><FolderOpenIcon />
|
||||
{{ formatMessage(messages.openFolder) }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</SettingsRow>
|
||||
</SettingsSection>
|
||||
|
||||
<LogExportModal ref="exportModal" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -13,6 +13,7 @@ export type SettingsCategoryId =
|
||||
| 'updates'
|
||||
| 'about'
|
||||
| 'feature-flags'
|
||||
| 'logs'
|
||||
|
||||
export type SettingsGroupId = 'launcher' | 'game' | 'data-privacy' | 'support' | 'developer'
|
||||
|
||||
@@ -116,6 +117,11 @@ export const settingsCategoryDefinitions: SettingsCategoryDefinition[] = [
|
||||
name: { id: 'app.settings.tabs.about', defaultMessage: 'About' },
|
||||
group: 'support',
|
||||
},
|
||||
{
|
||||
id: 'logs',
|
||||
name: { id: 'app.settings.tabs.logs', defaultMessage: 'Logs' },
|
||||
group: 'support',
|
||||
},
|
||||
{
|
||||
id: 'feature-flags',
|
||||
name: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
BotIcon,
|
||||
CoffeeIcon,
|
||||
CpuIcon,
|
||||
FileTextIcon,
|
||||
GameIcon,
|
||||
GaugeIcon,
|
||||
InfoIcon,
|
||||
@@ -78,6 +79,10 @@ const categoryContent: Record<SettingsCategoryId, Pick<SettingsCategory, 'icon'
|
||||
content: defineAsyncComponent(() => import('./UpdateSettings.vue')),
|
||||
},
|
||||
about: { icon: InfoIcon, content: defineAsyncComponent(() => import('./AboutSettings.vue')) },
|
||||
logs: {
|
||||
icon: FileTextIcon,
|
||||
content: defineAsyncComponent(() => import('./LogsSettings.vue')),
|
||||
},
|
||||
'feature-flags': {
|
||||
icon: ToggleRightIcon,
|
||||
content: defineAsyncComponent(() => import('./FeatureFlagSettings.vue')),
|
||||
|
||||
@@ -384,6 +384,16 @@ export const settingsSearchEntries: SettingsSearchEntry[] = [
|
||||
categoryId: 'feature-flags',
|
||||
label: message('settings.feature-flags.title', 'Feature flags'),
|
||||
},
|
||||
{
|
||||
id: 'logs-export',
|
||||
categoryId: 'logs',
|
||||
targetId: 'settings-target-logs-export',
|
||||
label: message('app.settings.logs.export.title', 'Export logs'),
|
||||
description: message(
|
||||
'app.settings.logs.export.description',
|
||||
'Package launcher logs with an optional environment summary, instance log, and crash analysis.',
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
export function validateSettingsSearchEntries(entries = settingsSearchEntries): string[] {
|
||||
|
||||
@@ -81,6 +81,38 @@ export async function exportErrorLogs(
|
||||
return await invoke('plugin:utils|export_error_logs', { outputPath, errorMessage })
|
||||
}
|
||||
|
||||
export async function exportLauncherLogs({
|
||||
range = 'last2Hours',
|
||||
level = 'all',
|
||||
includeSystemInfo = true,
|
||||
includeInstanceLogs = false,
|
||||
includeCrashAnalysis = false,
|
||||
fileNamePrefix = 'Starlight Launcher logs',
|
||||
} = {}) {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
||||
const outputPath = await save({
|
||||
defaultPath: `${fileNamePrefix} ${timestamp}.zip`,
|
||||
filters: [
|
||||
{
|
||||
name: 'ZIP archive',
|
||||
extensions: ['zip'],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (!outputPath) return null
|
||||
|
||||
await invoke('plugin:utils|export_launcher_logs', {
|
||||
outputPath,
|
||||
range,
|
||||
level,
|
||||
includeSystemInfo,
|
||||
includeInstanceLogs,
|
||||
includeCrashAnalysis,
|
||||
})
|
||||
return outputPath
|
||||
}
|
||||
|
||||
export async function createInstanceShortcut(instanceName, instanceId, options = {}) {
|
||||
const outputPath = await save({
|
||||
defaultPath: `Modrinth - ${instanceName}`,
|
||||
|
||||
@@ -11461,5 +11461,74 @@
|
||||
},
|
||||
"app.remote-announcements.preview-action": {
|
||||
"message": "Visit website"
|
||||
},
|
||||
"app.settings.tabs.logs": {
|
||||
"message": "Logs & diagnostics"
|
||||
},
|
||||
"app.settings.logs.export.title": {
|
||||
"message": "Export logs"
|
||||
},
|
||||
"app.settings.logs.export.description": {
|
||||
"message": "Package launcher logs with an optional environment summary, instance log, and crash analysis."
|
||||
},
|
||||
"app.settings.logs.export.button": {
|
||||
"message": "Export logs…"
|
||||
},
|
||||
"app.settings.logs.open-folder": {
|
||||
"message": "Open logs folder"
|
||||
},
|
||||
"app.settings.logs.export.modal.title": {
|
||||
"message": "Export logs"
|
||||
},
|
||||
"app.settings.logs.export.modal.description": {
|
||||
"message": "Choose how much detail to include. Exported logs have access tokens, Minecraft tokens, and IP addresses replaced with placeholders."
|
||||
},
|
||||
"app.settings.logs.export.modal.range": {
|
||||
"message": "Time range"
|
||||
},
|
||||
"app.settings.logs.export.modal.range.30-minutes": {
|
||||
"message": "Last 30 minutes"
|
||||
},
|
||||
"app.settings.logs.export.modal.range.2-hours": {
|
||||
"message": "Last 2 hours"
|
||||
},
|
||||
"app.settings.logs.export.modal.range.all": {
|
||||
"message": "All sessions"
|
||||
},
|
||||
"app.settings.logs.export.modal.level": {
|
||||
"message": "Log level"
|
||||
},
|
||||
"app.settings.logs.export.modal.level.all": {
|
||||
"message": "All levels"
|
||||
},
|
||||
"app.settings.logs.export.modal.level.debug": {
|
||||
"message": "Debug and above"
|
||||
},
|
||||
"app.settings.logs.export.modal.level.info": {
|
||||
"message": "Info and above"
|
||||
},
|
||||
"app.settings.logs.export.modal.extras": {
|
||||
"message": "Additional information"
|
||||
},
|
||||
"app.settings.logs.export.modal.system-info": {
|
||||
"message": "System and environment summary"
|
||||
},
|
||||
"app.settings.logs.export.modal.instance-logs": {
|
||||
"message": "Most recent instance log"
|
||||
},
|
||||
"app.settings.logs.export.modal.crash-analysis": {
|
||||
"message": "Crash analysis (when a crash is detected)"
|
||||
},
|
||||
"app.settings.logs.export.modal.cancel": {
|
||||
"message": "Cancel"
|
||||
},
|
||||
"app.settings.logs.export.modal.confirm": {
|
||||
"message": "Export"
|
||||
},
|
||||
"app.settings.logs.export.notification-title": {
|
||||
"message": "Logs exported"
|
||||
},
|
||||
"app.settings.logs.export.notification-text": {
|
||||
"message": "The log archive was saved to {path}"
|
||||
}
|
||||
}
|
||||
@@ -11354,5 +11354,28 @@
|
||||
"app.anniversary.banner": { "message": "Starlight {ordinal} 周年快乐!" },
|
||||
"app.action-bar.announcements": { "message": "公告" },
|
||||
"app.remote-announcements.center-title": { "message": "公告" },
|
||||
"app.remote-announcements.empty": { "message": "暂无公告" }
|
||||
"app.remote-announcements.empty": { "message": "暂无公告" },
|
||||
"app.settings.tabs.logs": { "message": "日志与诊断" },
|
||||
"app.settings.logs.export.title": { "message": "导出日志" },
|
||||
"app.settings.logs.export.description": { "message": "将启动器日志打包,可附带环境摘要、最近的实例日志与崩溃分析。" },
|
||||
"app.settings.logs.export.button": { "message": "导出日志…" },
|
||||
"app.settings.logs.open-folder": { "message": "打开日志文件夹" },
|
||||
"app.settings.logs.export.modal.title": { "message": "导出日志" },
|
||||
"app.settings.logs.export.modal.description": { "message": "选择要包含的详细程度。导出的日志会把访问令牌、Minecraft 令牌与 IP 地址替换为占位符。" },
|
||||
"app.settings.logs.export.modal.range": { "message": "时间范围" },
|
||||
"app.settings.logs.export.modal.range.30-minutes": { "message": "最近 30 分钟" },
|
||||
"app.settings.logs.export.modal.range.2-hours": { "message": "最近 2 小时" },
|
||||
"app.settings.logs.export.modal.range.all": { "message": "全部会话" },
|
||||
"app.settings.logs.export.modal.level": { "message": "日志等级" },
|
||||
"app.settings.logs.export.modal.level.all": { "message": "全部等级" },
|
||||
"app.settings.logs.export.modal.level.debug": { "message": "DEBUG 及以上" },
|
||||
"app.settings.logs.export.modal.level.info": { "message": "INFO 及以上" },
|
||||
"app.settings.logs.export.modal.extras": { "message": "附加信息" },
|
||||
"app.settings.logs.export.modal.system-info": { "message": "系统与环境摘要" },
|
||||
"app.settings.logs.export.modal.instance-logs": { "message": "最近的实例日志" },
|
||||
"app.settings.logs.export.modal.crash-analysis": { "message": "崩溃分析(检测到崩溃时)" },
|
||||
"app.settings.logs.export.modal.cancel": { "message": "取消" },
|
||||
"app.settings.logs.export.modal.confirm": { "message": "导出" },
|
||||
"app.settings.logs.export.notification-title": { "message": "日志已导出" },
|
||||
"app.settings.logs.export.notification-text": { "message": "日志压缩包已保存到 {path}" }
|
||||
}
|
||||
Reference in new issue
Block a user