feat: 添加列表视图进度条选项并优化进度条显示

This commit is contained in:
Montia37
2025-09-05 19:35:29 +08:00
parent e6dc8b1776
commit a649945566
8 changed files with 84 additions and 36 deletions

View File

@@ -129,6 +129,13 @@
"default": true, "default": true,
"help": "启用后默认显示 SWAP 信息" "help": "启用后默认显示 SWAP 信息"
}, },
{
"key": "enableListItemProgressBar",
"name": "启用列表视图进度条",
"type": "switch",
"default": true,
"help": "启用后列表视图中将会显示进度条来表示存储使用率"
},
{ {
"name": "Instance 设置", "name": "Instance 设置",
"type": "title" "type": "title"

View File

@@ -33,12 +33,6 @@ export const NodeCard = ({ node, enableSwap }: NodeCardProps) => {
trafficPercentage, trafficPercentage,
} = useNodeCommons(node); } = useNodeCommons(node);
const getProgressBarClass = (percentage: number) => {
if (percentage > 90) return "bg-red-600";
if (percentage > 50) return "bg-yellow-400";
return "bg-green-500";
};
return ( return (
<Card <Card
className={`flex flex-col mx-auto purcarte-blur w-full min-w-[280px] max-w-sm ${ className={`flex flex-col mx-auto purcarte-blur w-full min-w-[280px] max-w-sm ${
@@ -88,20 +82,14 @@ export const NodeCard = ({ node, enableSwap }: NodeCardProps) => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-secondary-foreground">CPU</span> <span className="text-secondary-foreground">CPU</span>
<div className="w-3/4 flex items-center gap-2"> <div className="w-3/4 flex items-center gap-2">
<ProgressBar <ProgressBar value={cpuUsage} />
value={cpuUsage}
className={getProgressBarClass(cpuUsage)}
/>
<span className="w-12 text-right">{cpuUsage.toFixed(0)}%</span> <span className="w-12 text-right">{cpuUsage.toFixed(0)}%</span>
</div> </div>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-secondary-foreground"></span> <span className="text-secondary-foreground"></span>
<div className="w-3/4 flex items-center gap-2"> <div className="w-3/4 flex items-center gap-2">
<ProgressBar <ProgressBar value={memUsage} />
value={memUsage}
className={getProgressBarClass(memUsage)}
/>
<span className="w-12 text-right">{memUsage.toFixed(0)}%</span> <span className="w-12 text-right">{memUsage.toFixed(0)}%</span>
</div> </div>
</div> </div>
@@ -109,10 +97,7 @@ export const NodeCard = ({ node, enableSwap }: NodeCardProps) => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-secondary-foreground">SWAP</span> <span className="text-secondary-foreground">SWAP</span>
<div className="w-3/4 flex items-center gap-2"> <div className="w-3/4 flex items-center gap-2">
<ProgressBar <ProgressBar value={swapUsage} />
value={swapUsage}
className={getProgressBarClass(swapUsage)}
/>
{node.swap_total > 0 ? ( {node.swap_total > 0 ? (
<span className="w-12 text-right">{swapUsage.toFixed(0)}%</span> <span className="w-12 text-right">{swapUsage.toFixed(0)}%</span>
) : ( ) : (
@@ -124,10 +109,7 @@ export const NodeCard = ({ node, enableSwap }: NodeCardProps) => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-secondary-foreground"></span> <span className="text-secondary-foreground"></span>
<div className="w-3/4 flex items-center gap-2"> <div className="w-3/4 flex items-center gap-2">
<ProgressBar <ProgressBar value={diskUsage} />
value={diskUsage}
className={getProgressBarClass(diskUsage)}
/>
<span className="w-12 text-right">{diskUsage.toFixed(0)}%</span> <span className="w-12 text-right">{diskUsage.toFixed(0)}%</span>
</div> </div>
</div> </div>

View File

@@ -6,13 +6,19 @@ import Flag from "./Flag";
import { Tag } from "../ui/tag"; import { Tag } from "../ui/tag";
import { useNodeCommons } from "@/hooks/useNodeCommons"; import { useNodeCommons } from "@/hooks/useNodeCommons";
import { CircleProgress } from "../ui/circle-progress"; import { CircleProgress } from "../ui/circle-progress";
import { ProgressBar } from "../ui/progress-bar";
interface NodeListItemProps { interface NodeListItemProps {
node: NodeWithStatus; node: NodeWithStatus;
enableSwap: boolean | undefined; enableSwap: boolean | undefined;
enableListItemProgressBar: boolean | undefined;
} }
export const NodeListItem = ({ node, enableSwap }: NodeListItemProps) => { export const NodeListItem = ({
node,
enableSwap,
enableListItemProgressBar,
}: NodeListItemProps) => {
const { const {
stats, stats,
isOnline, isOnline,
@@ -60,16 +66,32 @@ export const NodeListItem = ({ node, enableSwap }: NodeListItemProps) => {
<CpuIcon className="inline-block size-5 flex-shrink-0 text-blue-600" /> <CpuIcon className="inline-block size-5 flex-shrink-0 text-blue-600" />
<div className="ml-1 w-full items-center justify-center"> <div className="ml-1 w-full items-center justify-center">
<div>{node.cpu_cores} Cores</div> <div>{node.cpu_cores} Cores</div>
<div>{isOnline ? `${cpuUsage.toFixed(1)}%` : "N/A"}</div> {enableListItemProgressBar ? (
<div className="flex items-center gap-1">
<ProgressBar value={cpuUsage} h="h-2" />
<span className="w-10 text-right text-xs">
{isOnline ? `${cpuUsage.toFixed(0)}%` : "N/A"}
</span>
</div>
) : (
<div>{isOnline ? `${cpuUsage.toFixed(0)}%` : "N/A"}</div>
)}
</div> </div>
</div> </div>
<div className="col-span-1 flex items-center text-left"> <div className="col-span-1 flex items-center text-left">
<MemoryStickIcon className="inline-block size-5 flex-shrink-0 text-green-600" /> <MemoryStickIcon className="inline-block size-5 flex-shrink-0 text-green-600" />
<div className="ml-1 w-full items-center justify-center"> <div className="ml-1 w-full items-center justify-center">
<div>{formatBytes(node.mem_total)}</div> <div>{formatBytes(node.mem_total)}</div>
<div className="mt-1"> {enableListItemProgressBar ? (
{isOnline ? `${memUsage.toFixed(1)}%` : "N/A"} <div className="flex items-center gap-1">
</div> <ProgressBar value={memUsage} h="h-2" />
<span className="w-10 text-right text-xs">
{isOnline ? `${memUsage.toFixed(0)}%` : "N/A"}
</span>
</div>
) : (
<div>{isOnline ? `${memUsage.toFixed(0)}%` : "N/A"}</div>
)}
</div> </div>
</div> </div>
{enableSwap && ( {enableSwap && (
@@ -78,9 +100,16 @@ export const NodeListItem = ({ node, enableSwap }: NodeListItemProps) => {
{node.swap_total > 0 ? ( {node.swap_total > 0 ? (
<div className="ml-1 w-full items-center justify-center"> <div className="ml-1 w-full items-center justify-center">
<div>{formatBytes(node.swap_total)}</div> <div>{formatBytes(node.swap_total)}</div>
<div className="mt-1"> {enableListItemProgressBar ? (
{isOnline ? `${swapUsage.toFixed(1)}%` : "N/A"} <div className="flex items-center gap-1">
</div> <ProgressBar value={swapUsage} h="h-2" />
<span className="w-10 text-right text-xs">
{isOnline ? `${swapUsage.toFixed(0)}%` : "N/A"}
</span>
</div>
) : (
<div>{isOnline ? `${swapUsage.toFixed(0)}%` : "N/A"}</div>
)}
</div> </div>
) : ( ) : (
<div className="ml-1 w-full item-center justify-center">OFF</div> <div className="ml-1 w-full item-center justify-center">OFF</div>
@@ -91,9 +120,16 @@ export const NodeListItem = ({ node, enableSwap }: NodeListItemProps) => {
<HardDriveIcon className="inline-block size-5 flex-shrink-0 text-red-600" /> <HardDriveIcon className="inline-block size-5 flex-shrink-0 text-red-600" />
<div className="ml-1 w-full items-center justify-center"> <div className="ml-1 w-full items-center justify-center">
<div>{formatBytes(node.disk_total)}</div> <div>{formatBytes(node.disk_total)}</div>
<div className="mt-1"> {enableListItemProgressBar ? (
{isOnline ? `${diskUsage.toFixed(1)}%` : "N/A"} <div className="flex items-center gap-1">
</div> <ProgressBar value={diskUsage} h="h-2" />
<span className="w-10 text-right text-xs">
{isOnline ? `${diskUsage.toFixed(0)}%` : "N/A"}
</span>
</div>
) : (
<div>{isOnline ? `${diskUsage.toFixed(0)}%` : "N/A"}</div>
)}
</div> </div>
</div> </div>
<div className="col-span-1"> <div className="col-span-1">

View File

@@ -1,13 +1,19 @@
import { getProgressBarClass } from "@/utils";
export const ProgressBar = ({ export const ProgressBar = ({
value, value,
h = "h-3",
className, className,
}: { }: {
value: number; value: number;
h?: string;
className?: string; className?: string;
}) => ( }) => (
<div className="w-full bg-gray-200 rounded-full h-3 dark:bg-gray-700"> <div className={`w-full bg-gray-200 rounded-full ${h} dark:bg-gray-700`}>
<div <div
className={`h-3 rounded-full transition-all duration-500 ${className}`} className={`${h} rounded-full transition-all duration-500 ${getProgressBarClass(
value
)} ${className}`}
style={{ width: `${value}%` }}></div> style={{ width: `${value}%` }}></div>
</div> </div>
); );

View File

@@ -105,6 +105,9 @@ export function ConfigProvider({
blurValue, blurValue,
blurBackgroundColor, blurBackgroundColor,
enableSwap: theme.enableSwap ?? DEFAULT_CONFIG.enableSwap, enableSwap: theme.enableSwap ?? DEFAULT_CONFIG.enableSwap,
enableListItemProgressBar:
theme.enableListItemProgressBar ??
DEFAULT_CONFIG.enableListItemProgressBar,
}), }),
[ [
theme, theme,

View File

@@ -19,6 +19,7 @@ export interface ConfigOptions {
enableConnectBreaks?: boolean; // 是否启用连接断点 enableConnectBreaks?: boolean; // 是否启用连接断点
pingChartMaxPoints?: number; // 延迟图表最大点数 pingChartMaxPoints?: number; // 延迟图表最大点数
enableSwap?: boolean; // 是否启用SWAP显示 enableSwap?: boolean; // 是否启用SWAP显示
enableListItemProgressBar?: boolean; // 是否启用列表视图进度条
} }
// 默认配置值 // 默认配置值
@@ -43,4 +44,5 @@ export const DEFAULT_CONFIG: ConfigOptions = {
enableConnectBreaks: false, enableConnectBreaks: false,
pingChartMaxPoints: 0, pingChartMaxPoints: 0,
enableSwap: true, enableSwap: true,
enableListItemProgressBar: true,
}; };

View File

@@ -26,7 +26,12 @@ const HomePage: React.FC<HomePageProps> = ({ viewMode, searchTerm }) => {
const [selectedGroup, setSelectedGroup] = useState( const [selectedGroup, setSelectedGroup] = useState(
homeStateCache.selectedGroup homeStateCache.selectedGroup
); );
const { enableGroupedBar, enableStatsBar, enableSwap } = useAppConfig(); const {
enableGroupedBar,
enableStatsBar,
enableSwap,
enableListItemProgressBar,
} = useAppConfig();
const [displayOptions, setDisplayOptions] = useState({ const [displayOptions, setDisplayOptions] = useState({
time: true, time: true,
online: true, online: true,
@@ -173,6 +178,7 @@ const HomePage: React.FC<HomePageProps> = ({ viewMode, searchTerm }) => {
key={node.uuid} key={node.uuid}
node={node} node={node}
enableSwap={enableSwap} enableSwap={enableSwap}
enableListItemProgressBar={enableListItemProgressBar}
/> />
) )
)} )}

View File

@@ -105,3 +105,9 @@ export const formatTrafficLimit = (
return `${limitText} (${typeText})`; return `${limitText} (${typeText})`;
}; };
export const getProgressBarClass = (percentage: number) => {
if (percentage > 90) return "bg-red-600";
if (percentage > 50) return "bg-yellow-400";
return "bg-green-500";
};