mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-20 15:19:41 +08:00
feat: add system uptime tracking and localization support
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { formatDateDifference } from '@/@core/utils/formatters'
|
||||
import api from '@/api'
|
||||
import type { Process as SystemProcess } from '@/api/types'
|
||||
import { clearCacheAndReload } from '@/composables/useVersionChecker'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import mdLinkAttributes from 'markdown-it-link-attributes'
|
||||
@@ -37,6 +38,12 @@ md.use(mdLinkAttributes, {
|
||||
// 系统环境变量
|
||||
const systemEnv = ref<any>({})
|
||||
|
||||
// 系统运行时间的基准秒数和同步时间,用于在弹窗打开后实时递增展示。
|
||||
const systemUptimeBaseSeconds = ref<number | null>(null)
|
||||
const systemUptimeSyncedAt = ref(0)
|
||||
const systemUptimeNow = ref(Date.now())
|
||||
let systemUptimeTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
// 所有Release
|
||||
const allRelease = ref<any>([])
|
||||
|
||||
@@ -102,6 +109,22 @@ const frontendVersionStatistics = computed(() => versionStatistic.value?.fronten
|
||||
// 活跃用户统计
|
||||
const activeUsers = computed(() => versionStatistic.value?.active_users ?? {})
|
||||
|
||||
// 系统运行秒数
|
||||
const systemUptimeSeconds = computed(() => {
|
||||
if (systemUptimeBaseSeconds.value === null) return null
|
||||
|
||||
const elapsedSeconds = Math.floor((systemUptimeNow.value - systemUptimeSyncedAt.value) / 1000)
|
||||
|
||||
return Math.max(0, systemUptimeBaseSeconds.value + elapsedSeconds)
|
||||
})
|
||||
|
||||
// 友好的系统运行时间文本
|
||||
const systemUptimeText = computed(() => {
|
||||
if (systemUptimeSeconds.value === null) return ''
|
||||
|
||||
return formatUptimeDuration(systemUptimeSeconds.value)
|
||||
})
|
||||
|
||||
/** 格式化版本安装统计数字为千分位展示。 */
|
||||
function formatVersionStatisticNumber(value: unknown) {
|
||||
const numberValue = Number(value ?? 0)
|
||||
@@ -111,6 +134,85 @@ function formatVersionStatisticNumber(value: unknown) {
|
||||
return numberValue.toLocaleString()
|
||||
}
|
||||
|
||||
/** 将秒数保存为运行时间基准,并记录本地同步时间。 */
|
||||
function syncSystemUptime(seconds: number | null) {
|
||||
if (seconds === null) return
|
||||
|
||||
const now = Date.now()
|
||||
|
||||
systemUptimeBaseSeconds.value = seconds
|
||||
systemUptimeSyncedAt.value = now
|
||||
systemUptimeNow.value = now
|
||||
}
|
||||
|
||||
/** 将接口返回值规范化为可展示的秒数。 */
|
||||
function normalizeUptimeSeconds(value: unknown) {
|
||||
const numberValue = Number(value)
|
||||
|
||||
if (!Number.isFinite(numberValue) || numberValue < 0) return null
|
||||
|
||||
return Math.floor(numberValue)
|
||||
}
|
||||
|
||||
/** 从进程创建时间推导运行秒数;兼容秒级和毫秒级时间戳。 */
|
||||
function uptimeSecondsFromCreateTime(value: unknown) {
|
||||
const timestamp = Number(value)
|
||||
|
||||
if (!Number.isFinite(timestamp) || timestamp <= 0) return null
|
||||
|
||||
const timestampMs = timestamp > 1_000_000_000_000 ? timestamp : timestamp * 1000
|
||||
|
||||
return Math.max(0, Math.floor((Date.now() - timestampMs) / 1000))
|
||||
}
|
||||
|
||||
/** 获取单个进程的运行秒数,优先使用创建时间以保留跨天运行时长。 */
|
||||
function getProcessUptimeSeconds(process: SystemProcess) {
|
||||
return uptimeSecondsFromCreateTime(process.create_time) ?? normalizeUptimeSeconds(process.run_time)
|
||||
}
|
||||
|
||||
/** 从进程列表中挑选 MoviePilot 主进程,找不到时使用运行时间最长的进程兜底。 */
|
||||
function resolveSystemUptimeSeconds(processes: SystemProcess[]) {
|
||||
const availableProcesses = processes
|
||||
.map(process => ({
|
||||
process,
|
||||
uptimeSeconds: getProcessUptimeSeconds(process),
|
||||
}))
|
||||
.filter((item): item is { process: SystemProcess; uptimeSeconds: number } => item.uptimeSeconds !== null)
|
||||
|
||||
if (!availableProcesses.length) return null
|
||||
|
||||
const preferredProcesses = availableProcesses.filter(({ process }) =>
|
||||
/moviepilot|python|uvicorn|gunicorn|hypercorn/i.test(process.name ?? ''),
|
||||
)
|
||||
const targetProcesses = preferredProcesses.length ? preferredProcesses : availableProcesses
|
||||
|
||||
return targetProcesses.reduce((max, item) => (item.uptimeSeconds > max.uptimeSeconds ? item : max)).uptimeSeconds
|
||||
}
|
||||
|
||||
/** 格式化单个运行时间单位。 */
|
||||
function formatUptimeUnit(value: number, unit: 'day' | 'hour' | 'minute' | 'second') {
|
||||
const unitKey = value === 1 ? unit : `${unit}s`
|
||||
|
||||
return t(`setting.about.uptimeUnits.${unitKey}`, { count: value })
|
||||
}
|
||||
|
||||
/** 将运行秒数格式化为两段以内的友好文本,例如“3天 2小时”。 */
|
||||
function formatUptimeDuration(totalSeconds: number) {
|
||||
const normalizedSeconds = Math.max(0, Math.floor(totalSeconds))
|
||||
const days = Math.floor(normalizedSeconds / 86400)
|
||||
const hours = Math.floor((normalizedSeconds % 86400) / 3600)
|
||||
const minutes = Math.floor((normalizedSeconds % 3600) / 60)
|
||||
const seconds = normalizedSeconds % 60
|
||||
const parts: string[] = []
|
||||
|
||||
if (days > 0) parts.push(formatUptimeUnit(days, 'day'))
|
||||
if (hours > 0) parts.push(formatUptimeUnit(hours, 'hour'))
|
||||
if (minutes > 0 && parts.length < 2) parts.push(formatUptimeUnit(minutes, 'minute'))
|
||||
if (!parts.length) parts.push(formatUptimeUnit(seconds, 'second'))
|
||||
|
||||
return parts.slice(0, 2).join(' ')
|
||||
}
|
||||
|
||||
// 打开日志对话框
|
||||
function showReleaseDialog(title: string, body: string) {
|
||||
releaseDialogTitle.value = title
|
||||
@@ -151,6 +253,17 @@ async function querySystemEnv() {
|
||||
}
|
||||
}
|
||||
|
||||
// 查询系统运行时间
|
||||
async function querySystemUptime() {
|
||||
try {
|
||||
const processes: SystemProcess[] = await api.get('dashboard/processes')
|
||||
|
||||
syncSystemUptime(resolveSystemUptimeSeconds(processes))
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 查询所有Release
|
||||
async function queryAllRelease() {
|
||||
try {
|
||||
@@ -192,8 +305,17 @@ async function clearCache() {
|
||||
|
||||
onMounted(() => {
|
||||
querySystemEnv()
|
||||
querySystemUptime()
|
||||
queryAllRelease()
|
||||
querySupportingSites()
|
||||
|
||||
systemUptimeTimer = setInterval(() => {
|
||||
if (systemUptimeBaseSeconds.value !== null) systemUptimeNow.value = Date.now()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (systemUptimeTimer) clearInterval(systemUptimeTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -321,6 +443,16 @@ onMounted(() => {
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="systemUptimeText">
|
||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="block text-sm font-bold">{{ t('setting.about.systemUptime') }}</dt>
|
||||
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="flex-grow flex flex-row items-center truncate">
|
||||
<code class="truncate">{{ systemUptimeText }}</code>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="block text-sm font-bold">{{ t('setting.about.supportingSites') }}</dt>
|
||||
|
||||
@@ -1406,6 +1406,7 @@ export default {
|
||||
title: 'About MoviePilot',
|
||||
softwareVersion: 'Software Version',
|
||||
frontendVersion: 'Frontend Version',
|
||||
systemUptime: 'System Uptime',
|
||||
browserVersion: 'Browser Cached Version',
|
||||
authVersion: 'Auth Resource Version',
|
||||
indexerVersion: 'Indexer Resource Version',
|
||||
@@ -1439,6 +1440,16 @@ export default {
|
||||
users: 'Users',
|
||||
lastUpdated: 'Updated At',
|
||||
noVersionStatisticData: 'No statistics data',
|
||||
uptimeUnits: {
|
||||
day: '{count} day',
|
||||
days: '{count} days',
|
||||
hour: '{count} hour',
|
||||
hours: '{count} hours',
|
||||
minute: '{count} minute',
|
||||
minutes: '{count} minutes',
|
||||
second: '{count} second',
|
||||
seconds: '{count} seconds',
|
||||
},
|
||||
},
|
||||
system: {
|
||||
custom: 'Custom',
|
||||
|
||||
@@ -1401,6 +1401,7 @@ export default {
|
||||
title: '关于 MoviePilot',
|
||||
softwareVersion: '软件版本',
|
||||
frontendVersion: '前端版本',
|
||||
systemUptime: '系统已运行',
|
||||
browserVersion: '浏览器缓存版本',
|
||||
authVersion: '认证资源版本',
|
||||
indexerVersion: '站点资源版本',
|
||||
@@ -1434,6 +1435,16 @@ export default {
|
||||
users: '用户数',
|
||||
lastUpdated: '更新时间',
|
||||
noVersionStatisticData: '暂无统计数据',
|
||||
uptimeUnits: {
|
||||
day: '{count}天',
|
||||
days: '{count}天',
|
||||
hour: '{count}小时',
|
||||
hours: '{count}小时',
|
||||
minute: '{count}分钟',
|
||||
minutes: '{count}分钟',
|
||||
second: '{count}秒',
|
||||
seconds: '{count}秒',
|
||||
},
|
||||
},
|
||||
system: {
|
||||
custom: '自定义',
|
||||
|
||||
@@ -1402,6 +1402,7 @@ export default {
|
||||
title: '關於 MoviePilot',
|
||||
softwareVersion: '軟件版本',
|
||||
frontendVersion: '前端版本',
|
||||
systemUptime: '系統已運行',
|
||||
browserVersion: '瀏覽器緩存版本',
|
||||
authVersion: '認證資源版本',
|
||||
indexerVersion: '站點資源版本',
|
||||
@@ -1435,6 +1436,16 @@ export default {
|
||||
users: '用戶數',
|
||||
lastUpdated: '更新時間',
|
||||
noVersionStatisticData: '暫無統計數據',
|
||||
uptimeUnits: {
|
||||
day: '{count}天',
|
||||
days: '{count}天',
|
||||
hour: '{count}小時',
|
||||
hours: '{count}小時',
|
||||
minute: '{count}分鐘',
|
||||
minutes: '{count}分鐘',
|
||||
second: '{count}秒',
|
||||
seconds: '{count}秒',
|
||||
},
|
||||
},
|
||||
system: {
|
||||
custom: '自定義',
|
||||
|
||||
Reference in New Issue
Block a user