mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-10 17:42:50 +08:00
为 PluginCard 组件添加实时日志弹窗功能
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -110,4 +110,4 @@
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/locales"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,9 @@ const progressDialog = ref(false)
|
||||
// 插件数据页面
|
||||
const pluginInfoDialog = ref(false)
|
||||
|
||||
// 实时日志弹窗
|
||||
const loggingDialog = ref(false)
|
||||
|
||||
// 进度框文本
|
||||
const progressText = ref('正在更新插件...')
|
||||
|
||||
@@ -82,7 +85,7 @@ const cloneForm = ref({
|
||||
name: '',
|
||||
description: '',
|
||||
version: '',
|
||||
icon: ''
|
||||
icon: '',
|
||||
})
|
||||
|
||||
// 监听动作标识,如为true则打开详情
|
||||
@@ -136,7 +139,12 @@ async function uninstallPlugin() {
|
||||
// 通知父组件刷新
|
||||
emit('remove')
|
||||
} else {
|
||||
$toast.error(t('plugin.uninstallFailed', { name: props.plugin?.plugin_name, message: result.message }))
|
||||
$toast.error(
|
||||
t('plugin.uninstallFailed', {
|
||||
name: props.plugin?.plugin_name,
|
||||
message: result.message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -190,7 +198,12 @@ async function resetPlugin() {
|
||||
// 通知父组件刷新
|
||||
emit('save')
|
||||
} else {
|
||||
$toast.error(t('plugin.resetFailed', { name: props.plugin?.plugin_name, message: result.message }))
|
||||
$toast.error(
|
||||
t('plugin.resetFailed', {
|
||||
name: props.plugin?.plugin_name,
|
||||
message: result.message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -221,7 +234,12 @@ async function updatePlugin() {
|
||||
// 通知父组件刷新
|
||||
emit('save')
|
||||
} else {
|
||||
$toast.error(t('plugin.updateFailed', { name: props.plugin?.plugin_name, message: result.message }))
|
||||
$toast.error(
|
||||
t('plugin.updateFailed', {
|
||||
name: props.plugin?.plugin_name,
|
||||
message: result.message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -260,7 +278,7 @@ function showPluginClone() {
|
||||
name: t('plugin.cloneDefaultName', { name: props.plugin?.plugin_name }),
|
||||
description: t('plugin.cloneDefaultDescription', { description: props.plugin?.plugin_desc }),
|
||||
version: props.plugin?.plugin_version || '1.0',
|
||||
icon: props.plugin?.plugin_icon || ''
|
||||
icon: props.plugin?.plugin_icon || '',
|
||||
}
|
||||
pluginCloneDialog.value = true
|
||||
}
|
||||
@@ -281,7 +299,7 @@ async function executePluginClone() {
|
||||
name: cloneForm.value.name.trim(),
|
||||
description: cloneForm.value.description.trim(),
|
||||
version: cloneForm.value.version.trim(),
|
||||
icon: cloneForm.value.icon.trim()
|
||||
icon: cloneForm.value.icon.trim(),
|
||||
})
|
||||
|
||||
progressDialog.value = false
|
||||
@@ -368,7 +386,7 @@ const dropdownItems = ref([
|
||||
props: {
|
||||
prependIcon: 'mdi-file-document-outline',
|
||||
click: () => {
|
||||
openLoggerWindow()
|
||||
loggingDialog.value = true
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -541,135 +559,178 @@ watch(
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- 插件分身对话框 -->
|
||||
<VDialog v-if="pluginCloneDialog" v-model="pluginCloneDialog" width="600" :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VCardTitle class="d-flex align-center pa-4">
|
||||
<VIcon icon="mdi-content-copy" class="me-3" color="primary" />
|
||||
<div>
|
||||
<div class="text-h6">🎭 {{ t('plugin.cloneTitle') }}</div>
|
||||
<div class="text-caption text-medium-emphasis">{{ t('plugin.cloneSubtitle', { name: props.plugin?.plugin_name }) }}</div>
|
||||
</div>
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="pluginCloneDialog = false" />
|
||||
<VDivider />
|
||||
<!-- 实时日志弹窗 -->
|
||||
<VDialog
|
||||
v-if="loggingDialog"
|
||||
v-model="loggingDialog"
|
||||
scrollable
|
||||
max-width="60rem"
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VDialogCloseBtn @click="loggingDialog = false" />
|
||||
<VCardItem>
|
||||
<VCardTitle class="d-inline-flex">
|
||||
<VIcon icon="mdi-file-document" class="me-2" />
|
||||
{{ t('plugin.logTitle') }}
|
||||
<a class="mx-2 d-inline-flex align-center cursor-pointer" @click="openLoggerWindow">
|
||||
<VChip color="grey-darken-1" size="small" class="ml-2">
|
||||
<VIcon icon="mdi-open-in-new" size="small" start />
|
||||
{{ t('common.openInNewWindow') }}
|
||||
</VChip>
|
||||
</a>
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<LoggingView :logfile="`plugins/${props.plugin?.id?.toLowerCase()}.log`" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<VCardText class="pa-4">
|
||||
<!-- 功能说明 -->
|
||||
<VAlert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
class="mb-4"
|
||||
icon="mdi-information-outline"
|
||||
>
|
||||
<div class="text-body-2">
|
||||
<strong>{{ t('plugin.cloneFeature') }}</strong>:{{ t('plugin.cloneDescription') }}
|
||||
</div>
|
||||
</VAlert>
|
||||
<!-- 插件分身对话框 -->
|
||||
<VDialog v-if="pluginCloneDialog" v-model="pluginCloneDialog" width="600" :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VCardTitle class="d-flex align-center pa-4">
|
||||
<VIcon icon="mdi-content-copy" class="me-3" color="primary" />
|
||||
<div>
|
||||
<div class="text-h6">🎭 {{ t('plugin.cloneTitle') }}</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
{{ t('plugin.cloneSubtitle', { name: props.plugin?.plugin_name }) }}
|
||||
</div>
|
||||
</div>
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="pluginCloneDialog = false" />
|
||||
<VDivider />
|
||||
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.suffix"
|
||||
:label="t('plugin.suffix') + ' *'"
|
||||
:placeholder="t('plugin.suffixPlaceholder')"
|
||||
:hint="t('plugin.suffixHint')"
|
||||
persistent-hint
|
||||
:rules="[
|
||||
v => !!v || t('plugin.suffixRequired'),
|
||||
v => /^[a-zA-Z0-9]+$/.test(v) || t('plugin.suffixFormatError'),
|
||||
v => v.length <= 20 || t('plugin.suffixLengthError')
|
||||
]"
|
||||
required
|
||||
prepend-inner-icon="mdi-tag"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.name"
|
||||
:label="t('plugin.cloneName')"
|
||||
:placeholder="t('plugin.cloneNamePlaceholder')"
|
||||
:hint="t('plugin.cloneNameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-rename-box"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="cloneForm.description"
|
||||
:label="t('plugin.cloneDescriptionLabel')"
|
||||
:placeholder="t('plugin.cloneDescriptionPlaceholder')"
|
||||
:hint="t('plugin.cloneDescriptionHint')"
|
||||
persistent-hint
|
||||
rows="2"
|
||||
prepend-inner-icon="mdi-text"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.version"
|
||||
:label="t('plugin.cloneVersion')"
|
||||
:placeholder="t('plugin.cloneVersionPlaceholder')"
|
||||
:hint="t('plugin.cloneVersionHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-numeric"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.icon"
|
||||
:label="t('plugin.cloneIcon')"
|
||||
:placeholder="t('plugin.cloneIconPlaceholder')"
|
||||
:hint="t('plugin.cloneIconHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-image"
|
||||
/>
|
||||
</VCol>
|
||||
<VCardText class="pa-4">
|
||||
<!-- 功能说明 -->
|
||||
<VAlert type="info" variant="tonal" density="compact" class="mb-4" icon="mdi-information-outline">
|
||||
<div class="text-body-2">
|
||||
<strong>{{ t('plugin.cloneFeature') }}</strong
|
||||
>:{{ t('plugin.cloneDescription') }}
|
||||
</div>
|
||||
</VAlert>
|
||||
|
||||
<!-- 重要提醒 -->
|
||||
<VCol cols="12">
|
||||
<VAlert
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
class="mt-2"
|
||||
icon="mdi-alert-circle-outline"
|
||||
>
|
||||
<div class="text-body-2">
|
||||
<strong>{{ t('common.notice') }}</strong>:{{ t('plugin.cloneNotice') }}
|
||||
</div>
|
||||
</VAlert>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
|
||||
<VDivider />
|
||||
<VCardActions class="pa-4">
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
@click="pluginCloneDialog = false"
|
||||
variant="outlined"
|
||||
>
|
||||
{{ t('common.cancel') }}
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="primary"
|
||||
@click="executePluginClone"
|
||||
:disabled="!cloneForm.suffix.trim()"
|
||||
>
|
||||
<VIcon icon="mdi-content-copy" class="me-2" />
|
||||
{{ t('plugin.createClone') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.suffix"
|
||||
:label="t('plugin.suffix') + ' *'"
|
||||
:placeholder="t('plugin.suffixPlaceholder')"
|
||||
:hint="t('plugin.suffixHint')"
|
||||
persistent-hint
|
||||
:rules="[
|
||||
v => !!v || t('plugin.suffixRequired'),
|
||||
v => /^[a-zA-Z0-9]+$/.test(v) || t('plugin.suffixFormatError'),
|
||||
v => v.length <= 20 || t('plugin.suffixLengthError'),
|
||||
]"
|
||||
required
|
||||
prepend-inner-icon="mdi-tag"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.name"
|
||||
:label="t('plugin.cloneName')"
|
||||
:placeholder="t('plugin.cloneNamePlaceholder')"
|
||||
:hint="t('plugin.cloneNameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-rename-box"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="cloneForm.description"
|
||||
:label="t('plugin.cloneDescriptionLabel')"
|
||||
:placeholder="t('plugin.cloneDescriptionPlaceholder')"
|
||||
:hint="t('plugin.cloneDescriptionHint')"
|
||||
persistent-hint
|
||||
rows="2"
|
||||
prepend-inner-icon="mdi-text"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.version"
|
||||
:label="t('plugin.cloneVersion')"
|
||||
:placeholder="t('plugin.cloneVersionPlaceholder')"
|
||||
:hint="t('plugin.cloneVersionHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-numeric"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.icon"
|
||||
:label="t('plugin.cloneIcon')"
|
||||
:placeholder="t('plugin.cloneIconPlaceholder')"
|
||||
:hint="t('plugin.cloneIconHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-image"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- 重要提醒 -->
|
||||
<VCol cols="12">
|
||||
<VAlert type="warning" variant="tonal" density="compact" class="mt-2" icon="mdi-alert-circle-outline">
|
||||
<div class="text-body-2">
|
||||
<strong>{{ t('common.notice') }}</strong
|
||||
>:{{ t('plugin.cloneNotice') }}
|
||||
</div>
|
||||
</VAlert>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
|
||||
<VDivider />
|
||||
<VCardActions class="pt-3">
|
||||
<VSpacer />
|
||||
<VBtn @click="pluginCloneDialog = false" variant="outlined">
|
||||
{{ t('common.cancel') }}
|
||||
</VBtn>
|
||||
<VBtn color="primary" @click="executePluginClone" :disabled="!cloneForm.suffix.trim()">
|
||||
<VIcon icon="mdi-content-copy" class="me-2" />
|
||||
{{ t('plugin.createClone') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- 实时日志弹窗 -->
|
||||
<VDialog
|
||||
v-if="loggingDialog"
|
||||
v-model="loggingDialog"
|
||||
scrollable
|
||||
max-width="60rem"
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VDialogCloseBtn @click="loggingDialog = false" />
|
||||
<VCardItem>
|
||||
<VCardTitle class="d-inline-flex">
|
||||
<VIcon icon="mdi-file-document" class="me-2" />
|
||||
{{ t('plugin.logTitle') }}
|
||||
<a class="mx-2 d-inline-flex align-center cursor-pointer" @click="openLoggerWindow">
|
||||
<VChip color="grey-darken-1" size="small" class="ml-2">
|
||||
<VIcon icon="mdi-open-in-new" size="small" start />
|
||||
{{ t('common.openInNewWindow') }}
|
||||
</VChip>
|
||||
</a>
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<LoggingView :logfile="`plugins/${props.plugin?.id?.toLowerCase()}.log`" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ onMounted(() => {
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<LoggingView />
|
||||
<LoggingView logfile="moviepilot.log" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
@@ -2018,7 +2018,8 @@ export default {
|
||||
cloneTitle: 'Create Plugin Clone',
|
||||
cloneSubtitle: 'Create an independent clone instance for {name}',
|
||||
cloneFeature: 'Plugin Clone Feature',
|
||||
cloneDescription: 'Create an independent copy of the plugin with separate configuration and data, suitable for multi-account, testing environments, etc.',
|
||||
cloneDescription:
|
||||
'Create an independent copy of the plugin with separate configuration and data, suitable for multi-account, testing environments, etc.',
|
||||
suffix: 'Clone Suffix',
|
||||
suffixPlaceholder: 'e.g.: Test, Backup, Site1',
|
||||
suffixHint: 'Unique identifier to distinguish clones, only letters and numbers allowed',
|
||||
@@ -2039,12 +2040,14 @@ export default {
|
||||
cloneIcon: 'Icon URL',
|
||||
cloneIconPlaceholder: 'https://example.com/icon.png',
|
||||
cloneIconHint: 'Custom icon for the clone plugin (optional)',
|
||||
cloneNotice: 'Clone plugins are disabled by default after creation and need to be manually configured and enabled. The clone suffix cannot be modified once set.',
|
||||
cloneNotice:
|
||||
'Clone plugins are disabled by default after creation and need to be manually configured and enabled. The clone suffix cannot be modified once set.',
|
||||
createClone: 'Create Clone',
|
||||
cloning: 'Creating clone for {name}...',
|
||||
cloneSuccess: 'Plugin clone {name} created successfully!',
|
||||
cloneFailed: 'Plugin clone creation failed: {message}',
|
||||
cloneFailedGeneral: 'Plugin clone creation failed',
|
||||
logTitle: 'Plugin Logging',
|
||||
},
|
||||
profile: {
|
||||
personalInfo: 'Personal Information',
|
||||
|
||||
@@ -2021,6 +2021,7 @@ export default {
|
||||
cloneSuccess: '插件分身 {name} 创建成功!',
|
||||
cloneFailed: '插件分身创建失败:{message}',
|
||||
cloneFailedGeneral: '插件分身创建失败',
|
||||
logTitle: '插件日志',
|
||||
},
|
||||
profile: {
|
||||
personalInfo: '个人信息',
|
||||
|
||||
@@ -2022,6 +2022,7 @@ export default {
|
||||
cloneSuccess: '插件分身 {name} 創建成功!',
|
||||
cloneFailed: '插件分身創建失敗:{message}',
|
||||
cloneFailedGeneral: '插件分身創建失敗',
|
||||
logTitle: '插件日誌',
|
||||
},
|
||||
profile: {
|
||||
personalInfo: '個人信息',
|
||||
|
||||
@@ -1,84 +1,99 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
// 定义输入变量
|
||||
const props = defineProps<{
|
||||
logfile: string;
|
||||
}>();
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n();
|
||||
|
||||
// 已解析的日志列表
|
||||
const parsedLogs = ref<{ level: string; time: string; program: string; content: string }[]>([])
|
||||
const parsedLogs = ref<
|
||||
{ level: string; time: string; program: string; content: string }[]
|
||||
>([]);
|
||||
|
||||
// 表头
|
||||
const headers = [
|
||||
{ title: t('logging.level'), value: 'level' },
|
||||
{ title: t('logging.time'), value: 'time' },
|
||||
{ title: t('logging.program'), value: 'program' },
|
||||
{ title: t('logging.content'), value: 'content' },
|
||||
]
|
||||
{ title: t("logging.level"), value: "level" },
|
||||
{ title: t("logging.time"), value: "time" },
|
||||
{ title: t("logging.program"), value: "program" },
|
||||
{ title: t("logging.content"), value: "content" },
|
||||
];
|
||||
|
||||
// SSE消息对象
|
||||
let eventSource: EventSource | null = null
|
||||
let eventSource: EventSource | null = null;
|
||||
|
||||
// 日志颜色映射表
|
||||
const logColorMap: Record<string, string> = {
|
||||
DEBUG: 'secondary',
|
||||
INFO: 'info',
|
||||
WARNING: 'warning',
|
||||
ERROR: 'error',
|
||||
}
|
||||
DEBUG: "secondary",
|
||||
INFO: "info",
|
||||
WARNING: "warning",
|
||||
ERROR: "error",
|
||||
};
|
||||
|
||||
// 获取日志颜色
|
||||
function getLogColor(level: string): string {
|
||||
return logColorMap[level] || 'secondary'
|
||||
return logColorMap[level] || "secondary";
|
||||
}
|
||||
|
||||
// SSE持续获取日志
|
||||
function startSSELogging() {
|
||||
eventSource = new EventSource(`${import.meta.env.VITE_API_BASE_URL}system/logging`)
|
||||
const buffer: string[] = []
|
||||
let timeoutId: number | null = null
|
||||
eventSource = new EventSource(
|
||||
`${import.meta.env.VITE_API_BASE_URL}system/logging?logfile=${
|
||||
encodeURIComponent(props.logfile) ?? "moviepilot.log"
|
||||
}`
|
||||
);
|
||||
const buffer: string[] = [];
|
||||
let timeoutId: number | null = null;
|
||||
|
||||
eventSource.addEventListener('message', event => {
|
||||
const message = event.data
|
||||
eventSource.addEventListener("message", (event) => {
|
||||
const message = event.data;
|
||||
if (message) {
|
||||
buffer.push(message)
|
||||
buffer.push(message);
|
||||
if (!timeoutId) {
|
||||
timeoutId = window.setTimeout(() => {
|
||||
// 解析新日志
|
||||
const newParsedLogs = buffer
|
||||
.map(log => {
|
||||
const logPattern = /^【(.*?)】[0-9\-:]*\s(.*?)\s-\s(.*?)\s-\s(.*)$/
|
||||
const matches = log.match(logPattern)
|
||||
.map((log) => {
|
||||
const logPattern = /^【(.*?)】[0-9\-:]*\s(.*?)\s-\s(.*?)\s-\s(.*)$/;
|
||||
const matches = log.match(logPattern);
|
||||
if (matches) {
|
||||
const [, level, time, program, content] = matches
|
||||
return { level, time, program, content }
|
||||
const [, level, time, program, content] = matches;
|
||||
return { level, time, program, content };
|
||||
}
|
||||
return null
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.filter(Boolean);
|
||||
// 倒序后插入parsedLogs顶部
|
||||
parsedLogs.value.unshift(...(newParsedLogs.reverse() as any[]))
|
||||
parsedLogs.value.unshift(...(newParsedLogs.reverse() as any[]));
|
||||
// 保留最新的200条日志
|
||||
parsedLogs.value = parsedLogs.value.slice(0, 200)
|
||||
parsedLogs.value = parsedLogs.value.slice(0, 200);
|
||||
// 重置buffer
|
||||
buffer.length = 0
|
||||
timeoutId = null
|
||||
}, 100)
|
||||
buffer.length = 0;
|
||||
timeoutId = null;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
startSSELogging()
|
||||
})
|
||||
startSSELogging();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (eventSource) eventSource.close()
|
||||
})
|
||||
if (eventSource) eventSource.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoadingBanner v-if="parsedLogs.length === 0" class="mt-12" :text="t('logging.refreshing') + ' ...'" />
|
||||
<LoadingBanner
|
||||
v-if="parsedLogs.length === 0"
|
||||
class="mt-12"
|
||||
:text="t('logging.refreshing') + ' ...'"
|
||||
/>
|
||||
<div v-else>
|
||||
<VTable class="table-rounded" hide-default-footer disable-sort>
|
||||
<tbody>
|
||||
@@ -91,7 +106,12 @@ onBeforeUnmount(() => {
|
||||
hide-default-header
|
||||
>
|
||||
<template #item.level="{ item }">
|
||||
<VChip size="small" :color="getLogColor(item.level)" variant="elevated" v-text="item.level" />
|
||||
<VChip
|
||||
size="small"
|
||||
:color="getLogColor(item.level)"
|
||||
variant="elevated"
|
||||
v-text="item.level"
|
||||
/>
|
||||
</template>
|
||||
<template #item.time="{ item }">
|
||||
<span class="text-sm">{{ item.time }}</span>
|
||||
|
||||
Reference in New Issue
Block a user