From 3a4d6c245c1c85cbec8dea523c16eb816b37c982 Mon Sep 17 00:00:00 2001 From: Flook Date: Sat, 15 Nov 2025 06:41:09 +0700 Subject: [PATCH] =?UTF-8?q?=E0=B8=9E=E0=B8=B1=E0=B8=92=E0=B8=99=E0=B8=B2?= =?UTF-8?q?=20Frontend=20=E0=B9=80=E0=B8=A1=E0=B8=99=E0=B8=B9=20=E0=B9=81?= =?UTF-8?q?=E0=B8=94=E0=B8=8A=E0=B8=9A=E0=B8=AD=E0=B8=A3=E0=B9=8C=E0=B8=94?= =?UTF-8?q?/=E0=B8=A0=E0=B8=B2=E0=B8=9E=E0=B8=A3=E0=B8=A7=E0=B8=A1=20&=20?= =?UTF-8?q?=E0=B8=84=E0=B8=B9=E0=B9=88=E0=B8=A1=E0=B8=B7=E0=B8=AD=E0=B8=81?= =?UTF-8?q?=E0=B8=B2=E0=B8=A3=E0=B9=83=E0=B8=8A=E0=B9=89=E0=B8=87=E0=B8=B2?= =?UTF-8?q?=E0=B8=99=E0=B9=80=E0=B8=9A=E0=B8=B7=E0=B9=89=E0=B8=AD=E0=B8=87?= =?UTF-8?q?=E0=B8=95=E0=B9=89=E0=B8=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/config/sidebarRoutes.jsx | 6 +- web/src/pages/Dashboard.jsx | 144 ++++++++++++++++++++++++++--- web/src/pages/system/UserGuide.jsx | 77 +++++++++++++++ web/src/routes/pageRoutes.jsx | 7 ++ web/src/services/auditApi.js | 21 +++++ 5 files changed, 239 insertions(+), 16 deletions(-) create mode 100644 web/src/pages/system/UserGuide.jsx create mode 100644 web/src/services/auditApi.js diff --git a/web/src/config/sidebarRoutes.jsx b/web/src/config/sidebarRoutes.jsx index 2bb6ae1..5ed4e16 100644 --- a/web/src/config/sidebarRoutes.jsx +++ b/web/src/config/sidebarRoutes.jsx @@ -39,10 +39,10 @@ const routes = [ requiredRole: ['viewer', 'admin', 'manager'], }, { - path: '/dashboard/settings', + path: '/dashboard/guide', icon: , - name: 'ตั้งค่าระบบ', - requiredRole: ['admin'], + name: 'คู่มือการใช้งาน (Guide)', + requiredRole: ['viewer', 'manager', 'admin'], }, ]; diff --git a/web/src/pages/Dashboard.jsx b/web/src/pages/Dashboard.jsx index b67052c..16c1458 100644 --- a/web/src/pages/Dashboard.jsx +++ b/web/src/pages/Dashboard.jsx @@ -1,25 +1,143 @@ import React from 'react'; import TitleCard from '../components/TitleCard'; +import { useModelList } from '../services/modelRegistryApi'; +import { useSystemHealth } from '../services/healthApi'; +import { useInferenceSummary } from '../services/auditApi'; -function Dashboard() { - const TopButtons = ( - +import { FaPlay, FaDatabase, FaClock, FaCheckCircle, FaTimesCircle, FaPercent, FaSyncAlt } from 'react-icons/fa'; +import { Link } from 'react-router-dom'; + +// ---------------------------------------------------- +// Helper Component: สรุปตัวเลขสถิติ (Stats) +// ---------------------------------------------------- +const StatCard = ({ icon, title, value, unit, colorClass, linkPath, isLoading }) => { + + // แสดง loading spinner ถ้ากำลังโหลดข้อมูล + const displayValue = isLoading ? ( + + ) : ( + value ); return ( -
- {/* ใช้งาน TitleCard พร้อมปุ่มด้านบน */} - -

- แสดงสถิติสำคัญและ Model ทั้งหมด +

+
+
+ {icon} +
+
{title}
+
{displayValue} {unit}
+ {linkPath && ( +
+ + ดูรายละเอียด + +
+ )} +
+
+ ); +}; + +function Dashboard() { + // Hooks สำหรับดึงข้อมูลสถานะ + const { data: models, isLoading: modelsLoading } = useModelList(); + const { data: health, isLoading: healthLoading, isFetching: healthFetching } = useSystemHealth(); + const { data: summary, isLoading: summaryLoading } = useInferenceSummary(); + + // --- Logic คำนวณ --- + const activeModelCount = models?.filter(m => m.status === 'ACTIVE').length || 0; + const totalModelCount = models?.length || 0; + + // Health Status + const healthStatus = health?.status || (healthLoading ? 'CHECKING' : 'UNKNOWN'); + const isHealthy = healthStatus === 'Healthy'; + const healthIcon = isHealthy ? : ; + const healthColor = isHealthy ? 'text-success' : 'text-error'; + + // Inference Stats (จาก useInferenceSummary) + const totalRuns = summary?.total_runs || 0; + const successRate = summary?.success_rate || 0; + const avgLatencyMs = summary?.avg_latency_ms || 0; + + + return ( +
+

แดชบอร์ด/ภาพรวม

+ + {/* 1. ส่วนแสดงสถิติสำคัญ (KPI Cards) */} +
+ + {/* Card 1: Model Active Count */} + } + title="Model พร้อมรัน (ACTIVE)" + value={activeModelCount} + unit={`จาก ${totalModelCount}`} + colorClass="text-success" + linkPath="/dashboard/model-registry" + isLoading={modelsLoading} + /> + + {/* Card 2: Total Inference Runs (Audit Log) */} + } + title="งานรันทั้งหมด (24 ชม.)" + value={totalRuns} + unit="ครั้ง" + colorClass="text-info" + isLoading={summaryLoading} + // Note: ต้องมีเมนู Pipeline Logs หรือ Audit Log List + /> + + {/* Card 3: Success Rate (Audit Log) */} + } + title="Success Rate (24 ชม.)" + value={successRate} + unit="%" + colorClass={successRate > 90 ? "text-success" : "text-warning"} + isLoading={summaryLoading} + /> + + {/* Card 4: Avg Latency (Audit Log) */} + } + title="Latency เฉลี่ย (AI)" + value={avgLatencyMs} + unit="ms" + colorClass={avgLatencyMs > 5000 ? "text-error" : "text-warning"} + isLoading={summaryLoading} + /> +
+ + {/* 2. ส่วนแสดงสถานะ Infrastructure (Health) */} + : null} + > + + +

