feat: Add panel carousel on the overview page (#10394)

This commit is contained in:
ssongliu 2025-09-17 17:47:58 +08:00 committed by GitHub
parent c408619042
commit 1883b05f26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 208 additions and 70 deletions

View file

@ -241,6 +241,18 @@ export namespace Setting {
isBound: boolean; isBound: boolean;
name: string; name: string;
} }
export interface SimpleNodeItem {
id: number;
name: string;
addr: string;
description: string;
systemVersion: string;
securityEntrance: string;
cpuUsedPercent: number;
cpuTotal: number;
memoryTotal: number;
memoryUsedPercent: number;
}
export interface ReleasesNotes { export interface ReleasesNotes {
Version: string; Version: string;
CreatedAt: string; CreatedAt: string;

View file

@ -47,6 +47,9 @@ export const listNodeOptions = (type: string) => {
export const listAllNodes = () => { export const listAllNodes = () => {
return http.get<Array<Setting.NodeItem>>(`/core/nodes/all`); return http.get<Array<Setting.NodeItem>>(`/core/nodes/all`);
}; };
export const listAllSimpleNodes = () => {
return http.get<Array<Setting.SimpleNodeItem>>(`/core/nodes/simple/all`);
};
export const getLicenseSmsInfo = () => { export const getLicenseSmsInfo = () => {
return http.get<Setting.SmsInfo>(`/core/licenses/sms/info`); return http.get<Setting.SmsInfo>(`/core/licenses/sms/info`);

View file

@ -70,7 +70,7 @@
</CardWithHeader> </CardWithHeader>
<CardWithHeader :header="$t('commons.table.status')" class="card-interval"> <CardWithHeader :header="$t('commons.table.status')" class="card-interval">
<template #body> <template #body>
<Status ref="statusRef" style="margin-bottom: 33px" /> <SystemStatus ref="statusRef" style="margin-bottom: 33px" />
</template> </template>
</CardWithHeader> </CardWithHeader>
<CardWithHeader <CardWithHeader
@ -163,73 +163,137 @@
</CardWithHeader> </CardWithHeader>
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8"> <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<CardWithHeader :header="$t('home.systemInfo')"> <el-carousel
<template #header-r> :key="simpleNodes.length"
<el-button class="h-button-setting" @click="handleCopy" link icon="CopyDocument" /> indicator-position="none"
</template> height="346px"
<template #body> :arrow="showSimpleNode() ? 'hover' : 'never'"
<el-scrollbar> >
<el-descriptions :column="1" class="h-systemInfo" border> <el-carousel-item key="systemInfo">
<el-descriptions-item class-name="system-content" label-class-name="system-label"> <CardWithHeader :header="$t('home.systemInfo')">
<template #label> <template #header-r>
<span class="system-label">{{ $t('home.hostname') }}</span> <el-button class="h-button-setting" @click="handleCopy" link icon="CopyDocument" />
</template> </template>
{{ baseInfo.hostname }} <template #body>
</el-descriptions-item> <el-scrollbar>
<el-descriptions-item class-name="system-content" label-class-name="system-label"> <el-descriptions :column="1" class="ml-5" border>
<template #label> <el-descriptions-item
<span class="system-label">{{ $t('home.platformVersion') }}</span> class-name="system-content"
</template> label-class-name="system-label"
{{ >
baseInfo.platformVersion <template #label>
? baseInfo.platform + '-' + baseInfo.platformVersion <span class="system-label">{{ $t('home.hostname') }}</span>
: baseInfo.platform </template>
}} {{ baseInfo.hostname }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item class-name="system-content" label-class-name="system-label"> <el-descriptions-item
<template #label> class-name="system-content"
<span class="system-label">{{ $t('home.kernelVersion') }}</span> label-class-name="system-label"
</template> >
{{ baseInfo.kernelVersion }} <template #label>
</el-descriptions-item> <span class="system-label">{{ $t('home.platformVersion') }}</span>
<el-descriptions-item class-name="system-content" label-class-name="system-label"> </template>
<template #label> {{
<span class="system-label">{{ $t('home.kernelArch') }}</span> baseInfo.platformVersion
</template> ? baseInfo.platform + '-' + baseInfo.platformVersion
{{ baseInfo.kernelArch }} : baseInfo.platform
</el-descriptions-item> }}
<el-descriptions-item class-name="system-content" label-class-name="system-label"> </el-descriptions-item>
<template #label> <el-descriptions-item
<span class="system-label">{{ $t('home.ip') }}</span> class-name="system-content"
</template> label-class-name="system-label"
{{ baseInfo.ipV4Addr }} >
</el-descriptions-item> <template #label>
<el-descriptions-item <span class="system-label">{{ $t('home.kernelVersion') }}</span>
v-if="baseInfo.httpProxy && baseInfo.httpProxy !== 'noProxy'" </template>
class-name="system-content" {{ baseInfo.kernelVersion }}
label-class-name="system-label" </el-descriptions-item>
> <el-descriptions-item
<template #label> class-name="system-content"
<span class="system-label">{{ $t('home.proxy') }}</span> label-class-name="system-label"
{{ baseInfo.httpProxy }} >
</template> <template #label>
</el-descriptions-item> <span class="system-label">{{ $t('home.kernelArch') }}</span>
<el-descriptions-item class-name="system-content" label-class-name="system-label"> </template>
<template #label> {{ baseInfo.kernelArch }}
<span class="system-label">{{ $t('home.uptime') }}</span> </el-descriptions-item>
</template> <el-descriptions-item
{{ currentInfo.timeSinceUptime }} class-name="system-content"
</el-descriptions-item> label-class-name="system-label"
<el-descriptions-item class-name="system-content" label-class-name="system-label"> >
<template #label> <template #label>
<span class="system-label">{{ $t('home.runningTime') }}</span> <span class="system-label">{{ $t('home.ip') }}</span>
</template> </template>
{{ loadUpTime(currentInfo.uptime) }} {{ baseInfo.ipV4Addr }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> <el-descriptions-item
</el-scrollbar> v-if="baseInfo.httpProxy && baseInfo.httpProxy !== 'noProxy'"
</template> class-name="system-content"
</CardWithHeader> label-class-name="system-label"
>
<template #label>
<span class="system-label">{{ $t('home.proxy') }}</span>
{{ baseInfo.httpProxy }}
</template>
</el-descriptions-item>
<el-descriptions-item
class-name="system-content"
label-class-name="system-label"
>
<template #label>
<span class="system-label">{{ $t('home.uptime') }}</span>
</template>
{{ currentInfo.timeSinceUptime }}
</el-descriptions-item>
<el-descriptions-item
class-name="system-content"
label-class-name="system-label"
>
<template #label>
<span class="system-label">{{ $t('home.runningTime') }}</span>
</template>
{{ loadUpTime(currentInfo.uptime) }}
</el-descriptions-item>
</el-descriptions>
</el-scrollbar>
</template>
</CardWithHeader>
</el-carousel-item>
<el-carousel-item key="simpleNode" v-if="showSimpleNode()">
<CardWithHeader :header="$t('setting.panel')">
<template #body>
<el-scrollbar height="266px">
<div class="simple-node cursor-pointer" v-for="row in simpleNodes" :key="row.id">
<el-row :gutter="5">
<el-col :span="21">
<div class="name">
{{ row.name }}
</div>
<div class="detail">
{{ loadSource(row) }}
</div>
</el-col>
<el-col :span="1">
<el-button
@click="jumpPanel(row)"
size="small"
class="visit"
round
plain
type="primary"
>
{{ $t('commons.button.visit') }}
</el-button>
</el-col>
</el-row>
<div class="h-app-divider" />
</div>
</el-scrollbar>
</template>
</CardWithHeader>
</el-carousel-item>
</el-carousel>
<AppLauncher ref="appRef" class="card-interval" /> <AppLauncher ref="appRef" class="card-interval" />
</el-col> </el-col>
@ -242,7 +306,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onBeforeUnmount, ref, reactive } from 'vue'; import { onMounted, onBeforeUnmount, ref, reactive } from 'vue';
import Status from '@/views/home/status/index.vue'; import SystemStatus from '@/views/home/status/index.vue';
import AppLauncher from '@/views/home/app/index.vue'; import AppLauncher from '@/views/home/app/index.vue';
import VCharts from '@/components/v-charts/index.vue'; import VCharts from '@/components/v-charts/index.vue';
import LicenseImport from '@/components/license-import/index.vue'; import LicenseImport from '@/components/license-import/index.vue';
@ -254,7 +318,7 @@ import { dateFormatForSecond, computeSize, computeSizeFromKBs, loadUpTime, jumpT
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { loadBaseInfo, loadCurrentInfo } from '@/api/modules/dashboard'; import { loadBaseInfo, loadCurrentInfo } from '@/api/modules/dashboard';
import { getIOOptions, getNetworkOptions } from '@/api/modules/host'; import { getIOOptions, getNetworkOptions } from '@/api/modules/host';
import { getSettingInfo, loadUpgradeInfo } from '@/api/modules/setting'; import { getSettingInfo, listAllSimpleNodes, loadUpgradeInfo } from '@/api/modules/setting';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { routerToFileWithPath, routerToPath } from '@/utils/router'; import { routerToFileWithPath, routerToPath } from '@/utils/router';
@ -280,6 +344,7 @@ const netBytesRecvs = ref<Array<number>>([]);
const timeIODatas = ref<Array<string>>([]); const timeIODatas = ref<Array<string>>([]);
const timeNetDatas = ref<Array<string>>([]); const timeNetDatas = ref<Array<string>>([]);
const simpleNodes = ref([]);
const ioOptions = ref(); const ioOptions = ref();
const netOptions = ref(); const netOptions = ref();
@ -376,6 +441,11 @@ const onLoadNetworkOptions = async () => {
searchInfo.netOption = globalStore.defaultNetwork || (netOptions.value && netOptions.value[0]); searchInfo.netOption = globalStore.defaultNetwork || (netOptions.value && netOptions.value[0]);
}; };
const onLoadSimpleNode = async () => {
const res = await listAllSimpleNodes();
simpleNodes.value = res.data || [];
};
const onLoadIOOptions = async () => { const onLoadIOOptions = async () => {
const res = await getIOOptions(); const res = await getIOOptions();
ioOptions.value = res.data; ioOptions.value = res.data;
@ -407,6 +477,7 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
} }
if (isActive.value && !globalStore.isOnRestart) { if (isActive.value && !globalStore.isOnRestart) {
await onLoadCurrentInfo(); await onLoadCurrentInfo();
await onLoadSimpleNode();
} }
} catch { } catch {
clearInterval(Number(timer)); clearInterval(Number(timer));
@ -423,6 +494,14 @@ const quickJump = (item: any) => {
return routerToPath(item.router); return routerToPath(item.router);
}; };
const showSimpleNode = () => {
return globalStore.isMasterProductPro && simpleNodes.value?.length !== 0;
};
const jumpPanel = (row: any) => {
window.open(row.addr, '_blank', 'noopener,noreferrer');
};
const onLoadCurrentInfo = async () => { const onLoadCurrentInfo = async () => {
const res = await loadCurrentInfo(searchInfo.ioOption, searchInfo.netOption); const res = await loadCurrentInfo(searchInfo.ioOption, searchInfo.netOption);
currentInfo.value.timeSinceUptime = res.data.timeSinceUptime; currentInfo.value.timeSinceUptime = res.data.timeSinceUptime;
@ -569,6 +648,24 @@ const loadSafeStatus = async () => {
isSafety.value = res.data.securityEntrance; isSafety.value = res.data.securityEntrance;
}; };
const loadSource = (row: any) => {
if (row.status !== 'Healthy') {
return '-';
}
return (
row.cpuTotal +
' ' +
i18n.global.t('commons.units.core') +
' (' +
row.cpuUsedPercent?.toFixed(2) +
'%) / ' +
computeSize(row.memoryTotal) +
' (' +
row.memoryUsedPercent?.toFixed(2) +
'%)'
);
};
const onFocus = () => { const onFocus = () => {
isActive.value = true; isActive.value = true;
}; };
@ -588,6 +685,7 @@ const fetchData = () => {
onLoadNetworkOptions(); onLoadNetworkOptions();
onLoadIOOptions(); onLoadIOOptions();
onLoadBaseInfo(true, 'all'); onLoadBaseInfo(true, 'all');
onLoadSimpleNode();
}; };
onBeforeRouteUpdate((to, from, next) => { onBeforeRouteUpdate((to, from, next) => {
@ -663,6 +761,31 @@ onBeforeUnmount(() => {
width: 100% !important; width: 100% !important;
} }
.simple-node {
padding: 10px 15px 10px 0px;
margin: 3px 10px 3px 20px;
&:hover {
background-color: rgba(0, 94, 235, 0.03);
}
.name {
font-weight: 500 !important;
font-size: 16px !important;
color: var(--panel-text-color);
}
.detail {
font-size: 12px !important;
}
.visit {
margin-bottom: -25px;
}
}
.h-app-divider {
margin-top: 3px;
border: 0;
border-top: var(--panel-border);
}
.monitor-tags { .monitor-tags {
position: absolute; position: absolute;
top: -10px; top: -10px;