diff --git a/panel/src/components/VoxelFleet.tsx b/panel/src/components/VoxelFleet.tsx index beec70f..b1b6eed 100644 --- a/panel/src/components/VoxelFleet.tsx +++ b/panel/src/components/VoxelFleet.tsx @@ -34,8 +34,13 @@ export function VoxelFleet({ servers }: Props) { const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); - mount.appendChild(renderer.domElement); + renderer.domElement.style.position = "absolute"; + renderer.domElement.style.top = "0"; + renderer.domElement.style.left = "0"; + renderer.domElement.style.width = "100%"; + renderer.domElement.style.height = "100%"; renderer.domElement.style.display = "block"; + mount.appendChild(renderer.domElement); scene.add(new THREE.AmbientLight(0xffffff, 0.55)); const key = new THREE.DirectionalLight(0xffffff, 1.1); @@ -157,7 +162,7 @@ export function VoxelFleet({ servers }: Props) { }; }, []); - return
; + return ; } /** sig is a cheap fingerprint of the fleet's renderable shape (order-independent diff --git a/panel/src/i18n/resources/en-US/dashboard.json b/panel/src/i18n/resources/en-US/dashboard.json index d7dd7fd..08db249 100644 --- a/panel/src/i18n/resources/en-US/dashboard.json +++ b/panel/src/i18n/resources/en-US/dashboard.json @@ -10,5 +10,36 @@ "fleet": "Fleet", "manage": "Manage", "loading_scene": "Loading scene…", - "preparing_scene": "Preparing scene…" + "preparing_scene": "Preparing scene…", + "fleet_load": "Fleet Load & Health", + "active_load": "Player Load Rate", + "status_distribution": "Node Status Distribution", + "account_status": "Account Status", + "account_linked_title": "Account Linked", + "account_linked_desc": "Your identity is bound to a Minecraft UUID. You have full server ownership and wake permissions.", + "account_unlinked_title": "Account Unlinked", + "account_unlinked_desc": "You must link a Minecraft character to claim or operate servers. Go to Account to bind now.", + "go_link": "Bind Now", + "system_specs": "Specs & Metrics", + "spec_images_total": "Whitelisted Images", + "spec_images_built": "Built Locally", + "spec_images_external": "External Whitelisted", + "spec_servers_total": "Total Servers", + "spec_servers_owned": "Owned Servers", + "spec_servers_claimable": "Claimable Servers", + "spec_running_ratio": "Running Rate", + "spec_stopped_ratio": "Sleeping Rate", + "spec_failed_ratio": "Failed Rate", + "no_servers_monitor": "No active instances in the cluster", + "images_count": "Whitelisted backend images available", + "legend_title": "3D Node Monitor Legend", + "legend_running": "Running", + "legend_starting": "Starting", + "legend_stopping": "Stopping", + "legend_stopped": "Sleeping", + "legend_failed": "Failed", + "legend_unknown": "Unknown", + "welcome_title": "Welcome to Felis Lolicon", + "welcome_desc": "You have logged into the User Dashboard. Go to 'My Servers' to view and claim nodes allocated to you, or manage their sleep, wake, and console logs.", + "go_to_servers_btn": "Manage My Servers" } diff --git a/panel/src/i18n/resources/zh-CN/dashboard.json b/panel/src/i18n/resources/zh-CN/dashboard.json index b119375..37e7c93 100644 --- a/panel/src/i18n/resources/zh-CN/dashboard.json +++ b/panel/src/i18n/resources/zh-CN/dashboard.json @@ -10,5 +10,36 @@ "fleet": "服务器概览", "manage": "管理", "loading_scene": "正在加载 3D 场景…", - "preparing_scene": "正在准备场景…" + "preparing_scene": "正在准备场景…", + "fleet_load": "负载与健康度", + "active_load": "玩家在线负载", + "status_distribution": "节点状态分布", + "account_status": "账号绑定状态", + "account_linked_title": "已关联游戏身份", + "account_linked_desc": "您的 Web 身份已成功绑定至 Minecraft 角色。拥有完整的所有权认领及启动权限。", + "account_unlinked_title": "未关联游戏角色", + "account_unlinked_desc": "您需要关联 Minecraft 角色才能认领或操作服务器。请立即前往个人中心绑定。", + "go_link": "前往绑定", + "system_specs": "系统实例与镜像指标", + "spec_images_total": "支持镜像总数", + "spec_images_built": "集群自建镜像", + "spec_images_external": "外部登记镜像", + "spec_servers_total": "可见服务器总数", + "spec_servers_owned": "我拥有的服务器", + "spec_servers_claimable": "公网可认领服", + "spec_running_ratio": "节点运行率", + "spec_stopped_ratio": "节点休眠率", + "spec_failed_ratio": "节点故障率", + "no_servers_monitor": "当前集群暂无服务器实例", + "images_count": "个支持的白名单镜像环境", + "legend_title": "3D 节点监控图例", + "legend_running": "运行中", + "legend_starting": "启动中", + "legend_stopping": "停止中", + "legend_stopped": "已休眠", + "legend_failed": "异常失败", + "legend_unknown": "未知状态", + "welcome_title": "欢迎使用 Felis 编排平台", + "welcome_desc": "您已成功登录用户控制台。您可以在「我的服务器」页面查看并认领分配给您的专属节点,或者对其进行休眠、唤醒和控制台日志监控。", + "go_to_servers_btn": "操作我的服务器" } diff --git a/panel/src/pages/Dashboard.tsx b/panel/src/pages/Dashboard.tsx index f3c1fa7..9708616 100644 --- a/panel/src/pages/Dashboard.tsx +++ b/panel/src/pages/Dashboard.tsx @@ -1,13 +1,25 @@ import { lazy, Suspense, useMemo } from "react"; import { Link } from "react-router-dom"; -import { Activity, Server, Play, Hand } from "lucide-react"; +import { + Activity, + Server, + Play, + CheckCircle, + AlertTriangle, + ArrowRight, + Cpu, + ShieldCheck, + Database, + Box, + Hand, +} from "lucide-react"; import { useTranslation } from "react-i18next"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { Loading, ErrorState, EmptyState } from "@/components/States"; +import { Loading, ErrorState } from "@/components/States"; import { api } from "@/lib/api"; import { useAsync, useConfig } from "@/lib/hooks"; -import type { Phase, ServerInfo } from "@/lib/types"; +import type { Phase, ServerInfo, WhitelistImage } from "@/lib/types"; // three.js is heavy and only the Dashboard renders it — split it into its own // async chunk so the rest of the panel doesn't pay for it on first load. @@ -41,18 +53,32 @@ function Stat({ export function Dashboard() { const cfg = useConfig(); - const { data, error, loading, reload } = useAsync(() => api.myServers(), []); + const { data: servers, error: serversErr, loading: serversLoading, reload: reloadServers } = useAsync(() => api.myServers(), []); + const { data: linkData, error: linkErr, loading: linkLoading } = useAsync(() => api.linkStatus(), []); + const { data: identity, error: meErr, loading: meLoading } = useAsync(() => api.me(), []); + + // 仅对管理员加载镜像白名单列表,防止普通用户请求 403 报错 + const { data: imagesData } = useAsync(() => { + if (identity?.is_admin) { + return api.listImages(); + } + return Promise.resolve([]); + }, [identity]); + const { t } = useTranslation("dashboard"); const counts = useMemo(() => { - const servers = data ?? []; - const by = (p: Phase) => servers.filter((s) => s.phase === p).length; + const list = servers ?? []; + const by = (p: Phase) => list.filter((s) => s.phase === p).length; return { - total: servers.length, + total: list.length, running: by("Running"), - players: servers.reduce((n, s) => n + (s.players ?? 0), 0), + players: list.reduce((n, s) => n + (s.players ?? 0), 0), }; - }, [data]); + }, [servers]); + + const loading = serversLoading || linkLoading || meLoading; + const error = serversErr || linkErr || meErr; return ({t("subtitle")}
+ {t("account_linked_desc")} +
++ {t("account_unlinked_desc")} +
++ {t("welcome_desc")} +
+ + {/* 极简平台新手引导指引(真实且有用) */} +三维体素网络拓扑图
+