+ ตรวจสอบสถานะของ DB, Redis, MinIO, และ AI Model Endpoints ได้ที่เมนูสถานะระบบ

- {/* เพิ่มส่วนของการแสดง Metric สำคัญ เช่น Stats Component */} +
+ + {/* 3. ส่วนแสดง Log ล่าสุด (Audit Log) */} + +
+                    {summaryLoading ? "กำลังโหลด Log ล่าสุด..." : JSON.stringify(summary?.last_logs || [], null, 2)}
+                
); } -export default Dashboard; +export default Dashboard; \ No newline at end of file diff --git a/web/src/pages/system/UserGuide.jsx b/web/src/pages/system/UserGuide.jsx new file mode 100644 index 0000000..c27e90b --- /dev/null +++ b/web/src/pages/system/UserGuide.jsx @@ -0,0 +1,77 @@ +// src/pages/Guide/UserGuide.jsx +import React from 'react'; +import { FaUpload, FaFlask, FaDatabase, FaServer, FaBook } from 'react-icons/fa'; +import TitleCard from '../../components/TitleCard'; + +export default function UserGuide() { + return ( + } + > + {/* SINGLE COLUMN LAYOUT */} +
+
+ + {/* ภาพรวม */} +
+

ภาพรวมระบบ DDO Console

+

+ DDO Console ถูกออกแบบมาเพื่อเป็น MLOps Gateway สำหรับจัดการวงจรชีวิต + ของ AI Model ทางการแพทย์ (Medical Imaging AI) โดยเฉพาะ... +

+
+ + {/* Model Registry */} +
+

+ 1. Model Registry & Control +

+ +
    +
  • ไปที่เมนู "Model Registry & Control"
  • +
  • การลงทะเบียน: ต้องกรอก Base URL และ Inference Path
  • +
+
+ + {/* Inference */} +
+

+ 2. AI Inference (Run) +

+ +
    +
  • เลือก Model: ต้องเลือกเฉพาะ Model ที่เป็น ACTIVE
  • +
  • อัปโหลดไฟล์: รองรับไฟล์ภาพ NIfTI (.nii)
  • +
+
+ + {/* การเตรียม Model */} +
+

+ 3. การเตรียม Model (Deployment) +

+ +
    +
  • การจัดเก็บ: ไฟล์โมเดลต้องอยู่ใน MinIO bucket 'models'
  • +
  • FastAPI: ต้องมี Health Check (GET)
  • +
+
+ + {/* การตรวจสอบสถานะ */} +
+

+ 4. การตรวจสอบสถานะระบบ (System Ops) +

+ +

+ ใช้เมนู "สถานะระบบ (Health)" เพื่อตรวจสอบความพร้อมของ infrastructure และ Model ACTIVE +

+
+ +
+
+
+ ); +} diff --git a/web/src/routes/pageRoutes.jsx b/web/src/routes/pageRoutes.jsx index 2650131..f248ef0 100644 --- a/web/src/routes/pageRoutes.jsx +++ b/web/src/routes/pageRoutes.jsx @@ -3,6 +3,7 @@ import BlankPage from '../pages/BlankPage'; import ModelRegistry from '../pages/data/ModelRegistry'; import InferenceRun from '../pages/data/InferenceRun'; import SystemHealth from '../pages/system/Health'; +import UserGuide from '../pages/system/UserGuide'; // Array ของเส้นทางย่อยภายใต้ /dashboard/ const pageRoutes = [ @@ -29,6 +30,12 @@ const pageRoutes = [ path: 'health', element: , }, + // --- คู่มือการใช้งาน (Guide) --- + { + // Path: /dashboard/guide + path: 'guide', + element: , + }, // Fallback สำหรับเส้นทางที่ไม่ตรงกับเมนูใดๆ { path: '*', diff --git a/web/src/services/auditApi.js b/web/src/services/auditApi.js new file mode 100644 index 0000000..079d331 --- /dev/null +++ b/web/src/services/auditApi.js @@ -0,0 +1,21 @@ +import { useQuery } from '@tanstack/react-query'; +import axiosClient from './axiosClient'; + +const STALE_TIME = 60000; // 1 นาที + +/** + * Hook สำหรับดึงสรุปสถิติ Inference (Total Runs, Success Rate, Avg Latency) + * Endpoint: GET /api/v1/audit/inference-summary/ + */ +export const useInferenceSummary = () => { + return useQuery({ + queryKey: ['inferenceSummary'], + queryFn: async () => { + const response = await axiosClient.get('/api/v1/audit/inference-summary/'); + return response.data; + }, + staleTime: STALE_TIME, + // ดึงข้อมูลใหม่ทุก 30 วินาทีเพื่ออัปเดตสถิติ Dashboard + refetchInterval: 30000, + }); +}; \ No newline at end of file